Linux-kernel网桥代码分析(二)

第三部分: ioctl管理网桥
3.1 通过ioctl系统调用创建网桥
     仍然以前的配置作为例,我们分用户空间程序brctl是如何通过ioctl系统调用在kernel空间内创建上述的数据结构。创建网桥,我们不需要预知任何网络设备信息,因此我们通过ioctl来创建网桥时不应该与任何网络设备绑定到一起。网桥模块为此ioctl函数提供了一个恰如其分的名字 br_ioctl_deviceless_stub。Brctl工具使用的ioctl系统调用最终会调用此函数,它相关代码如下:

[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);

        用户空间程序使用网桥相关的命令来调用ioctl函数时,它经kernel依据命令所属的分类分派到sock_ioctl函数。在sock_ioctl函数里面,当ioctl命令为SIOCGIFBR,SIOCSIFBR, SIOCBRADDBR 和SIOCBRDELBR,它将ioctl的请求转发到br_ioctl_deviceless_stub函数。

Br_ioctl_deviceless_stub函数代码和分析如下:

[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:   
      // 这两个网桥命令是比较老式的,我们在这里不作讨论   
      return old_deviceless(uarg);   
    // 新式的网桥ioctl命令有两个,添加新网桥和删除现有的网桥   
    // 需要用户空间提供网桥的名字。   
    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:
      // 这两个网桥命令是比较老式的,我们在这里不作讨论
      return old_deviceless(uarg);
    // 新式的网桥ioctl命令有两个,添加新网桥和删除现有的网桥
    // 需要用户空间提供网桥的名字。
    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;

      该函数调用br_add_bridge和br_del_brdge函数的实现新建和删除网桥的功能。由于这两个函数所完成的事情刚好相反,在此,我们只讨论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;   
  // 创建网桥的核心工作,创建一个与网桥同名的网络设备。   
  // 可以通过该设备分配的IP地址来管理该网桥。 同时该设备   
  // 是虚拟的设备,它的接收包和发送包处理函数与一般的真实网卡   
  // 设备不同。   
  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注册该网桥设备,这样在用户空间就以使用   
  // ifconfig来为之分配IP,或通ioctl来对该网桥添加新的接口。   
  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;
  // 创建网桥的核心工作,创建一个与网桥同名的网络设备。
  // 可以通过该设备分配的IP地址来管理该网桥。 同时该设备
  // 是虚拟的设备,它的接收包和发送包处理函数与一般的真实网卡
  // 设备不同。
  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注册该网桥设备,这样在用户空间就以使用
  // ifconfig来为之分配IP,或通ioctl来对该网桥添加新的接口。
  ret = register_netdevice(dev);
  if (ret)
    goto out;
  ret = br_sysfs_addbr(dev);
  if (ret)
    unregister_netdevice(dev);
out:
  rtnl_unlock();
  return ret;

      现在创建网桥设备的任务落到new_bridge_dev的身上。New_bridge_dev函数的功能与一般的网卡驱动初化为代码非常类似的。因为这里段代就创建一个网桥设备,从这个层面来说,这段代码也算是驱动代码,结构和真实驱动非常类似。

[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结构,它的priv数据为net_bridge结构体。   
  // br_dev_setup函数初化了net_device结构的很多函数指针。   
  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结构,它的priv数据为net_bridge结构体。
  // br_dev_setup函数初化了net_device结构的很多函数指针。
  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)   {   
  // 为该网桥设备随机分配MAC地址   
  random_ether_addr(dev->dev_addr);   
  // 初始化dev的部分函数指针,因为目前网桥设备主适用于以及网   
  // 以太网的部分功能对它也适用。   
  ether_setup(dev);   
  // 设置设备的ioctl函数为br_dev_ioctl。下面可以看到通过该ioctl函数   
  // 来为网桥添加网络接口。   
  dev->do_ioctl = br_dev_ioctl;   
  // 网桥与一般网卡不同,网桥统一统计它的数据包和字节数等信息。   
  dev->get_stats = br_dev_get_stats;   
  // 网桥接口的数据包发送函数,真实设备要向外发送数据时,是通过   
  // 网卡向外发送数据。而该网桥设备要向外发送数据时,它的处理逻辑与   
  // 网桥其它接口的基本一致。   
  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) {
  // 为该网桥设备随机分配MAC地址
  random_ether_addr(dev->dev_addr);
  // 初始化dev的部分函数指针,因为目前网桥设备主适用于以及网
  // 以太网的部分功能对它也适用。
  ether_setup(dev);
  // 设置设备的ioctl函数为br_dev_ioctl。下面可以看到通过该ioctl函数
  // 来为网桥添加网络接口。
  dev->do_ioctl = br_dev_ioctl;
  // 网桥与一般网卡不同,网桥统一统计它的数据包和字节数等信息。
  dev->get_stats = br_dev_get_stats;
  // 网桥接口的数据包发送函数,真实设备要向外发送数据时,是通过
  // 网卡向外发送数据。而该网桥设备要向外发送数据时,它的处理逻辑与
  // 网桥其它接口的基本一致。
  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系统调用为网桥添加端口

        仅仅创建网桥,还是不够的。实际应用中的网桥需要添加实际的端口(即物理接口),如例子中的eth1, eth2等。应用程序在使用ioctl来为网桥增加物理接口,br_dev_ioctl的代码和分析如下:

[linux-2.6.24.4/net/bridge/br_ioctl.c]

// dev 为网桥接口,ifreq 为添加/删除的物理接口的参数   
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 为网桥接口,ifreq 为添加/删除的物理接口的参数
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函数来控制网桥的物理接口,该函数的代码和分析如下:

[linux-2.6.24.4/net/bridge/br_ioctl.c]

// br 网桥,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 网桥,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函数。

[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仅支持以太网网桥   
  if (dev->flags & IFF_LOOPBACK || dev->type != ARPHRD_ETHER)   
    return -EINVAL;   
  // 把网桥接口当作物理接口加入到另一个网桥中,是不行的。   
  // 逻辑和代码上都会出现 loop   
  if (dev->hard_start_xmit == br_dev_xmit)   
    return -ELOOP;   
  // 该物理接口加绑定到另一个网桥了。   
  if (dev->br_port != NULL)   
    return -EBUSY;   
  // 为该接口创建一个网桥端口数据,并初始化好该端口的相关   
  // 数据,详情可参阅该函数代码。   
  p = new_nbp(br, dev);   
  if (IS_ERR(p))   
    return PTR_ERR(p);   
  err = kobject_add(&p->kobj);   
  if (err)   
    goto err0;   
  // 将该接口的物理地址写入到 MAC-端口映射表中。   
  // 该MAC是属于网桥内部端口的固定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);   
  // 打开该接口的混杂模式,网桥中的各个端口必须处于   
  // 混杂模式,网桥才能正确工作。   
  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仅支持以太网网桥
  if (dev->flags & IFF_LOOPBACK || dev->type != ARPHRD_ETHER)
    return -EINVAL;
  // 把网桥接口当作物理接口加入到另一个网桥中,是不行的。
  // 逻辑和代码上都会出现 loop
  if (dev->hard_start_xmit == br_dev_xmit)
    return -ELOOP;
  // 该物理接口加绑定到另一个网桥了。
  if (dev->br_port != NULL)
    return -EBUSY;
  // 为该接口创建一个网桥端口数据,并初始化好该端口的相关
  // 数据,详情可参阅该函数代码。
  p = new_nbp(br, dev);
  if (IS_ERR(p))
    return PTR_ERR(p);
  err = kobject_add(&p->kobj);
  if (err)
    goto err0;
  // 将该接口的物理地址写入到 MAC-端口映射表中。
  // 该MAC是属于网桥内部端口的固定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);
  // 打开该接口的混杂模式,网桥中的各个端口必须处于
  // 混杂模式,网桥才能正确工作。
  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;
}

 

第四部分: 总结
    网桥是2层的网格连接设备,它工作在协议栈的第二层。本文以简单的例子作为基础,分析网桥处理报文,更新MAC-端口映射表,和如何控制网桥和端口的功能。文中帖上了大量的关键代码,并以代码加上注释这种贴近程序员的方式来分析代码。对于缺少kernel网络编程经验的朋友,在某些代码处,写了在背景知识的分析和解释。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值