概述
目录
1 网桥添加 br_add_bridge()
1.1 申请并初始化 net_device、net_bridge
1.1.1 初始化网桥 br_dev_setup()
1.1.2 网桥操作函数集合
2 网桥删除 br_del_bridge()
3 网桥端口添加 br_add_if()
3.1 网桥端口创建 new_nbp()
4 网桥端口删除 br_del_if()
4.1 删除网桥端口 del_nbp()
4.1.1 网桥端口销毁 destroy_nbp_rcu()、destroy_nbp()
4.1.2 网桥端口计数释放判断 kobject_put()
4.1.3 网桥端口释放动作 kobject_release()
4.1.4 网桥端口对象释放接口 release_nbp()
1 网桥添加 br_add_bridge()
终端命令如下:
[root@Node_A ~]# brctl addbr bridge_200 //新建一个网桥,名为“bridge_200”
当用户态执行如上命令,内核将调用接口创建并初始化一个网桥。
- 调用 alloc_netdev 创建并初始化一个网桥设备与网桥
- 调用 register_netdev 注册网桥设备
int br_add_bridge(struct net *net, const char *name)
{
struct net_device *dev;
int res;
dev = alloc_netdev(sizeof(struct net_bridge), name,
br_dev_setup);
if (!dev)
return -ENOMEM;
dev_net_set(dev, net);
dev->rtnl_link_ops = &br_link_ops;
res = register_netdev(dev);
if (res)
free_netdev(dev);
return res;
}
#define alloc_netdev(sizeof_priv, name, setup)
alloc_netdev_mqs(sizeof_priv, name, setup, 1, 1)
int register_netdev(struct net_device *dev)
{
int err;
rtnl_lock();
err = register_netdevice(dev);
rtnl_unlock();
return err;
}
1.1 申请并初始化 net_device、net_bridge
/**
* alloc_netdev_mqs - allocate network device
* @sizeof_priv: size of private data to allocate space for
* @name: device name format string
* @setup: callback to initialize device
* @txqs: the number of TX subqueues to allocate
* @rxqs: the number of RX subqueues to allocate
*
* Allocates a struct net_device with private data area for driver use
* and performs basic initialization. Also allocates subquue structs
* for each queue on the device.
*/
struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
void (*setup)(struct net_device *),
unsigned int txqs, unsigned int rxqs)
{
struct net_device *dev;
size_t alloc_size;
struct net_device *p;
BUG_ON(strlen(name) >= sizeof(dev->name));
if (txqs < 1) {
pr_err("alloc_netdev: Unable to allocate device with zero queuesn");
return NULL;
}
#ifdef CONFIG_RPS
if (rxqs < 1) {
pr_err("alloc_netdev: Unable to allocate device with zero RX queuesn");
return NULL;
}
#endif
alloc_size = sizeof(struct net_device);
if (sizeof_priv) {
/* ensure 32-byte alignment of private area */
alloc_size = ALIGN(alloc_size, NETDEV_ALIGN);
alloc_size += sizeof_priv;
}
/* ensure 32-byte alignment of whole construct */
alloc_size += NETDEV_ALIGN - 1;
p = kzalloc(alloc_size, GFP_KERNEL);
if (!p)
return NULL;
dev = PTR_ALIGN(p, NETDEV_ALIGN);
dev->padded = (char *)dev - (char *)p;
dev->pcpu_refcnt = alloc_percpu(int);
if (!dev->pcpu_refcnt)
goto free_p;
if (dev_addr_init(dev))
goto free_pcpu;
dev_mc_init(dev);
dev_uc_init(dev);
dev_net_set(dev, &init_net);
dev->gso_max_size = GSO_MAX_SIZE;
dev->gso_max_segs = GSO_MAX_SEGS;
INIT_LIST_HEAD(&dev->napi_list);
INIT_LIST_HEAD(&dev->unreg_list);
INIT_LIST_HEAD(&dev->link_watch_list);
INIT_LIST_HEAD(&dev->upper_dev_list);
dev->priv_flags = IFF_XMIT_DST_RELEASE;
setup(dev);//br_dev_setup
dev->num_tx_queues = txqs;
dev->real_num_tx_queues = txqs;
if (netif_alloc_netdev_queues(dev))
goto free_all;
#ifdef CONFIG_RPS
dev->num_rx_queues = rxqs;
dev->real_num_rx_queues = rxqs;
if (netif_alloc_rx_queues(dev))
goto free_all;
#endif
strcpy(dev->name, name);
dev->group = INIT_NETDEV_GROUP;
if (!dev->ethtool_ops)
dev->ethtool_ops = &default_ethtool_ops;
return dev;
free_all:
free_netdev(dev);
return NULL;
free_pcpu:
free_percpu(dev->pcpu_refcnt);
kfree(dev->_tx);
#ifdef CONFIG_RPS
kfree(dev->_rx);
#endif
free_p:
kfree(p);
return NULL;
}
EXPORT_SYMBOL(alloc_netdev_mqs);
1.1.1 初始化网桥 br_dev_setup()
- 设置网桥操作回调函数集
- 初始化br相关的自旋锁与链表
- 设置br的优先级为0x8000
- 初始化netfilter相关的参数
void br_dev_setup(struct net_device *dev)
{
struct net_bridge *br = netdev_priv(dev);
eth_hw_addr_random(dev);
ether_setup(dev);
/*设置网桥设备的回调函数*/
dev->netdev_ops = &br_netdev_ops;
dev->destructor = br_dev_free;
SET_ETHTOOL_OPS(dev, &br_ethtool_ops);
SET_NETDEV_DEVTYPE(dev, &br_type);
dev->tx_queue_len = 0;
/*设置设备的标签为IFF_EBRIDGE*/
dev->priv_flags = IFF_EBRIDGE;
dev->features = NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_HIGHDMA |
NETIF_F_GSO_MASK | NETIF_F_HW_CSUM | NETIF_F_LLTX |
NETIF_F_NETNS_LOCAL | NETIF_F_HW_VLAN_CTAG_TX;
dev->hw_features = NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_HIGHDMA |
NETIF_F_GSO_MASK | NETIF_F_HW_CSUM |
NETIF_F_HW_VLAN_CTAG_TX;
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;
memcpy(br->group_addr, eth_reserved_addr_base, ETH_ALEN);
br->stp_enabled = BR_NO_STP;
br->group_fwd_mask = BR_GROUPFWD_DEFAULT;
br->designated_root = br->bridge_id;
br->bridge_max_age = br->max_age = 20 * HZ;
br->bridge_hello_time = br->hello_time = 2 * HZ;
br->bridge_forward_delay = br->forward_delay = 15 * HZ;
br->ageing_time = 300 * HZ;
br_netfilter_rtable_init(br);
br_stp_timer_init(br);
br_multicast_init(br);
}
1.1.2 网桥操作函数集合
static const struct ethtool_ops br_ethtool_ops = {
.get_drvinfo = br_getinfo,
.get_link = ethtool_op_get_link,
};
static const struct net_device_ops br_netdev_ops = {
.ndo_open = br_dev_open,
.ndo_stop = br_dev_stop,
.ndo_init = br_dev_init,
.ndo_start_xmit = br_dev_xmit,
.ndo_get_stats64 = br_get_stats64,
.ndo_set_mac_address = br_set_mac_address,
.ndo_set_rx_mode = br_dev_set_multicast_list,
.ndo_change_mtu = br_change_mtu,
.ndo_do_ioctl = br_dev_ioctl,//网桥端口的添加、删除
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_netpoll_setup = br_netpoll_setup,
.ndo_netpoll_cleanup = br_netpoll_cleanup,
.ndo_poll_controller = br_poll_controller,
#endif
.ndo_add_slave = br_add_slave,
.ndo_del_slave = br_del_slave,
.ndo_fix_features = br_fix_features,
.ndo_fdb_add = br_fdb_add,
.ndo_fdb_del = br_fdb_delete,
.ndo_fdb_dump = br_fdb_dump,
.ndo_bridge_getlink = br_getlink,
.ndo_bridge_setlink = br_setlink,
.ndo_bridge_dellink = br_dellink,
};
当socket接收到添加或者删除网桥端口的ioctl后,就会调用到函数dev_ioctl,dev_ioctl会调用 dev_ifsioc,而 dev_ifsioc就通过 ops->ndo_do_ioctl,从而调用到br_dev_ioctl
在该数据结构中,将 ndo_do_ioctl 成员变量赋值为br_dev_ioctl,而br_dev_ioctl主要是处理add br if、del br if 的 ioctl,我们还记得在网桥代码初始化时,有对socket的ioctl进行网桥相关的扩展,实现add br 、del br的ioctl。而此处则实现了add br if 、del br if 的socket ioctl扩展。
2 网桥删除 br_del_bridge()
终端命令:
[root@localhost xxx]# brctl addbr bridge_200
[root@localhost xxx]# ifconfig bridge_200
bridge_200 Link encap:Ethernet HWaddr EE:88:16:9D:4E:8F
BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 b) TX bytes:0 (0.0 b)
[root@localhost xxx]# ifconfig bridge_200 up
[root@localhost xxx]# ifconfig bridge_200
bridge_200 Link encap:Ethernet HWaddr EE:88:16:9D:4E:8F
inet6 addr: fe80::ec88:16ff:fe9d:4e8f/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 b) TX bytes:468 (468.0 b)
[root@localhost xxx]# brctl delbr bridge_200
bridge bridge_200 is still up; can't delete it
[root@localhost xxx]# brctl delbr eth19
can't delete bridge eth19: Operation not permitted
[root@localhost xxx]#
该函数实现删除网桥的功能,该函数的逻辑比较简单,主要实现以下两点
- 首先判断能否删除网桥(如果设备不是网桥或者网桥设备是up状态时不能删除网桥)
- 若符合删除的条件,则调用 br_dev_delete() 进行删除
int br_del_bridge(struct net *net, const char *name)
{
struct net_device *dev;
int ret = 0;
rtnl_lock();
dev = __dev_get_by_name(net, name);
if (dev == NULL)
ret = -ENXIO; /* Could not find device */
else if (!(dev->priv_flags & IFF_EBRIDGE)) {
/* Attempt to delete non bridge device! */
ret = -EPERM;
}
else if (dev->flags & IFF_UP) {
/* Not shutdown yet. */
ret = -EBUSY;
}
else
br_dev_delete(dev, NULL);
rtnl_unlock();
return ret;
}
3 网桥端口添加 br_add_if()
首先我们需要知道,哪些设备不能作为网桥端口不能添加到桥组的设备符合以下几个条件
- 回环设备不能作为网桥端口
- 非以太网设备不能作为网桥端口
- 网桥设备不能作为网桥端口
- 已经加入到桥组中的设备不能再次加入桥组
终端命令:
[root@localhost xxx]# brctl addif bridge_200 eth18
[root@localhost xxx]# ifconfig bridge_200
bridge_200 Link encap:Ethernet HWaddr 00:22:93:75:9C:88
inet6 addr: fe80::ec88:16ff:fe9d:4e8f/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 b) TX bytes:468 (468.0 b)
[root@localhost xxx]# ifconfig eth18
eth18 Link encap:Ethernet HWaddr 00:22:93:75:9C:88
inet6 addr: fe80::222:93ff:fe75:9c88/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:67 errors:0 dropped:0 overruns:0 frame:0
TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:4020 (3.9 KiB) TX bytes:600 (600.0 b)
Interrupt:17 Memory:fbca0000-fbcc0000
[root@localhost xxx]# brctl delif bridge_200 eth18
[root@localhost xxx]# ifconfig bridge_200
bridge_200 Link encap:Ethernet HWaddr 00:00:00:00:00:00
inet6 addr: fe80::ec88:16ff:fe9d:4e8f/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 b) TX bytes:468 (468.0 b)
br_add_if() 网桥添加端口主要逻辑如下:
- 首先判断该设备是否符合加入桥组的条件
- 若设备符合条件,则调用new_nbp,生成一个新的网桥端口并进行初始化
- 调用dev_set_promiscuity,将该设备设置为混杂模式
- 调用kobject_init_and_add,为该网桥端口创建kobject并与网桥的kobject关联
- 调用netdev_rx_handler_register(dev, br_handle_frame, p),为该网桥端口注册处理函数
- 将该网桥端口加入到网桥的port_list中
- 判断设备是否是up状态,若是up状态,则调用br_stp_enable_port,使能网桥端口
- 设置网桥设备的mtu
- 调用 br_fdb_insert,为该网桥端口建立转发数据库项
- 调用kobject与sysfs的接口函数,实现kobject与sysfs之间的关联
/* called with RTNL */
int br_add_if(struct net_bridge *br, struct net_device *dev)
{
struct net_bridge_port *p;
int err = 0;
bool changed_addr;
/* Don't allow bridging non-ethernet like devices */
if ((dev->flags & IFF_LOOPBACK) ||
dev->type != ARPHRD_ETHER || dev->addr_len != ETH_ALEN ||
!is_valid_ether_addr(dev->dev_addr))
return -EINVAL;
/* No bridging of bridges */
if (dev->netdev_ops->ndo_start_xmit == br_dev_xmit)
return -ELOOP;
/* Device is already being bridged */
if (br_port_exists(dev))
return -EBUSY;
/* No bridging devices that dislike that (e.g. wireless) */
if (dev->priv_flags & IFF_DONT_BRIDGE)
return -EOPNOTSUPP;
p = new_nbp(br, dev);
if (IS_ERR(p))
return PTR_ERR(p);
call_netdevice_notifiers(NETDEV_JOIN, dev);
err = dev_set_promiscuity(dev, 1);
if (err)
goto put_back;
err = kobject_init_and_add(&p->kobj, &brport_ktype, &(dev->dev.kobj),
SYSFS_BRIDGE_PORT_ATTR);
if (err)
goto err1;
err = br_sysfs_addif(p);
if (err)
goto err2;
if (br_netpoll_info(br) && ((err = br_netpoll_enable(p, GFP_KERNEL))))
goto err3;
err = netdev_master_upper_dev_link(dev, br->dev);
if (err)
goto err4;
err = netdev_rx_handler_register(dev, br_handle_frame, p);
if (err)
goto err5;
dev->priv_flags |= IFF_BRIDGE_PORT;
dev_disable_lro(dev);
list_add_rcu(&p->list, &br->port_list);
netdev_update_features(br->dev);
spin_lock_bh(&br->lock);
changed_addr = br_stp_recalculate_bridge_id(br);
if (netif_running(dev) && netif_oper_up(dev) &&
(br->dev->flags & IFF_UP))
br_stp_enable_port(p);
spin_unlock_bh(&br->lock);
br_ifinfo_notify(RTM_NEWLINK, p);
if (changed_addr)
call_netdevice_notifiers(NETDEV_CHANGEADDR, br->dev);
dev_set_mtu(br->dev, br_min_mtu(br));
if (br_fdb_insert(br, p, dev->dev_addr, 0))
netdev_err(dev, "failed insert local address bridge forwarding tablen");
kobject_uevent(&p->kobj, KOBJ_ADD);
return 0;
....
return err;
}
3.1 网桥端口创建 new_nbp()
该函数的功能如下:
- 首先获取一个未被使用的端口号,主要使用函数 find_portno
- 申请内存
- 对端口的br、dev、port_no进行初始化
/* called with RTNL but without bridge lock */
static struct net_bridge_port *new_nbp(struct net_bridge *br,
struct net_device *dev)
{
int index;
struct net_bridge_port *p;
index = find_portno(br);
if (index < 0)
return ERR_PTR(index);
p = kzalloc(sizeof(*p), GFP_KERNEL);
if (p == NULL)
return ERR_PTR(-ENOMEM);
p->br = br;
dev_hold(dev);
p->dev = dev;
p->path_cost = port_cost(dev);
p->priority = 0x8000 >> BR_PORT_BITS;
p->port_no = index;
p->flags = 0;
br_init_port(p);
p->state = BR_STATE_DISABLED;
br_stp_port_timer_init(p);
br_multicast_add_port(p);
return p;
}
4 网桥端口删除 br_del_if()
终端命令见第三章,主要逻辑如下:
- 判断端口是否在一个网桥中,若没在桥组中,则直接返回
- 调用 del_nbp(),删除一个网桥端口
/* called with RTNL */
int br_del_if(struct net_bridge *br, struct net_device *dev)
{
struct net_bridge_port *p;
bool changed_addr;
p = br_port_get_rtnl(dev);
if (!p || p->br != br)
return -EINVAL;
/* Since more than one interface can be attached to a bridge,
* there still maybe an alternate path for netconsole to use;
* therefore there is no reason for a NETDEV_RELEASE event.
*/
del_nbp(p);
spin_lock_bh(&br->lock);
changed_addr = br_stp_recalculate_bridge_id(br);
spin_unlock_bh(&br->lock);
if (changed_addr)
call_netdevice_notifiers(NETDEV_CHANGEADDR, br->dev);
netdev_update_features(br->dev);
return 0;
}
4.1 删除网桥端口 del_nbp()
该函数主要逻辑如下:
- 调用sysfs_remove_link,删除br->ifobj目录下名为name的软链接文件
- 调用dev_set_promiscuity,设置网卡的工作模式为普通模式
- 调用br_stp_disable_port,设置端口的状态为disable
- 调用br_fdb_delete_by_port,删除CAM表中与该port有关的表项
- 调用list_del_rcu,将该port端口从网桥的port_list链表中删除
- 设置dev->priv_flags为非网桥模式
- 从linux系统中删除该port对应的kobject项
- 通过call_rcu机制,调用destroy_nbp_rcu,将该网桥端口的内存释放掉
/* Delete port(interface) from bridge is done in two steps.
* via RCU. First step, marks device as down. That deletes
* all the timers and stops new packets from flowing through.
*
* Final cleanup doesn't occur until after all CPU's finished
* processing packets.
*
* Protected from multiple admin operations by RTNL mutex
*/
static void del_nbp(struct net_bridge_port *p)
{
struct net_bridge *br = p->br;
struct net_device *dev = p->dev;
sysfs_remove_link(br->ifobj, p->dev->name);
dev_set_promiscuity(dev, -1);
spin_lock_bh(&br->lock);
br_stp_disable_port(p);
spin_unlock_bh(&br->lock);
br_ifinfo_notify(RTM_DELLINK, p);
nbp_vlan_flush(p);
br_fdb_delete_by_port(br, p, 1);
list_del_rcu(&p->list);
dev->priv_flags &= ~IFF_BRIDGE_PORT;
netdev_rx_handler_unregister(dev);
netdev_upper_dev_unlink(dev, br->dev);
br_multicast_del_port(p);
kobject_uevent(&p->kobj, KOBJ_REMOVE);
kobject_del(&p->kobj);
br_netpoll_disable(p);
call_rcu(&p->rcu, destroy_nbp_rcu);
}
4.1.1 网桥端口销毁 destroy_nbp_rcu()、destroy_nbp()
该函数通过call_rcu,会调用destroy_nbp_rcu删除网桥端口,而destroy_nbp_rcu实现以下功能
- 通过调用 container_of,获取到桥端口的地址
- 调用 destory_nbp 销毁端口
destroy_nbp,其执行以下操作:
- 将该桥端口中的br、dev指针设置为NULL
- 递减 dev 的使用计数
- 递减 p->kobj 的引用计数
static void destroy_nbp_rcu(struct rcu_head *head)
{
struct net_bridge_port *p =
container_of(head, struct net_bridge_port, rcu);
destroy_nbp(p);
}
static void destroy_nbp(struct net_bridge_port *p)
{
struct net_device *dev = p->dev;
p->br = NULL;
p->dev = NULL;
dev_put(dev);
kobject_put(&p->kobj);
}
static inline void dev_put(struct net_device *dev)
{
this_cpu_dec(*dev->pcpu_refcnt);
}
4.1.2 网桥端口计数释放判断 kobject_put()
/**
* kobject_put - decrement refcount for object.
* @kobj: object.
*
* Decrement the refcount, and if 0, call kobject_cleanup().
*/
void kobject_put(struct kobject *kobj)
{
if (kobj) {
if (!kobj->state_initialized)
WARN(1, KERN_WARNING "kobject: '%s' (%p): is not "
"initialized, yet kobject_put() is being "
"called.n", kobject_name(kobj), kobj);
kref_put(&kobj->kref, kobject_release);
}
}
static inline int kref_put(struct kref *kref, void (*release)(struct kref *kref))
{
return kref_sub(kref, 1, release);
}
static inline int kref_sub(struct kref *kref, unsigned int count,
void (*release)(struct kref *kref))
{
WARN_ON(release == NULL);
if (atomic_sub_and_test((int) count, &kref->refcount)) {
release(kref);//kobject_release
return 1;
}
return 0;
}
4.1.3 网桥端口释放动作 kobject_release()
static void kobject_release(struct kref *kref)
{
kobject_cleanup(container_of(kref, struct kobject, kref));
}
static void kobject_cleanup(struct kobject *kobj)
{
struct kobj_type *t = get_ktype(kobj);
const char *name = kobj->name;
pr_debug("kobject: '%s' (%p): %sn",
kobject_name(kobj), kobj, __func__);
if (t && !t->release) //release_nbp
pr_debug("kobject: '%s' (%p): does not have a release() "
"function, it is broken and must be fixed.n",
kobject_name(kobj), kobj);
/* send "remove" if the caller did not do it but sent "add" */
if (kobj->state_add_uevent_sent && !kobj->state_remove_uevent_sent) {
pr_debug("kobject: '%s' (%p): auto cleanup 'remove' eventn",
kobject_name(kobj), kobj);
kobject_uevent(kobj, KOBJ_REMOVE);
}
/* remove from sysfs if the caller did not do it */
if (kobj->state_in_sysfs) {
pr_debug("kobject: '%s' (%p): auto cleanup kobject_deln",
kobject_name(kobj), kobj);
kobject_del(kobj);
}
if (t && t->release) {
pr_debug("kobject: '%s' (%p): calling ktype releasen",
kobject_name(kobj), kobj);
t->release(kobj);//release_nbp()
}
/* free name if we allocated it */
if (name) {
pr_debug("kobject: '%s': free namen", name);
kfree(name);
}
}
4.1.4 网桥端口对象释放接口 release_nbp()
struct kobj_type brport_ktype 结构体里包含了 kobject 相关的属性,当 kobject 的引用计数归零后,就会调用 release 函数释放 kobject 占用的内存。
release_nbp() 的作用就是根据kobj,释放掉一个网桥端口,具体操作如下:
- 通过调用container_of,获取kobj所属于的网桥端口
- 释放内存
static struct kobj_type brport_ktype = {
#ifdef CONFIG_SYSFS
.sysfs_ops =&brport_sysfs_ops,
#endif
.release = release_nbp,
};
static void release_nbp(struct kobject *kobj)
{
struct net_bridge_port *p
= container_of(kobj, struct net_bridge_port, kobj);
kfree(p);
}
最后
以上就是无心音响为你收集整理的linux 网桥代码分析之网桥及网桥端口的添加与删除Ⅲ1 网桥添加 br_add_bridge()3 网桥端口添加 br_add_if()4 网桥端口删除 br_del_if()的全部内容,希望文章能够帮你解决linux 网桥代码分析之网桥及网桥端口的添加与删除Ⅲ1 网桥添加 br_add_bridge()3 网桥端口添加 br_add_if()4 网桥端口删除 br_del_if()所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复