Linux-網橋原理分析(二)

4 網橋的實現

在內核,網橋是以模塊的方式存在,註冊源碼路徑:\net\brige\br.c:

4.1 初始化
 

static int __init br_init(void)
{
    br_fdb_init(); 
//網橋數據庫初始化,分配slab緩衝區



#ifdef CONFIG_BRIDGE_NETFILTER
    if (br_netfilter_init()) 
//netfilter鉤子初始化

        return 1;
#endif
    brioctl_set(br_ioctl_deviceless_stub); 
//設置ioctl鉤子函數:br_ioctl_hook

    br_handle_frame_hook = br_handle_frame;
//設置報文處理鉤子:br_ioctl_hook


    
//網橋數據庫處理鉤子

    br_fdb_get_hook = br_fdb_get;
    br_fdb_put_hook = br_fdb_put;

    
//在netdev_chain通知鏈表上註冊

    register_netdevice_notifier(&br_device_notifier);

    return 0;
}

4.2 新建網橋

前面說到通過brctl addbr br0命令建立網橋,此處用戶控件調用的brctl命令最終對應到內核中的br_ioctl_deviceless_stub處理函數:
 

int br_ioctl_deviceless_stub(unsigned int cmd, void __user *uarg)
{
    switch (cmd) {
    case SIOCGIFBR:
    case SIOCSIFBR:
        return old_deviceless(uarg);
        
    case SIOCBRADDBR: 
//新建網橋

    case SIOCBRDELBR: 
//刪除網橋

    {
        char buf[IFNAMSIZ];

        if (!capable(CAP_NET_ADMIN))
            return -EPERM;
            
        
//copy_from_user:把用戶空間的數據拷入內核空間

        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;
}

在這裏,我們傳入的cmd爲SIOCBRADDBR.轉入br_add_bridge(buf)中進行: 

int br_add_bridge(const char *name)
{
    struct net_device *dev;
    int ret;
    
    
//爲虛擬橋新建一個net_device

    dev = new_bridge_dev(name);
    if (!dev) 
        return -ENOMEM;

    rtnl_lock();
    
//由內核確定接口名字,例如eth0 eth1等

    if (strchr(dev->name, '%')) {
        ret = dev_alloc_name(dev, dev->name);
        if (ret < 0)
            goto err1;
    }
    
//向內核註冊此網絡設備

    ret = register_netdevice(dev);
    if (ret)
        goto err2;

    
/* network device kobject is not setup until
     * after rtnl_unlock does it's hotplug magic.
     * so hold reference to avoid race.
     */

    dev_hold(dev);
    rtnl_unlock();
    
    
//在sysfs中建立相關信息

    ret = br_sysfs_addbr(dev);
    dev_put(dev);

    if (ret) 
        unregister_netdev(dev);
 out:
    return ret;

 err2:
    free_netdev(dev);
 err1:
    rtnl_unlock();
    goto out;
}

網橋是一個虛擬的設備,它的註冊跟實際的物理網絡設備註冊是一樣的。我們關心的是網橋對應的net_device結構是什麼樣的,繼續跟蹤進new_bridge_dev:

static struct net_device *new_bridge_dev(const char *name)
{
    struct net_bridge *br;
    struct net_device *dev;

    
//分配net_device

    dev = alloc_netdev(sizeof(struct net_bridge), name,
             br_dev_setup);
    
    if (!dev)
        return NULL;
    
//網橋的私區結構爲net_bridge

    br = netdev_priv(dev);
    
//私區結構中的dev字段指向設備本身

    br->dev = dev;

    spin_lock_init(&br->lock);
    
//隊列初始化。在port_list中保存了這個橋上的端口列表

    INIT_LIST_HEAD(&br->port_list);
    spin_lock_init(&br->hash_lock);

    
//下面這部份代碼跟stp協議相關,我們暫不關心

    br->bridge_id.prio[0] = 0x80;
    br->bridge_id.prio[1] = 0x00;
    memset(br->bridge_id.addr, 0, ETH_ALEN);

    br->stp_enabled = 0;
    br->designated_root = br->bridge_id;
    br->root_path_cost = 0;
    br->root_port = 0;
    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->topology_change = 0;
    br->topology_change_detected = 0;
    br->ageing_time = 300 * HZ;
    INIT_LIST_HEAD(&br->age_list);

    br_stp_timer_init(br);

    return dev;
}

在br_dev_setup中還做了一些另外在函數指針初始化: 

void br_dev_setup(struct net_device *dev)
{
    
//將橋的MAC地址設爲零

    memset(dev->dev_addr, 0, ETH_ALEN);
     
//初始化dev的部分函數指針,因爲目前網橋設備主適用於以及網,

     
//以太網的部分功能對它也適用

    ether_setup(dev);
    
    
//設置設備的ioctl函數爲br_dev_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_MODULE_OWNER(dev);
    dev->stop = br_dev_stop;
    dev->tx_queue_len = 0;
    dev->set_mac_address = NULL;
    dev->priv_flags = IFF_EBRIDGE;
}

4.3  添加刪除端口

僅僅創建網橋,還是不夠的。實際應用中的網橋需要添加實際的端口(即物理接口),如例子中的eth1, eth2等。應用程序在使用ioctl來爲網橋增加物理接口,對應內核函數br_dev_ioctl的代碼和分析如下: 


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:


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(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:

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;

    /*--爲該接口創建一個網橋端口數據,並初始化好該端口的相關數據--*/
    if (IS_ERR(= new_nbp(br, dev, br_initial_port_cost(dev))))
        return PTR_ERR(p);
        
    
/*--將該接口的物理地址寫入到 MAC-端口映射表中,
        該MAC是屬於網橋內部端口的固定MAC地址, 
        它在fdb中的記錄是固定的,不會失效(agged)--*/

     if ((err = br_fdb_insert(br, p, dev->dev_addr)))
        destroy_nbp(p);
     /*--添加相應的系統文件信息--*/
    else if ((err = br_sysfs_addif(p)))
        del_nbp(p);
    else {
        
/*--打開該接口的混雜模式,網橋中的各個端口必須處於混雜模式,
            網橋才能正確工作--*/

        dev_set_promiscuity(dev, 1);
        
        /*--加到端口列表--*/
        list_add_rcu(&p->list, &br->port_list);

        /*--STP相關設置-*/
        spin_lock_bh(&br->lock);
        br_stp_recalculate_bridge_id(br);
        br_features_recompute(br);
        if ((br->dev->flags & IFF_UP) 
         && (dev->flags & IFF_UP) && netif_carrier_ok(dev))
            br_stp_enable_port(p);
        spin_unlock_bh(&br->lock);
        
        /*--設置設備的mtu--*/
        dev_set_mtu(br->dev, br_min_mtu(br));
    }

    return err;
}


br_del_if:

 

int br_del_if(struct net_bridge *br, struct net_device *dev)
{
    struct net_bridge_port *= dev->br_port;
    
    if (!|| p->br != br) 
        return -EINVAL;

    br_sysfs_removeif(p);
    del_nbp(p);

    spin_lock_bh(&br->lock);
    br_stp_recalculate_bridge_id(br);
    br_features_recompute(br);
    spin_unlock_bh(&br->lock);

    return 0;
}

4 網橋的實現

在內核,網橋是以模塊的方式存在,註冊源碼路徑:\net\brige\br.c:

4.1 初始化
 

static int __init br_init(void)
{
    br_fdb_init(); 
//網橋數據庫初始化,分配slab緩衝區



#ifdef CONFIG_BRIDGE_NETFILTER
    if (br_netfilter_init()) 
//netfilter鉤子初始化

        return 1;
#endif
    brioctl_set(br_ioctl_deviceless_stub); 
//設置ioctl鉤子函數:br_ioctl_hook

    br_handle_frame_hook = br_handle_frame;
//設置報文處理鉤子:br_ioctl_hook


    
//網橋數據庫處理鉤子

    br_fdb_get_hook = br_fdb_get;
    br_fdb_put_hook = br_fdb_put;

    
//在netdev_chain通知鏈表上註冊

    register_netdevice_notifier(&br_device_notifier);

    return 0;
}

4.2 新建網橋

前面說到通過brctl addbr br0命令建立網橋,此處用戶控件調用的brctl命令最終對應到內核中的br_ioctl_deviceless_stub處理函數:
 

int br_ioctl_deviceless_stub(unsigned int cmd, void __user *uarg)
{
    switch (cmd) {
    case SIOCGIFBR:
    case SIOCSIFBR:
        return old_deviceless(uarg);
        
    case SIOCBRADDBR: 
//新建網橋

    case SIOCBRDELBR: 
//刪除網橋

    {
        char buf[IFNAMSIZ];

        if (!capable(CAP_NET_ADMIN))
            return -EPERM;
            
        
//copy_from_user:把用戶空間的數據拷入內核空間

        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;
}

在這裏,我們傳入的cmd爲SIOCBRADDBR.轉入br_add_bridge(buf)中進行: 

int br_add_bridge(const char *name)
{
    struct net_device *dev;
    int ret;
    
    
//爲虛擬橋新建一個net_device

    dev = new_bridge_dev(name);
    if (!dev) 
        return -ENOMEM;

    rtnl_lock();
    
//由內核確定接口名字,例如eth0 eth1等

    if (strchr(dev->name, '%')) {
        ret = dev_alloc_name(dev, dev->name);
        if (ret < 0)
            goto err1;
    }
    
//向內核註冊此網絡設備

    ret = register_netdevice(dev);
    if (ret)
        goto err2;

    
/* network device kobject is not setup until
     * after rtnl_unlock does it's hotplug magic.
     * so hold reference to avoid race.
     */

    dev_hold(dev);
    rtnl_unlock();
    
    
//在sysfs中建立相關信息

    ret = br_sysfs_addbr(dev);
    dev_put(dev);

    if (ret) 
        unregister_netdev(dev);
 out:
    return ret;

 err2:
    free_netdev(dev);
 err1:
    rtnl_unlock();
    goto out;
}

網橋是一個虛擬的設備,它的註冊跟實際的物理網絡設備註冊是一樣的。我們關心的是網橋對應的net_device結構是什麼樣的,繼續跟蹤進new_bridge_dev:

static struct net_device *new_bridge_dev(const char *name)
{
    struct net_bridge *br;
    struct net_device *dev;

    
//分配net_device

    dev = alloc_netdev(sizeof(struct net_bridge), name,
             br_dev_setup);
    
    if (!dev)
        return NULL;
    
//網橋的私區結構爲net_bridge

    br = netdev_priv(dev);
    
//私區結構中的dev字段指向設備本身

    br->dev = dev;

    spin_lock_init(&br->lock);
    
//隊列初始化。在port_list中保存了這個橋上的端口列表

    INIT_LIST_HEAD(&br->port_list);
    spin_lock_init(&br->hash_lock);

    
//下面這部份代碼跟stp協議相關,我們暫不關心

    br->bridge_id.prio[0] = 0x80;
    br->bridge_id.prio[1] = 0x00;
    memset(br->bridge_id.addr, 0, ETH_ALEN);

    br->stp_enabled = 0;
    br->designated_root = br->bridge_id;
    br->root_path_cost = 0;
    br->root_port = 0;
    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->topology_change = 0;
    br->topology_change_detected = 0;
    br->ageing_time = 300 * HZ;
    INIT_LIST_HEAD(&br->age_list);

    br_stp_timer_init(br);

    return dev;
}

在br_dev_setup中還做了一些另外在函數指針初始化: 

void br_dev_setup(struct net_device *dev)
{
    
//將橋的MAC地址設爲零

    memset(dev->dev_addr, 0, ETH_ALEN);
     
//初始化dev的部分函數指針,因爲目前網橋設備主適用於以及網,

     
//以太網的部分功能對它也適用

    ether_setup(dev);
    
    
//設置設備的ioctl函數爲br_dev_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_MODULE_OWNER(dev);
    dev->stop = br_dev_stop;
    dev->tx_queue_len = 0;
    dev->set_mac_address = NULL;
    dev->priv_flags = IFF_EBRIDGE;
}

4.3  添加刪除端口

僅僅創建網橋,還是不夠的。實際應用中的網橋需要添加實際的端口(即物理接口),如例子中的eth1, eth2等。應用程序在使用ioctl來爲網橋增加物理接口,對應內核函數br_dev_ioctl的代碼和分析如下: 


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:


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(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:

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;

    /*--爲該接口創建一個網橋端口數據,並初始化好該端口的相關數據--*/
    if (IS_ERR(= new_nbp(br, dev, br_initial_port_cost(dev))))
        return PTR_ERR(p);
        
    
/*--將該接口的物理地址寫入到 MAC-端口映射表中,
        該MAC是屬於網橋內部端口的固定MAC地址, 
        它在fdb中的記錄是固定的,不會失效(agged)--*/

     if ((err = br_fdb_insert(br, p, dev->dev_addr)))
        destroy_nbp(p);
     /*--添加相應的系統文件信息--*/
    else if ((err = br_sysfs_addif(p)))
        del_nbp(p);
    else {
        
/*--打開該接口的混雜模式,網橋中的各個端口必須處於混雜模式,
            網橋才能正確工作--*/

        dev_set_promiscuity(dev, 1);
        
        /*--加到端口列表--*/
        list_add_rcu(&p->list, &br->port_list);

        /*--STP相關設置-*/
        spin_lock_bh(&br->lock);
        br_stp_recalculate_bridge_id(br);
        br_features_recompute(br);
        if ((br->dev->flags & IFF_UP) 
         && (dev->flags & IFF_UP) && netif_carrier_ok(dev))
            br_stp_enable_port(p);
        spin_unlock_bh(&br->lock);
        
        /*--設置設備的mtu--*/
        dev_set_mtu(br->dev, br_min_mtu(br));
    }

    return err;
}


br_del_if:

 

int br_del_if(struct net_bridge *br, struct net_device *dev)
{
    struct net_bridge_port *= dev->br_port;
    
    if (!|| p->br != br) 
        return -EINVAL;

    br_sysfs_removeif(p);
    del_nbp(p);

    spin_lock_bh(&br->lock);
    br_stp_recalculate_bridge_id(br);
    br_features_recompute(br);
    spin_unlock_bh(&br->lock);

    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章