linux下vlan的實現分析(上)
阿新 • • 發佈:2019-01-06
一. VLAN的核心概念
1. 劃分VLAN的核心目的只有一個:分割廣播域。
通過VLAN對廣播域進行合理分割之後,一是可以縮小ARP攻擊的範圍,從而提高網路的安全性;二是可以縮小廣播域的大小,從而提高網路的效能。
所以要注意的是,劃分VLAN的目的中根本沒有隔離不同VLAN使用者互訪這一說法,這只是劃分VLAN之後的一種應用,不然使用三層裝置實現不同VLAN
間互訪就成了多此一舉。
2. 單獨的一個VLAN模擬了一個常規的乙太網交換機,因此VLAN實際上就是將一臺物理交換機分割成了多臺邏輯交換機,變相也節省了物理裝置。
站在二層的角度,不同VLAN間是無法通訊的,VLAN間想要進行通訊必須有三層參與。
3. 本地VLAN的實現方式有很多種,常見的有基於埠的VLAN、基於MAC地址的VLAN、基於IP地址的VLAN、基於使用者的VLAN等,這裡略過。
4. 跨裝置的VLAN,標準的實現方法主要就是802.1q,通過VID來識別不同的VLAN,本文主要就是記錄了802.1q的相關內容。
5. 802.1q的應用原則:通常在上行鏈路的第一臺VLAN交換機打上tag,在下行鏈路的最後一臺VLAN交換機去掉tag;
基於802.1q的VLAN劃分既要合理,又要儘量簡單,原則就是隻有當一個數據幀不打tag就不能區分屬於哪個VLAN時才打上tag,
能去掉時儘早去掉。
二. Linux中VLAN的實現原理
Linux中跟VLAN相關的程式碼主要位於net/8021q目錄中。
Linux中的VLAN是一種特殊的虛擬裝置,所有的VLAN裝置必須依賴於它的宿主裝置才能存在。
1. VLAN模組涉及的主要結構
/* 以下定義了整個VLAN模組公用的私有空間(當然公用的前提是同一個網路名稱空間下)
* 具體就是記錄了有關VLAN的proc檔案系統資訊
*/
struct vlan_net {
struct proc_dir_entry *proc_vlan_dir; // proc檔案系統中VLAN的頂層目錄節點
struct proc_dir_entry *proc_vlan_conf; // proc檔案系統中VLAN的配置檔案節點
unsigned short name_type; // vlan裝置名字顯示風格,預設就是eth0.10的風格
}
/* ioctl呼叫SIOCGIFVLAN / SIOCSIFVLAN這兩條命令時傳入的引數結構
* 使用者空間和kernel中都使用到本結構
*/
struct vlan_ioctl_args {
int cmd; // 具體vlan命令
char device1[24]; /* 使用者傳入的裝置名,用於核心查詢實際對應的裝置
呼叫ADD_VLAN_CMD時,該引數傳入的是vlan的宿主裝置名
呼叫SET_VLAN_NAME_TYPE_CMD時,該引數不用填
呼叫其他命令時,該引數傳入的是vlan裝置名
*/
union {
char device2[24]; // 用於返回vlan裝置名
int VID;
unsigned int skb_priority;
unsigned int name_type;
unsigned int bind_type;
unsigned int flag; // 注意點,想要開啟某功能時需要同時設定vlan_qos為非0
}u;
short vlan_qos;
}
/* 定義了每個VLAN裝置附屬的私有空間結構
* 每個VLAN裝置都有一個私有空間vlan_dev_priv,同時整個VLAN模組的私有空間vlan_net是所有VLAN裝置共有的
*/
struct vlan_dev_priv {
unsigned int nr_ingress_mappings;
u32 ingress_priority_map[8];
unsigned int nr_egress_mappings;
struct vlan_priority_tci_mapping *egress_priority_map[16];
__be16 vlan_proto; // vlan協議型別(大端)
u16 vlan_id; // vlan id
u16 flags; // VLAN_FLAG_* 用來記錄該vlan裝置的幾個特徵標誌,預設總是設定了REORDER_HDR
struct net_device *real_dev; // 指向宿主裝置
unsigned char real_dev_addr[ETH_ALEN]; // 宿主裝置mac
struct proc_dir_entry *dent;
struct vlan_pcpu_stats __percpu *vlan_pcpu_stats;
#ifdef CONFIG_NET_POLL_CONTROLLER
struct netpoll *netpoll;
#endif
unsigned int nest_level;
}
/* 定義一個記錄vlan裝置資訊的結構,儲存了一個宿主裝置上繫結的所有vlan裝置的資訊
* 這個結構是宿主裝置用來管理其下轄的所有vlan裝置的
*/
struct vlan_info {
struct net_device *real_dev; // 指向宿主裝置
struct vlan_group grp; // vlan組,記錄了所有繫結在該宿主裝置上的vlan裝置
struct list_head vid_list; // vlan id的連結串列頭
unsigned int nr_vids; // 記錄了該vlan id連結串列中節點數量
struct rcu_head rcu;
}
/* 定義同一個宿主裝置下的vlan組模型
* 儲存的vlan裝置呈現四維結構:VLAN_PROTO_NUM * VLAN_GROUP_ARRAY_SPLIT_PARTS * VLAN_GROUP_ARRAY_PART_LEN * struct net_device指標
*/
struct vlan_group {
unsigned int nr_vlan_devs; // 記錄了該vlan組中包含的vlan裝置數量
struct hlist_node hlist;
struct net_device **vlan_devices_arrays[VLAN_PROTO_NUM]
[VLAN_GROUP_ARRAY_SPLIT_PARTS]; // 記錄了該vlan組中包含的所有vlan裝置
}
2. VLAN模組的初始化
int vlan_net_id; // 用於記錄vlan模組在所有網路名稱空間中的ID號(註冊到網路名稱空間時分配)
// 以下是整個VLAN模組的初始化入口
int __init vlan_proto_init(void)
{
// 將vlan模組註冊到每一個網路名稱空間,注意就是在這裡完成了模組私有空間的申請,對於VLAN來說就是vlan_net,並且執行了vlan_init_net
register_pernet_subsys(&vlan_net_ops);
// 向通知鏈中註冊一個vlan事件通知塊
register_netdevice_notifier(&vlan_notifier_block);
// 初始化操作vlan用的netlink介面
vlan_netlink_init();
// 設定用於vlan操作的ioctl鉤子函式
vlan_ioctl_set(vlan_ioctl_handler);
}
小結:以上4步操作基本是載入一個頂層網路模組的通用格式,
而模組特有的那部分初始化通常就是放在pernet_operations->init指向的函式中,以VLAN為例,就是vlan_init_net
// 以下就是VLAN模組特有的那部分初始化
int __net_init vlan_init_net(struct net *net)
{
// 根據vlan_net_id索引對應的私有空間,也就是vlan_net
struct vlan_net *vn = net_generic(net, vlan_net_id);
// 設定vlan裝置名的預設顯示風格,類似 eth0.10
vn->name_type = VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD;
// 在proc檔案系統中建立vlan介面(/proc/net/vlan)
vlan_proc_init(net);
}
int __net_init vlan_proc_init(struct net *net)
{
// 根據vlan_net_id索引得到對應的vlan_net結構
struct vlan_net *vn = net_generic(net, vlan_net_id);
// proc檔案系統下建立/proc/net/vlan目錄
vn->proc_vlan_dir = proc_net_mkdir(net, name_root, net->proc_net);
// proc檔案系統下建立/proc/net/vlan/config檔案(檔案屬性:普通檔案 + 可讀可寫)
vn->proc_vlan_conf = proc_create(name_conf, S_IFREG|S_IRUSR|S_IWUSR,vn->proc_vlan_dir, &vlan_fops);
}
小結:這部分VLAN特有的初始化,具體主要就是建立了proc檔案系統下的VLAN節點
3. VLAN模組的功能管理
對VLAN模組的功能管理主要基於2種介面方式:ioctl和netlink
3.1 ioctl方式分析
VLAN模組初始化過程中註冊了對應的ioctl鉤子函式vlan_ioctl_hook,
這樣,當用戶空間執行ioctl呼叫SIOCGIFVLAN/SIOCSIFVLAN這兩條命令時,對應的vlan_ioctl_handler將被執行
int vlan_ioctl_handler(struct net *net, void __user *arg)
{
// 將ioctl的vlan傳入引數從使用者空間拷貝到核心
copy_from_user(&args, arg, sizeof(struct vlan_ioctl_args));
switch (args.cmd)
{
case SET_VLAN_INGRESS_PRIORITY_CMD:
case SET_VLAN_EGRESS_PRIORITY_CMD:
case SET_VLAN_FLAG_CMD:
case ADD_VLAN_CMD:
case DEL_VLAN_CMD:
case GET_VLAN_REALDEV_NAME_CMD:
case GET_VLAN_VID_CMD:
// 根據使用者傳入的裝置名查詢對應的裝置管理塊
dev = __dev_get_by_name(net, args.device1);
// 以上這些命令中除了ADD_VLAN_CMD,必須確保device1傳入的裝置名指的是vlan裝置
if (args.cmd != ADD_VLAN_CMD && !is_vlan_dev(dev))
goto out;
}
switch (args.cmd)
{
case SET_VLAN_INGRESS_PRIORITY_CMD: // 暫略
break;
case SET_VLAN_EGRESS_PRIORITY_CMD: // 暫略
break;
case SET_VLAN_FLAG_CMD:
// 設定vlan標誌位命令的使用者需要進行管理員級別許可權測試
if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
break;
// 如果傳入的vlan裝置名風格有效則更新
if ((args.u.name_type >= 0) &&(args.u.name_type < VLAN_NAME_TYPE_HIGHEST))
{
struct vlan_net *vn;
vn = net_generic(net, vlan_net_id);
vn->name_type = args.u.name_type;
}
break;
case ADD_VLAN_CMD:
// 新增vlan裝置的使用者需要進行管理員級別許可權測試
if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
break;
// 在宿主裝置上註冊一個新的vlan裝置
register_vlan_device(dev, args.u.VID);
break;
case DEL_VLAN_CMD:
// 刪除vlan裝置的使用者需要進行管理員級別許可權測試
if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
break;
// 登出vlan裝置
unregister_vlan_dev(dev, NULL);
break;
case GET_VLAN_REALDEV_NAME_CMD:
// 獲取vlan裝置對應的宿主裝置名
vlan_dev_get_realdev_name(dev, args.u.device2);
// 拷貝回用戶空間
copy_to_user(arg, &args,sizeof(struct vlan_ioctl_args));
break;
case GET_VLAN_VID_CMD:
// 獲取該vlan裝置的vid
args.u.VID = vlan_dev_vlan_id(dev);
// 拷貝回用戶空間
copy_to_user(arg, &args,sizeof(struct vlan_ioctl_args));
break;
}
}
3.1.1 VLAN裝置的建立
/* 在宿主裝置上註冊一個新的VLAN裝置
* @real_dev - 宿主裝置(一般就是真實的網絡卡,但對交換機來說,可能是DSA中建立的虛擬埠)
* @vlan_id - 要建立的VLAN裝置的ID
*/
int register_vlan_device(struct net_device *real_dev, u16 vlan_id)
{
// 首先獲取宿主裝置所在的網路名稱空間
struct net *net = dev_net(real_dev);
// 然後根據vlan_net_id在索引得到對應的私有空間,也就是vlan_net
struct vlan_net *vn = net_generic(net, vlan_net_id);
// vlan id號不能超過4096
if (vlan_id >= VLAN_VID_MASK)
return -ERANGE;
// 檢查宿主裝置是否支援vlan協議以及要建立的vlan id在該裝置上是否已經存在
vlan_check_real_dev(real_dev, htons(ETH_P_8021Q), vlan_id);
// 根據vlan裝置不同的顯示風格生成相應的vlan裝置名
switch(vn->name_type)
{
case VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD:
snprintf(name, IFNAMSIZ, "%s.%i", real_dev->name, vlan_id);
break;
...
}
// 建立一個vlan裝置,並執行vlan_setup完成基本的初始化
new_dev = alloc_netdev(sizeof(struct vlan_dev_priv), name, vlan_setup);
// 將新建立的vlan裝置關聯到當前的網路名稱空間
dev_net_set(new_dev, net);
new_dev->mtu = real_dev->mtu; // 將宿主裝置的mtu繼承到vlan裝置
new_dev->priv_flags |= (real_dev->priv_flags & IFF_UNICAST_FLT); // 如果宿主裝置支援單播過濾功能,則同樣繼承到vlan裝置
// 初始化vlan裝置附屬的私有空間vlan_dev_priv
vlan = vlan_dev_priv(new_dev);
vlan->vlan_proto = htons(ETH_P_8021Q);
vlan->vlan_id = vlan_id;
vlan->real_dev = real_dev;
vlan->dent = NULL;
vlan->flags = VLAN_FLAG_REORDER_HDR;
new_dev->rtnl_link_ops = &vlan_link_ops;// 註冊netlink介面操作集合用於管理該vlan裝置
// 進一步註冊該vlan裝置
register_vlan_dev(new_dev);
}
/* 每個新建立的vlan裝置初始化回撥函式
*/
void vlan_setup(struct net_device *dev)
{
// 為vlan裝置設定一些鏈路層基本引數
ether_setup(dev);
dev->priv_flags |= IFF_802_1Q_VLAN; // 表明這是一個vlan裝置
dev->priv_flags &= ~(IFF_XMIT_DST_RELEASE | IFF_TX_SKB_SHARING);
dev->tx_queue_len = 0;
dev->netdev_ops = &vlan_netdev_ops; // 註冊vlan裝置管理操作回撥函式集
dev->destructor = vlan_dev_free; // 註冊vlan設備註銷回調函式
dev->ethtool_ops = &vlan_ethtool_ops; // 註冊使用ethtool工具操作vlan裝置的回撥函式集
memset(dev->broadcast, 0, ETH_ALEN); // 清零廣播mac
}
/* 進一步註冊該vlan裝置
*/
int register_vlan_dev(struct net_device *dev)
{
struct vlan_dev_priv *vlan = vlan_dev_priv(dev); // 獲取vlan裝置附屬的私有空間
struct net_device *real_dev = vlan->real_dev; // 獲取宿主裝置
u16 vlan_id = vlan->vlan_id; // 獲取vlan id
// 將指定vlan協議ID的vlan id新增到宿主裝置的vlan_info管理塊中
vlan_vid_add(real_dev, vlan->vlan_proto, vlan_id);
// 確保程式執行到這裡時宿主裝置的vlan_info成員不再為空
vlan_info = rtnl_dereference(real_dev->vlan_info);
// 對四維結構的vlan組模型在第三維度上進行預分配
vlan_group_prealloc_vid(grp, vlan->vlan_proto, vlan_id);
// 設定該vlan裝置的巢狀級別
vlan->nest_level = dev_get_nest_level(real_dev, is_vlan_dev) + 1;
// 註冊該網路裝置到核心中,註冊結果會從通知鏈中反饋
register_netdevice(dev);
// 將vlan裝置加入宿主裝置的upper鄰接連結串列中
netdev_upper_dev_link(real_dev, dev);
// 宿主裝置的引用計數加1
dev_hold(real_dev);
// 從宿主裝置繼承一些狀態(比如裝置的鏈路狀態)
netif_stacked_transfer_operstate(real_dev, dev);
// 將vlan裝置加入lweventlist通知鏈
linkwatch_fire_event(dev);
// 最後一步,將指定的vlan裝置記錄到四維vlan組模型中
vlan_group_set_device(grp, vlan->vlan_proto, vlan_id, dev);
grp->nr_vlan_devs++;
}
3.2 netlink方式分析
VLAN模組初始化過程中還通過vlan_netlink_init註冊了一組netlink介面vlan_link_ops用於操作VLAN模組
3.2.1 VLAN裝置的建立
/* 註冊並配置一個新的vlan裝置
* @src_net - 所處的網路名稱空間
* @dev - 新建立的網路裝置
* @tb[] - ?
* @data[] - ?
*
* 備註:對應ioctl介面的函式register_vlan_device,主要的區別在於呼叫本函式前vlan裝置管理塊已經被建立
*/
int vlan_newlink(struct net *src_net, struct net_device *dev,struct nlattr *tb[], struct nlattr *data[])
{
struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
// 檢查是否有傳入IFLA_VLAN_ID屬性
if (!data[IFLA_VLAN_ID])
return -EINVAL;
// 檢查是否有傳入IFLA_LINK屬性
if (!tb[IFLA_LINK])
return -EINVAL;
// 從IFLA_LINK屬性中獲取宿主裝置介面序號,再根據介面序號索引得到對應的宿主裝置
real_dev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));
// 如果傳入了IFLA_VLAN_PROTOCOL屬性則從中獲取vlan的協議型別,否則採用預設的vlan協議型別
if (data[IFLA_VLAN_PROTOCOL])
proto = nla_get_be16(data[IFLA_VLAN_PROTOCOL]);
else
proto = htons(ETH_P_8021Q);
// 初始化vlan裝置附屬的私有空間vlan_dev_priv
vlan->vlan_proto = proto;
vlan->vlan_id = nla_get_u16(data[IFLA_VLAN_ID]);
vlan->real_dev = real_dev;
vlan->flags = VLAN_FLAG_REORDER_HDR;
// 檢查宿主裝置是否支援vlan協議以及要建立的vlan id在該裝置上是否已經存在
vlan_check_real_dev(real_dev, vlan->vlan_proto, vlan->vlan_id);
// 如果沒有傳入IFLA_MTU屬性則從宿主裝置繼承mtu值,否則意味著使用了自定義的mtu值,這時候需要確保自定義值不大於宿主裝置上的mtu值
if (!tb[IFLA_MTU])
dev->mtu = real_dev->mtu;
else if (dev->mtu > real_dev->mtu)
return -EINVAL;
// 該vlan裝置在這裡繼續處理來自使用者空間設定的引數
vlan_changelink(dev, tb, data);
// 進一步註冊該vlan裝置
return register_vlan_dev(dev);
}
1. 劃分VLAN的核心目的只有一個:分割廣播域。
通過VLAN對廣播域進行合理分割之後,一是可以縮小ARP攻擊的範圍,從而提高網路的安全性;二是可以縮小廣播域的大小,從而提高網路的效能。
所以要注意的是,劃分VLAN的目的中根本沒有隔離不同VLAN使用者互訪這一說法,這只是劃分VLAN之後的一種應用,不然使用三層裝置實現不同VLAN
間互訪就成了多此一舉。
2. 單獨的一個VLAN模擬了一個常規的乙太網交換機,因此VLAN實際上就是將一臺物理交換機分割成了多臺邏輯交換機,變相也節省了物理裝置。
站在二層的角度,不同VLAN間是無法通訊的,VLAN間想要進行通訊必須有三層參與。
3. 本地VLAN的實現方式有很多種,常見的有基於埠的VLAN、基於MAC地址的VLAN、基於IP地址的VLAN、基於使用者的VLAN等,這裡略過。
4. 跨裝置的VLAN,標準的實現方法主要就是802.1q,通過VID來識別不同的VLAN,本文主要就是記錄了802.1q的相關內容。
5. 802.1q的應用原則:通常在上行鏈路的第一臺VLAN交換機打上tag,在下行鏈路的最後一臺VLAN交換機去掉tag;
基於802.1q的VLAN劃分既要合理,又要儘量簡單,原則就是隻有當一個數據幀不打tag就不能區分屬於哪個VLAN時才打上tag,
能去掉時儘早去掉。
二. Linux中VLAN的實現原理
Linux中跟VLAN相關的程式碼主要位於net/8021q目錄中。
Linux中的VLAN是一種特殊的虛擬裝置,所有的VLAN裝置必須依賴於它的宿主裝置才能存在。
1. VLAN模組涉及的主要結構
/* 以下定義了整個VLAN模組公用的私有空間(當然公用的前提是同一個網路名稱空間下)
* 具體就是記錄了有關VLAN的proc檔案系統資訊
*/
struct vlan_net {
struct proc_dir_entry *proc_vlan_dir; // proc檔案系統中VLAN的頂層目錄節點
struct proc_dir_entry *proc_vlan_conf; // proc檔案系統中VLAN的配置檔案節點
unsigned short name_type; // vlan裝置名字顯示風格,預設就是eth0.10的風格
}
/* ioctl呼叫SIOCGIFVLAN / SIOCSIFVLAN這兩條命令時傳入的引數結構
* 使用者空間和kernel中都使用到本結構
*/
struct vlan_ioctl_args {
int cmd; // 具體vlan命令
char device1[24]; /* 使用者傳入的裝置名,用於核心查詢實際對應的裝置
呼叫ADD_VLAN_CMD時,該引數傳入的是vlan的宿主裝置名
呼叫SET_VLAN_NAME_TYPE_CMD時,該引數不用填
呼叫其他命令時,該引數傳入的是vlan裝置名
*/
union {
char device2[24]; // 用於返回vlan裝置名
int VID;
unsigned int skb_priority;
unsigned int name_type;
unsigned int bind_type;
unsigned int flag; // 注意點,想要開啟某功能時需要同時設定vlan_qos為非0
}u;
short vlan_qos;
}
/* 定義了每個VLAN裝置附屬的私有空間結構
* 每個VLAN裝置都有一個私有空間vlan_dev_priv,同時整個VLAN模組的私有空間vlan_net是所有VLAN裝置共有的
*/
struct vlan_dev_priv {
unsigned int nr_ingress_mappings;
u32 ingress_priority_map[8];
unsigned int nr_egress_mappings;
struct vlan_priority_tci_mapping *egress_priority_map[16];
__be16 vlan_proto; // vlan協議型別(大端)
u16 vlan_id; // vlan id
u16 flags; // VLAN_FLAG_* 用來記錄該vlan裝置的幾個特徵標誌,預設總是設定了REORDER_HDR
struct net_device *real_dev; // 指向宿主裝置
unsigned char real_dev_addr[ETH_ALEN]; // 宿主裝置mac
struct proc_dir_entry *dent;
struct vlan_pcpu_stats __percpu *vlan_pcpu_stats;
#ifdef CONFIG_NET_POLL_CONTROLLER
struct netpoll *netpoll;
#endif
unsigned int nest_level;
}
/* 定義一個記錄vlan裝置資訊的結構,儲存了一個宿主裝置上繫結的所有vlan裝置的資訊
* 這個結構是宿主裝置用來管理其下轄的所有vlan裝置的
*/
struct vlan_info {
struct net_device *real_dev; // 指向宿主裝置
struct vlan_group grp; // vlan組,記錄了所有繫結在該宿主裝置上的vlan裝置
struct list_head vid_list; // vlan id的連結串列頭
unsigned int nr_vids; // 記錄了該vlan id連結串列中節點數量
struct rcu_head rcu;
}
/* 定義同一個宿主裝置下的vlan組模型
* 儲存的vlan裝置呈現四維結構:VLAN_PROTO_NUM * VLAN_GROUP_ARRAY_SPLIT_PARTS * VLAN_GROUP_ARRAY_PART_LEN * struct net_device指標
*/
struct vlan_group {
unsigned int nr_vlan_devs; // 記錄了該vlan組中包含的vlan裝置數量
struct hlist_node hlist;
struct net_device **vlan_devices_arrays[VLAN_PROTO_NUM]
[VLAN_GROUP_ARRAY_SPLIT_PARTS]; // 記錄了該vlan組中包含的所有vlan裝置
}
2. VLAN模組的初始化
int vlan_net_id; // 用於記錄vlan模組在所有網路名稱空間中的ID號(註冊到網路名稱空間時分配)
// 以下是整個VLAN模組的初始化入口
int __init vlan_proto_init(void)
{
// 將vlan模組註冊到每一個網路名稱空間,注意就是在這裡完成了模組私有空間的申請,對於VLAN來說就是vlan_net,並且執行了vlan_init_net
register_pernet_subsys(&vlan_net_ops);
// 向通知鏈中註冊一個vlan事件通知塊
register_netdevice_notifier(&vlan_notifier_block);
// 初始化操作vlan用的netlink介面
vlan_netlink_init();
// 設定用於vlan操作的ioctl鉤子函式
vlan_ioctl_set(vlan_ioctl_handler);
}
小結:以上4步操作基本是載入一個頂層網路模組的通用格式,
而模組特有的那部分初始化通常就是放在pernet_operations->init指向的函式中,以VLAN為例,就是vlan_init_net
// 以下就是VLAN模組特有的那部分初始化
int __net_init vlan_init_net(struct net *net)
{
// 根據vlan_net_id索引對應的私有空間,也就是vlan_net
struct vlan_net *vn = net_generic(net, vlan_net_id);
// 設定vlan裝置名的預設顯示風格,類似 eth0.10
vn->name_type = VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD;
// 在proc檔案系統中建立vlan介面(/proc/net/vlan)
vlan_proc_init(net);
}
int __net_init vlan_proc_init(struct net *net)
{
// 根據vlan_net_id索引得到對應的vlan_net結構
struct vlan_net *vn = net_generic(net, vlan_net_id);
// proc檔案系統下建立/proc/net/vlan目錄
vn->proc_vlan_dir = proc_net_mkdir(net, name_root, net->proc_net);
// proc檔案系統下建立/proc/net/vlan/config檔案(檔案屬性:普通檔案 + 可讀可寫)
vn->proc_vlan_conf = proc_create(name_conf, S_IFREG|S_IRUSR|S_IWUSR,vn->proc_vlan_dir, &vlan_fops);
}
小結:這部分VLAN特有的初始化,具體主要就是建立了proc檔案系統下的VLAN節點
3. VLAN模組的功能管理
對VLAN模組的功能管理主要基於2種介面方式:ioctl和netlink
3.1 ioctl方式分析
VLAN模組初始化過程中註冊了對應的ioctl鉤子函式vlan_ioctl_hook,
這樣,當用戶空間執行ioctl呼叫SIOCGIFVLAN/SIOCSIFVLAN這兩條命令時,對應的vlan_ioctl_handler將被執行
int vlan_ioctl_handler(struct net *net, void __user *arg)
{
// 將ioctl的vlan傳入引數從使用者空間拷貝到核心
copy_from_user(&args, arg, sizeof(struct vlan_ioctl_args));
switch (args.cmd)
{
case SET_VLAN_INGRESS_PRIORITY_CMD:
case SET_VLAN_EGRESS_PRIORITY_CMD:
case SET_VLAN_FLAG_CMD:
case ADD_VLAN_CMD:
case DEL_VLAN_CMD:
case GET_VLAN_REALDEV_NAME_CMD:
case GET_VLAN_VID_CMD:
// 根據使用者傳入的裝置名查詢對應的裝置管理塊
dev = __dev_get_by_name(net, args.device1);
// 以上這些命令中除了ADD_VLAN_CMD,必須確保device1傳入的裝置名指的是vlan裝置
if (args.cmd != ADD_VLAN_CMD && !is_vlan_dev(dev))
goto out;
}
switch (args.cmd)
{
case SET_VLAN_INGRESS_PRIORITY_CMD: // 暫略
break;
case SET_VLAN_EGRESS_PRIORITY_CMD: // 暫略
break;
case SET_VLAN_FLAG_CMD:
// 設定vlan標誌位命令的使用者需要進行管理員級別許可權測試
if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
break;
// 如果傳入的vlan裝置名風格有效則更新
if ((args.u.name_type >= 0) &&(args.u.name_type < VLAN_NAME_TYPE_HIGHEST))
{
struct vlan_net *vn;
vn = net_generic(net, vlan_net_id);
vn->name_type = args.u.name_type;
}
break;
case ADD_VLAN_CMD:
// 新增vlan裝置的使用者需要進行管理員級別許可權測試
if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
break;
// 在宿主裝置上註冊一個新的vlan裝置
register_vlan_device(dev, args.u.VID);
break;
case DEL_VLAN_CMD:
// 刪除vlan裝置的使用者需要進行管理員級別許可權測試
if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
break;
// 登出vlan裝置
unregister_vlan_dev(dev, NULL);
break;
case GET_VLAN_REALDEV_NAME_CMD:
// 獲取vlan裝置對應的宿主裝置名
vlan_dev_get_realdev_name(dev, args.u.device2);
// 拷貝回用戶空間
copy_to_user(arg, &args,sizeof(struct vlan_ioctl_args));
break;
case GET_VLAN_VID_CMD:
// 獲取該vlan裝置的vid
args.u.VID = vlan_dev_vlan_id(dev);
// 拷貝回用戶空間
copy_to_user(arg, &args,sizeof(struct vlan_ioctl_args));
break;
}
}
3.1.1 VLAN裝置的建立
/* 在宿主裝置上註冊一個新的VLAN裝置
* @real_dev - 宿主裝置(一般就是真實的網絡卡,但對交換機來說,可能是DSA中建立的虛擬埠)
* @vlan_id - 要建立的VLAN裝置的ID
*/
int register_vlan_device(struct net_device *real_dev, u16 vlan_id)
{
// 首先獲取宿主裝置所在的網路名稱空間
struct net *net = dev_net(real_dev);
// 然後根據vlan_net_id在索引得到對應的私有空間,也就是vlan_net
struct vlan_net *vn = net_generic(net, vlan_net_id);
// vlan id號不能超過4096
if (vlan_id >= VLAN_VID_MASK)
return -ERANGE;
// 檢查宿主裝置是否支援vlan協議以及要建立的vlan id在該裝置上是否已經存在
vlan_check_real_dev(real_dev, htons(ETH_P_8021Q), vlan_id);
// 根據vlan裝置不同的顯示風格生成相應的vlan裝置名
switch(vn->name_type)
{
case VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD:
snprintf(name, IFNAMSIZ, "%s.%i", real_dev->name, vlan_id);
break;
...
}
// 建立一個vlan裝置,並執行vlan_setup完成基本的初始化
new_dev = alloc_netdev(sizeof(struct vlan_dev_priv), name, vlan_setup);
// 將新建立的vlan裝置關聯到當前的網路名稱空間
dev_net_set(new_dev, net);
new_dev->mtu = real_dev->mtu; // 將宿主裝置的mtu繼承到vlan裝置
new_dev->priv_flags |= (real_dev->priv_flags & IFF_UNICAST_FLT); // 如果宿主裝置支援單播過濾功能,則同樣繼承到vlan裝置
// 初始化vlan裝置附屬的私有空間vlan_dev_priv
vlan = vlan_dev_priv(new_dev);
vlan->vlan_proto = htons(ETH_P_8021Q);
vlan->vlan_id = vlan_id;
vlan->real_dev = real_dev;
vlan->dent = NULL;
vlan->flags = VLAN_FLAG_REORDER_HDR;
new_dev->rtnl_link_ops = &vlan_link_ops;// 註冊netlink介面操作集合用於管理該vlan裝置
// 進一步註冊該vlan裝置
register_vlan_dev(new_dev);
}
/* 每個新建立的vlan裝置初始化回撥函式
*/
void vlan_setup(struct net_device *dev)
{
// 為vlan裝置設定一些鏈路層基本引數
ether_setup(dev);
dev->priv_flags |= IFF_802_1Q_VLAN; // 表明這是一個vlan裝置
dev->priv_flags &= ~(IFF_XMIT_DST_RELEASE | IFF_TX_SKB_SHARING);
dev->tx_queue_len = 0;
dev->netdev_ops = &vlan_netdev_ops; // 註冊vlan裝置管理操作回撥函式集
dev->destructor = vlan_dev_free; // 註冊vlan設備註銷回調函式
dev->ethtool_ops = &vlan_ethtool_ops; // 註冊使用ethtool工具操作vlan裝置的回撥函式集
memset(dev->broadcast, 0, ETH_ALEN); // 清零廣播mac
}
/* 進一步註冊該vlan裝置
*/
int register_vlan_dev(struct net_device *dev)
{
struct vlan_dev_priv *vlan = vlan_dev_priv(dev); // 獲取vlan裝置附屬的私有空間
struct net_device *real_dev = vlan->real_dev; // 獲取宿主裝置
u16 vlan_id = vlan->vlan_id; // 獲取vlan id
// 將指定vlan協議ID的vlan id新增到宿主裝置的vlan_info管理塊中
vlan_vid_add(real_dev, vlan->vlan_proto, vlan_id);
// 確保程式執行到這裡時宿主裝置的vlan_info成員不再為空
vlan_info = rtnl_dereference(real_dev->vlan_info);
// 對四維結構的vlan組模型在第三維度上進行預分配
vlan_group_prealloc_vid(grp, vlan->vlan_proto, vlan_id);
// 設定該vlan裝置的巢狀級別
vlan->nest_level = dev_get_nest_level(real_dev, is_vlan_dev) + 1;
// 註冊該網路裝置到核心中,註冊結果會從通知鏈中反饋
register_netdevice(dev);
// 將vlan裝置加入宿主裝置的upper鄰接連結串列中
netdev_upper_dev_link(real_dev, dev);
// 宿主裝置的引用計數加1
dev_hold(real_dev);
// 從宿主裝置繼承一些狀態(比如裝置的鏈路狀態)
netif_stacked_transfer_operstate(real_dev, dev);
// 將vlan裝置加入lweventlist通知鏈
linkwatch_fire_event(dev);
// 最後一步,將指定的vlan裝置記錄到四維vlan組模型中
vlan_group_set_device(grp, vlan->vlan_proto, vlan_id, dev);
grp->nr_vlan_devs++;
}
3.2 netlink方式分析
VLAN模組初始化過程中還通過vlan_netlink_init註冊了一組netlink介面vlan_link_ops用於操作VLAN模組
3.2.1 VLAN裝置的建立
/* 註冊並配置一個新的vlan裝置
* @src_net - 所處的網路名稱空間
* @dev - 新建立的網路裝置
* @tb[] - ?
* @data[] - ?
*
* 備註:對應ioctl介面的函式register_vlan_device,主要的區別在於呼叫本函式前vlan裝置管理塊已經被建立
*/
int vlan_newlink(struct net *src_net, struct net_device *dev,struct nlattr *tb[], struct nlattr *data[])
{
struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
// 檢查是否有傳入IFLA_VLAN_ID屬性
if (!data[IFLA_VLAN_ID])
return -EINVAL;
// 檢查是否有傳入IFLA_LINK屬性
if (!tb[IFLA_LINK])
return -EINVAL;
// 從IFLA_LINK屬性中獲取宿主裝置介面序號,再根據介面序號索引得到對應的宿主裝置
real_dev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));
// 如果傳入了IFLA_VLAN_PROTOCOL屬性則從中獲取vlan的協議型別,否則採用預設的vlan協議型別
if (data[IFLA_VLAN_PROTOCOL])
proto = nla_get_be16(data[IFLA_VLAN_PROTOCOL]);
else
proto = htons(ETH_P_8021Q);
// 初始化vlan裝置附屬的私有空間vlan_dev_priv
vlan->vlan_proto = proto;
vlan->vlan_id = nla_get_u16(data[IFLA_VLAN_ID]);
vlan->real_dev = real_dev;
vlan->flags = VLAN_FLAG_REORDER_HDR;
// 檢查宿主裝置是否支援vlan協議以及要建立的vlan id在該裝置上是否已經存在
vlan_check_real_dev(real_dev, vlan->vlan_proto, vlan->vlan_id);
// 如果沒有傳入IFLA_MTU屬性則從宿主裝置繼承mtu值,否則意味著使用了自定義的mtu值,這時候需要確保自定義值不大於宿主裝置上的mtu值
if (!tb[IFLA_MTU])
dev->mtu = real_dev->mtu;
else if (dev->mtu > real_dev->mtu)
return -EINVAL;
// 該vlan裝置在這裡繼續處理來自使用者空間設定的引數
vlan_changelink(dev, tb, data);
// 進一步註冊該vlan裝置
return register_vlan_dev(dev);
}