Chinaunix首页 | 论坛 | 博客
  • 博客访问: 415067
  • 博文数量: 55
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 3458
  • 用 户 组: 普通用户
  • 注册时间: 2014-05-18 20:37
个人简介

哈哈

文章分类

全部博文(55)

分类: LINUX

2014-07-22 21:22:36

Linux 内核中桥要实现的主要功能分析:

1、可以创建/删除一个桥,并且可以让用户往桥里添加/删除二层口

2、可以进行MAC地址学习

3、维护FDB表项及表项的老化

4、可以根据MAC地址进行二层转发

5、每个桥需要提供一个三层口,用于虚拟桥设备和三层口的通信。

一、数据结构一览图



二、锁的使用

1、每个桥里有一个bridge port 链表,该链表连接了所有加入该桥的二层端口。

每个port结构体使用rcu来释放。遍历该链表时使用rcu 读锁来保护。插入链表新节点时使用bridge->lock自旋锁来保护。

 

2、每个桥里有一个FDBhash表,该表里的每个FDB表项使用rcu来释放。读该hash表时使用rcu读锁来保护。插入新的表项时使用bridge->hash_lock自旋锁来保护。

 

三、桥的创建

首先我们来看看桥的创建过程。Bridge 模块个用户空间是使用ioctl进行通信的。Bridge相关的ioctl处理函数为br_ioctl_deviceless_stub调用br_add_bridge来进行bridge的创建,这里我们分析一下主要的bridge创建过程

 

创建桥过程,首先会创建一个网桥设备struct net_device 实例。该net_device priv数据区里存放桥的配置信息(struct net_bridge)

 

调用br_dev_setup()来设置网桥设备。这里我们重点介绍一下网桥设备的发送函数br_dev_xmit()。通过网桥的三层口往外发送报文时,调用该函数

 

netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)

{

    struct net_bridge *br = netdev_priv(dev);

    const unsigned char *dest = skb->data;

    struct net_bridge_fdb_entry *dst;

 

    dev->stats.tx_packets++;

    dev->stats.tx_bytes += skb->len;

 

    skb_reset_mac_header(skb);

    skb_pull(skb, ETH_HLEN);
    /*如果是组播或广播报文,则在桥里面泛洪发送该报文*/

    if (dest[0] & 1)

        br_flood_deliver(br, skb);
    /*如果是单播报文,在桥的FDB表中查找,如果找到对应表项,

 怎根据表项发送报文*/

    else if ((dst = __br_fdb_get(br, dest)) != NULL)

        br_deliver(dst->dst, skb);
    /*如果是单播,并且没有找到相应的FDB,就进行泛洪发送*/

    else

        br_flood_deliver(br, skb);

 

    return NETDEV_TX_OK;

}


四、桥端口的添加

int br_add_if(struct net_bridge *br, struct net_device *dev)

{

    struct net_bridge_port *p;

    int err = 0;

 

    /* Device is already being bridged */
    /*如果该二层端口已经加入了bridge,则不能在加入bridge了,

 一个端口只能加入一个bridge*/

    if (dev->br_port != NULL)

        return -EBUSY;
    /*创建一个新的net_bridge_port 并初始化*/

    p = new_nbp(br, dev);

    if (IS_ERR(p))

        return PTR_ERR(p);

    /*把端口设置成混杂模式*/

    err = dev_set_promiscuity(dev, 1);

    if (err)

        goto put_back;
    /*把该端口的MAC地址加入到FDB表中,并设置为local  FDB*/

    err = br_fdb_insert(br, p, dev->dev_addr);

    if (err)

        goto err1;
    /*关联devbridge_port*/

    rcu_assign_pointer(dev->br_port, p);
    /*bridge_port链到bridge port链表上*/

    list_add_rcu(&p->list, &br->port_list);

    。。。。。。

    return err;

}

五、报文的处理流程


如图,这里就不对每个处理函数就行讲解了。

 

上送网络层进行处理

static void br_pass_frame_up(struct net_bridge *br, struct sk_buff *skb)

{

    struct net_device *indev, *brdev = br->dev;

    skb->dev = brdev; //把报文的接收dev修改为bridge dev

    NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, indev, NULL,

            netif_receive_skb);

}

 

泛洪发送

static void br_flood(struct net_bridge *br, struct sk_buff *skb,

                            void (*__packet_hook)(const struct net_bridge_port *p,

                            struct sk_buff *skb))

{

    struct net_bridge_port *p;

    struct net_bridge_port *prev;

 

    prev = NULL;
    /*遍历bridge下加入的二层端口,从每个二层端口发送一份出去*/

    list_for_each_entry_rcu(p, &br->port_list, list)
    {

        if (should_deliver(p, skb)) 
        {

            if (prev != NULL) 
            {

                struct sk_buff *skb2;
                /*把报文拷贝一份后从二层端口发送出去*/

                if ((skb2 = skb_clone(skb, GFP_ATOMIC)) == NULL)
                {

                    br->dev->stats.tx_dropped++;

                    kfree_skb(skb);

                    return;

                }

 

                __packet_hook(prev, skb2);

            }

            prev = p;

        }

    }

 

    if (prev != NULL) {

        __packet_hook(prev, skb);

        return;

    }

 

    kfree_skb(skb);

}

 

六、MAC地址的学习

bridge每收到一个报文,就会根据报文的源MAC来更新FDB表项。

 

int br_handle_frame_finish(struct sk_buff *skb)

{

    const unsigned char *dest = eth_hdr(skb)->h_dest;

    struct net_bridge_port *p = rcu_dereference(skb->dev->br_port);

    struct net_bridge *br;

    struct net_bridge_fdb_entry *dst;

    struct sk_buff *skb2;

    br = p->br;

    br_fdb_update(br, p, eth_hdr(skb)->h_source);

}

 

void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,

                               const unsigned char *addr)

{

    struct hlist_head *head = &br->hash[br_mac_hash(addr)];

    struct net_bridge_fdb_entry *fdb;

 

    fdb = fdb_find(head, addr);
    /*如果FDB表项已经存在,则更新*/

    if (likely(fdb)) 
    {

        /* attempt to update an entry for a local interface */
        /*如果是本机FDB,表示接收错误*/

        if (unlikely(fdb->is_local)) 
        {

            if (net_ratelimit())

                printk(KERN_WARNING "%s: received packet with "

                               "own address as source address\n",

                               source->dev->name);

        }
        else 
        
{
            
/*更新FDB表项,同时更新FDB的老化时间戳*/

            fdb->dst = source;

            fdb->ageing_timer = jiffies;

        }

    }
    /*如果FDB表项不存在,就新建一个FDB*/
    
else 
    
{
        
spin_lock(&br->hash_lock);

        if (!fdb_find(head, addr))

        fdb_create(head, source, addr, 0);

        spin_unlock(&br->hash_lock);

    }

}

 

七、MAC地址的老化

每次FDB表项被命中更新时,会更新FDB表项的老化时间戳。Bridge起了一个定时器,来定期的清理长时间不用的FDB表项。

初始化定时器

void br_stp_timer_init(struct net_bridge *br)

{

    。。。。。。

    setup_timer(&br->gc_timer, br_fdb_cleanup, (unsigned long) br);

}

 

void br_fdb_cleanup(unsigned long _data)

{

    struct net_bridge *br = (struct net_bridge *)_data;

    unsigned long delay = hold_time(br);

    unsigned long next_timer = jiffies + br->forward_delay;

    int i;
    /*加锁进行保护*/

    spin_lock_bh(&br->hash_lock);
    /*遍历FDB hash表中的每一项,判断FDB表项是否过期,如果过期,就删除*/

    for (i = 0; i < BR_HASH_SIZE; i++) 
    {

        struct net_bridge_fdb_entry *f;

        struct hlist_node *h, *n;

 

        hlist_for_each_entry_safe(f, h, n, &br->hash[i], hlist) 
        {

            unsigned long this_timer;
            /*跳过静态的FDB表项*/

            if (f->is_static)

                continue;

            this_timer = f->ageing_timer + delay;
            /*如果FDB表项过期,就删除该FDB表项*/

            if (time_before_eq(this_timer, jiffies))

                fdb_delete(f);

            else if (time_before(this_timer, next_timer))

                next_timer = this_timer;

        }

    }

    spin_unlock_bh(&br->hash_lock);

 

    /*遍历完后重新设置老化定时器*/

    mod_timer(&br->gc_timer, round_jiffies(next_timer + HZ/4));

}





阅读(10325) | 评论(1) | 转发(6) |
给主人留下些什么吧!~~

CU博客助理2014-09-11 15:29:23

专家点评:逻辑清晰、配图优美、关键要点均已重点标注,感谢博主的悉心整理,为博友们提供了很好的内核资料。