第三部分: ioctl管理網(wǎng)橋 3.1 通過ioctl系統(tǒng)調(diào)用創(chuàng)建網(wǎng)橋 仍然以前的配置作為例,我們分用戶空間程序brctl是如何通過ioctl系統(tǒng)調(diào)用在kernel空間內(nèi)創(chuàng)建上述的數(shù)據(jù)結(jié)構(gòu)。創(chuàng)建網(wǎng)橋,我們不需要預(yù)知任何網(wǎng)絡(luò)設(shè)備信息,因此我們通過ioctl來創(chuàng)建網(wǎng)橋時不應(yīng)該與任何網(wǎng)絡(luò)設(shè)備綁定到一起。網(wǎng)橋模塊為此ioctl函數(shù)提供了一個恰如其分的名字 br_ioctl_deviceless_stub。Brctl工具使用的ioctl系統(tǒng)調(diào)用最終會調(diào)用此函數(shù),它相關(guān)代碼如下:
[linux-2.6.24.4/net/bridge/br.c]
brioctl_set(br_ioctl_deviceless_stub);
[linux-2.6.24.4/net/socket.c]
void brioctl_set(int (*hook) (struct net *, unsigned int, void __user *)) { mutex_lock(&br_ioctl_mutex); br_ioctl_hook = hook; mutex_unlock(&br_ioctl_mutex); } void brioctl_set(int (*hook) (struct net *, unsigned int, void __user *)) { mutex_lock(&br_ioctl_mutex); br_ioctl_hook = hook; mutex_unlock(&br_ioctl_mutex); }
用戶空間程序使用網(wǎng)橋相關(guān)的命令來調(diào)用ioctl函數(shù)時,它經(jīng)kernel依據(jù)命令所屬的分類分派到sock_ioctl函數(shù)。在sock_ioctl函數(shù)里面,當(dāng)ioctl命令為SIOCGIFBR,SIOCSIFBR, SIOCBRADDBR 和SIOCBRDELBR,它將ioctl的請求轉(zhuǎn)發(fā)到br_ioctl_deviceless_stub函數(shù)。
Br_ioctl_deviceless_stub函數(shù)代碼和分析如下:
[linux-2.6.24.4/net/bridge/br_ioctl.c]
int br_ioctl_deviceless_stub(struct net *net, unsigned int cmd, void __user *uarg) { switch (cmd) { case SIOCGIFBR: case SIOCSIFBR: // 這兩個網(wǎng)橋命令是比較老式的,我們在這里不作討論 return old_deviceless(uarg); // 新式的網(wǎng)橋ioctl命令有兩個,添加新網(wǎng)橋和刪除現(xiàn)有的網(wǎng)橋 // 需要用戶空間提供網(wǎng)橋的名字。 case SIOCBRADDBR: case SIOCBRDELBR: { char buf[IFNAMSIZ]; if (!capable(CAP_NET_ADMIN)) return -EPERM; if (copy_from_user(buf, uarg, IFNAMSIZ)) return -EFAULT; buf[IFNAMSIZ-1] = 0; if (cmd == SIOCBRADDBR) return br_add_bridge(buf); return br_del_bridge(buf); } } return -EOPNOTSUPP; } int br_ioctl_deviceless_stub(struct net *net, unsigned int cmd, void __user *uarg) { switch (cmd) { case SIOCGIFBR: case SIOCSIFBR: // 這兩個網(wǎng)橋命令是比較老式的,我們在這里不作討論 return old_deviceless(uarg); // 新式的網(wǎng)橋ioctl命令有兩個,添加新網(wǎng)橋和刪除現(xiàn)有的網(wǎng)橋 // 需要用戶空間提供網(wǎng)橋的名字。 case SIOCBRADDBR: case SIOCBRDELBR: { char buf[IFNAMSIZ]; if (!capable(CAP_NET_ADMIN)) return -EPERM; if (copy_from_user(buf, uarg, IFNAMSIZ)) return -EFAULT; buf[IFNAMSIZ-1] = 0; if (cmd == SIOCBRADDBR) return br_add_bridge(buf); return br_del_bridge(buf); } } return -EOPNOTSUPP; }
該函數(shù)調(diào)用br_add_bridge和br_del_brdge函數(shù)的實現(xiàn)新建和刪除網(wǎng)橋的功能。由于這兩個函數(shù)所完成的事情剛好相反,在此,我們只討論br_add_bridge的代碼:
[linux-2.6.24.4/net/bridge/br_if.c]
int br_add_bridge(const char *name) { struct net_device *dev; int ret; // 創(chuàng)建網(wǎng)橋的核心工作,創(chuàng)建一個與網(wǎng)橋同名的網(wǎng)絡(luò)設(shè)備。 // 可以通過該設(shè)備分配的IP地址來管理該網(wǎng)橋。 同時該設(shè)備 // 是虛擬的設(shè)備,它的接收包和發(fā)送包處理函數(shù)與一般的真實網(wǎng)卡 // 設(shè)備不同。 dev = new_bridge_dev(name); if (!dev) return -ENOMEM; rtnl_lock(); if (strchr(dev->name, '%')) { ret = dev_alloc_name(dev, dev->name); if (ret < 0) { free_netdev(dev); goto out; } } // 向kernel注冊該網(wǎng)橋設(shè)備,這樣在用戶空間就以使用 // ifconfig來為之分配IP,或通ioctl來對該網(wǎng)橋添加新的接口。 ret = register_netdevice(dev); if (ret) goto out; ret = br_sysfs_addbr(dev); if (ret) unregister_netdevice(dev); out: rtnl_unlock(); return ret; } int br_add_bridge(const char *name) { struct net_device *dev; int ret; // 創(chuàng)建網(wǎng)橋的核心工作,創(chuàng)建一個與網(wǎng)橋同名的網(wǎng)絡(luò)設(shè)備。 // 可以通過該設(shè)備分配的IP地址來管理該網(wǎng)橋。 同時該設(shè)備 // 是虛擬的設(shè)備,它的接收包和發(fā)送包處理函數(shù)與一般的真實網(wǎng)卡 // 設(shè)備不同。 dev = new_bridge_dev(name); if (!dev) return -ENOMEM; rtnl_lock(); if (strchr(dev->name, '%')) { ret = dev_alloc_name(dev, dev->name); if (ret < 0) { free_netdev(dev); goto out; } } // 向kernel注冊該網(wǎng)橋設(shè)備,這樣在用戶空間就以使用 // ifconfig來為之分配IP,或通ioctl來對該網(wǎng)橋添加新的接口。 ret = register_netdevice(dev); if (ret) goto out; ret = br_sysfs_addbr(dev); if (ret) unregister_netdevice(dev); out: rtnl_unlock(); return ret; }
現(xiàn)在創(chuàng)建網(wǎng)橋設(shè)備的任務(wù)落到new_bridge_dev的身上。New_bridge_dev函數(shù)的功能與一般的網(wǎng)卡驅(qū)動初化為代碼非常類似的。因為這里段代就創(chuàng)建一個網(wǎng)橋設(shè)備,從這個層面來說,這段代碼也算是驅(qū)動代碼,結(jié)構(gòu)和真實驅(qū)動非常類似。
[linux-2.6.24.4/net/bridge/br_if.c]
static struct net_device *new_bridge_dev(const char *name) { struct net_bridge *br; struct net_device *dev; // 分配net_device結(jié)構(gòu),它的priv數(shù)據(jù)為net_bridge結(jié)構(gòu)體。 // br_dev_setup函數(shù)初化了net_device結(jié)構(gòu)的很多函數(shù)指針。 dev = alloc_netdev(sizeof(struct net_bridge), name, br_dev_setup); if (!dev) return NULL; br = netdev_priv(dev); br->dev = dev; spin_lock_init(&br->lock); INIT_LIST_HEAD(&br->port_list); spin_lock_init(&br->hash_lock); br->bridge_id.prio[0] = 0x80; br->bridge_id.prio[1] = 0x00; …. return dev; } static struct net_device *new_bridge_dev(const char *name) { struct net_bridge *br; struct net_device *dev; // 分配net_device結(jié)構(gòu),它的priv數(shù)據(jù)為net_bridge結(jié)構(gòu)體。 // br_dev_setup函數(shù)初化了net_device結(jié)構(gòu)的很多函數(shù)指針。 dev = alloc_netdev(sizeof(struct net_bridge), name, br_dev_setup); if (!dev) return NULL; br = netdev_priv(dev); br->dev = dev; spin_lock_init(&br->lock); INIT_LIST_HEAD(&br->port_list); spin_lock_init(&br->hash_lock); br->bridge_id.prio[0] = 0x80; br->bridge_id.prio[1] = 0x00; …. return dev; }
[linux-2.6.24.4/net/bridge/br_device.c]
void br_dev_setup(struct net_device *dev) { // 為該網(wǎng)橋設(shè)備隨機(jī)分配MAC地址 random_ether_addr(dev->dev_addr); // 初始化dev的部分函數(shù)指針,因為目前網(wǎng)橋設(shè)備主適用于以及網(wǎng) // 以太網(wǎng)的部分功能對它也適用。 ether_setup(dev); // 設(shè)置設(shè)備的ioctl函數(shù)為br_dev_ioctl。下面可以看到通過該ioctl函數(shù) // 來為網(wǎng)橋添加網(wǎng)絡(luò)接口。 dev->do_ioctl = br_dev_ioctl; // 網(wǎng)橋與一般網(wǎng)卡不同,網(wǎng)橋統(tǒng)一統(tǒng)計它的數(shù)據(jù)包和字節(jié)數(shù)等信息。 dev->get_stats = br_dev_get_stats; // 網(wǎng)橋接口的數(shù)據(jù)包發(fā)送函數(shù),真實設(shè)備要向外發(fā)送數(shù)據(jù)時,是通過 // 網(wǎng)卡向外發(fā)送數(shù)據(jù)。而該網(wǎng)橋設(shè)備要向外發(fā)送數(shù)據(jù)時,它的處理邏輯與 // 網(wǎng)橋其它接口的基本一致。 dev->hard_start_xmit = br_dev_xmit; dev->open = br_dev_open; dev->set_multicast_list = br_dev_set_multicast_list; dev->change_mtu = br_change_mtu; dev->destructor = free_netdev; SET_ETHTOOL_OPS(dev, &br_ethtool_ops); dev->stop = br_dev_stop; dev->tx_queue_len = 0; dev->set_mac_address = br_set_mac_address; dev->priv_flags = IFF_EBRIDGE; dev->features = NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_HIGHDMA | NETIF_F_GSO_MASK | NETIF_F_NO_CSUM | NETIF_F_LLTX; } void br_dev_setup(struct net_device *dev) { // 為該網(wǎng)橋設(shè)備隨機(jī)分配MAC地址 random_ether_addr(dev->dev_addr); // 初始化dev的部分函數(shù)指針,因為目前網(wǎng)橋設(shè)備主適用于以及網(wǎng) // 以太網(wǎng)的部分功能對它也適用。 ether_setup(dev); // 設(shè)置設(shè)備的ioctl函數(shù)為br_dev_ioctl。下面可以看到通過該ioctl函數(shù) // 來為網(wǎng)橋添加網(wǎng)絡(luò)接口。 dev->do_ioctl = br_dev_ioctl; // 網(wǎng)橋與一般網(wǎng)卡不同,網(wǎng)橋統(tǒng)一統(tǒng)計它的數(shù)據(jù)包和字節(jié)數(shù)等信息。 dev->get_stats = br_dev_get_stats; // 網(wǎng)橋接口的數(shù)據(jù)包發(fā)送函數(shù),真實設(shè)備要向外發(fā)送數(shù)據(jù)時,是通過 // 網(wǎng)卡向外發(fā)送數(shù)據(jù)。而該網(wǎng)橋設(shè)備要向外發(fā)送數(shù)據(jù)時,它的處理邏輯與 // 網(wǎng)橋其它接口的基本一致。 dev->hard_start_xmit = br_dev_xmit; dev->open = br_dev_open; dev->set_multicast_list = br_dev_set_multicast_list; dev->change_mtu = br_change_mtu; dev->destructor = free_netdev; SET_ETHTOOL_OPS(dev, &br_ethtool_ops); dev->stop = br_dev_stop; dev->tx_queue_len = 0; dev->set_mac_address = br_set_mac_address; dev->priv_flags = IFF_EBRIDGE; dev->features = NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_HIGHDMA | NETIF_F_GSO_MASK | NETIF_F_NO_CSUM | NETIF_F_LLTX; }
3.2 通過ioctl系統(tǒng)調(diào)用為網(wǎng)橋添加端口
僅僅創(chuàng)建網(wǎng)橋,還是不夠的。實際應(yīng)用中的網(wǎng)橋需要添加實際的端口(即物理接口),如例子中的eth1, eth2等。應(yīng)用程序在使用ioctl來為網(wǎng)橋增加物理接口,br_dev_ioctl的代碼和分析如下:
[linux-2.6.24.4/net/bridge/br_ioctl.c]
// dev 為網(wǎng)橋接口,ifreq 為添加/刪除的物理接口的參數(shù) int br_dev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) { struct net_bridge *br = netdev_priv(dev); switch(cmd) { case SIOCDEVPRIVATE: return old_dev_ioctl(dev, rq, cmd); case SIOCBRADDIF: case SIOCBRDELIF: return add_del_if(br, rq->ifr_ifindex, cmd == SIOCBRADDIF); } pr_debug("Bridge does not support ioctl 0x%x\n", cmd); return -EOPNOTSUPP; } // dev 為網(wǎng)橋接口,ifreq 為添加/刪除的物理接口的參數(shù) int br_dev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) { struct net_bridge *br = netdev_priv(dev); switch(cmd) { case SIOCDEVPRIVATE: return old_dev_ioctl(dev, rq, cmd); case SIOCBRADDIF: case SIOCBRDELIF: return add_del_if(br, rq->ifr_ifindex, cmd == SIOCBRADDIF); } pr_debug("Bridge does not support ioctl 0x%x\n", cmd); return -EOPNOTSUPP; }
這段代碼一目了然,通過add_del_if函數(shù)來控制網(wǎng)橋的物理接口,該函數(shù)的代碼和分析如下:
[linux-2.6.24.4/net/bridge/br_ioctl.c]
// br 網(wǎng)橋,ifindex 添加/刪除物理接口的index static int add_del_if(struct net_bridge *br, int ifindex, int isadd) { struct net_device *dev; int ret; if (!capable(CAP_NET_ADMIN)) return -EPERM; dev = dev_get_by_index(&init_net, ifindex); if (dev == NULL) return -EINVAL; if (isadd) ret = br_add_if(br, dev); else ret = br_del_if(br, dev); dev_put(dev); return ret; } // br 網(wǎng)橋,ifindex 添加/刪除物理接口的index static int add_del_if(struct net_bridge *br, int ifindex, int isadd) { struct net_device *dev; int ret; if (!capable(CAP_NET_ADMIN)) return -EPERM; dev = dev_get_by_index(&init_net, ifindex); if (dev == NULL) return -EINVAL; if (isadd) ret = br_add_if(br, dev); else ret = br_del_if(br, dev); dev_put(dev); return ret; }
具體的代碼在br_add_if和br_del_if中,出于討論的方便,我們只分析br_add_if函數(shù)。
[linux-2.6.24.4/net/bridge/br_if.c]
int br_add_if(struct net_bridge *br, struct net_device *dev) { struct net_bridge_port *p; int err = 0; // Kernel僅支持以太網(wǎng)網(wǎng)橋 if (dev->flags & IFF_LOOPBACK || dev->type != ARPHRD_ETHER) return -EINVAL; // 把網(wǎng)橋接口當(dāng)作物理接口加入到另一個網(wǎng)橋中,是不行的。 // 邏輯和代碼上都會出現(xiàn) loop if (dev->hard_start_xmit == br_dev_xmit) return -ELOOP; // 該物理接口加綁定到另一個網(wǎng)橋了。 if (dev->br_port != NULL) return -EBUSY; // 為該接口創(chuàng)建一個網(wǎng)橋端口數(shù)據(jù),并初始化好該端口的相關(guān) // 數(shù)據(jù),詳情可參閱該函數(shù)代碼。 p = new_nbp(br, dev); if (IS_ERR(p)) return PTR_ERR(p); err = kobject_add(&p->kobj); if (err) goto err0; // 將該接口的物理地址寫入到 MAC-端口映射表中。 // 該MAC是屬于網(wǎng)橋內(nèi)部端口的固定MAC地址, // 它在fdb中的記錄是固定的,不會失效(agged) err = br_fdb_insert(br, p, dev->dev_addr); if (err) goto err1; err = br_sysfs_addif(p); if (err) goto err2; rcu_assign_pointer(dev->br_port, p); // 打開該接口的混雜模式,網(wǎng)橋中的各個端口必須處于 // 混雜模式,網(wǎng)橋才能正確工作。 dev_set_promiscuity(dev, 1); // 加到端口列表 list_add_rcu(&p->list, &br->port_list); spin_lock_bh(&br->lock); br_stp_recalculate_bridge_id(br); br_features_recompute(br); if ((dev->flags & IFF_UP) && netif_carrier_ok(dev) && (br->dev->flags & IFF_UP)) br_stp_enable_port(p); spin_unlock_bh(&br->lock); br_ifinfo_notify(RTM_NEWLINK, p); dev_set_mtu(br->dev, br_min_mtu(br)); kobject_uevent(&p->kobj, KOBJ_ADD); return 0; err2: br_fdb_delete_by_port(br, p, 1); err1: kobject_del(&p->kobj); err0: kobject_put(&p->kobj); return err; } int br_add_if(struct net_bridge *br, struct net_device *dev) { struct net_bridge_port *p; int err = 0; // Kernel僅支持以太網(wǎng)網(wǎng)橋 if (dev->flags & IFF_LOOPBACK || dev->type != ARPHRD_ETHER) return -EINVAL; // 把網(wǎng)橋接口當(dāng)作物理接口加入到另一個網(wǎng)橋中,是不行的。 // 邏輯和代碼上都會出現(xiàn) loop if (dev->hard_start_xmit == br_dev_xmit) return -ELOOP; // 該物理接口加綁定到另一個網(wǎng)橋了。 if (dev->br_port != NULL) return -EBUSY; // 為該接口創(chuàng)建一個網(wǎng)橋端口數(shù)據(jù),并初始化好該端口的相關(guān) // 數(shù)據(jù),詳情可參閱該函數(shù)代碼。 p = new_nbp(br, dev); if (IS_ERR(p)) return PTR_ERR(p); err = kobject_add(&p->kobj); if (err) goto err0; // 將該接口的物理地址寫入到 MAC-端口映射表中。 // 該MAC是屬于網(wǎng)橋內(nèi)部端口的固定MAC地址, // 它在fdb中的記錄是固定的,不會失效(agged) err = br_fdb_insert(br, p, dev->dev_addr); if (err) goto err1; err = br_sysfs_addif(p); if (err) goto err2; rcu_assign_pointer(dev->br_port, p); // 打開該接口的混雜模式,網(wǎng)橋中的各個端口必須處于 // 混雜模式,網(wǎng)橋才能正確工作。 dev_set_promiscuity(dev, 1); // 加到端口列表 list_add_rcu(&p->list, &br->port_list); spin_lock_bh(&br->lock); br_stp_recalculate_bridge_id(br); br_features_recompute(br); if ((dev->flags & IFF_UP) && netif_carrier_ok(dev) && (br->dev->flags & IFF_UP)) br_stp_enable_port(p); spin_unlock_bh(&br->lock); br_ifinfo_notify(RTM_NEWLINK, p); dev_set_mtu(br->dev, br_min_mtu(br)); kobject_uevent(&p->kobj, KOBJ_ADD); return 0; err2: br_fdb_delete_by_port(br, p, 1); err1: kobject_del(&p->kobj); err0: kobject_put(&p->kobj); return err; }
第四部分: 總結(jié) 網(wǎng)橋是2層的網(wǎng)格連接設(shè)備,它工作在協(xié)議棧的第二層。本文以簡單的例子作為基礎(chǔ),分析網(wǎng)橋處理報文,更新MAC-端口映射表,和如何控制網(wǎng)橋和端口的功能。文中帖上了大量的關(guān)鍵代碼,并以代碼加上注釋這種貼近程序員的方式來分析代碼。對于缺少kernel網(wǎng)絡(luò)編程經(jīng)驗的朋友,在某些代碼處,寫了在背景知識的分析和解釋。
|