bbs.ChinaUnix.net
首页 | 新闻 | Linux | FreeBSD | AIX | Windows | 博客 | 论坛 | 存储 | 网络 | 人才 | Wiki | 资料 | 读书 | 手册 | 下载 | 空间 | 搜索
  免费注册 | 忘记密码 | 会员登录 | 搜索 | 帮助 


原创精华帖子 网桥原理及源代码详解


  首页 » 论坛 » BSD »
[打印] [收藏] [本帖文本页] [推荐此主题给朋友]

/*                                     解析:xie_minix                           */

/* 概述:
*                该代码在FB中提供桥接功能,不过他只是在以太网接口上工作,能提供多个逻辑桥
*                ,我们称为组,组是由一组有相同组ID的接口组成,组ID的范围在1到2^16-1之间.
*                打开桥的功能是通过sysctl net.link.ether.bridge=1来启动的.
*                而sysctl net.link.ether.bridge_cfg是把以太网接口进行分组的命令,如:
*                sysctl net.link.ether.bridge_cfg="vr0:1,vr1:1,fxp0:2,fxp1:2" 该命令的结果
*                为网卡vr0和vr1可以进行相互通信,fxp0和fxp1之间可以互相转发,等于是分为俩组了.
*                但目前的该项设置还不能进行多组成员和单向控制,即一块卡可以为多个组的成员和某卡
*                与另外卡的数据单向流动.在本文中,我将结合代码来讲解如何实现以上的功能.
*                在本代码中,重要的数据结构是cluster_softc,他主要是记录一个组的接口所连接的机器
*                的硬件地址,该地址数组存储采用HASH算法,据我所知,4.4版和OpenBSD版的HASH函数算法根本不同,
*                我们在下面的代码分析过程中将看到,我也将讲解两个版本的不同之处,说实话,该算法我根本看
*                不懂(OpenBSD),估计该算法应该有相关的论文描述.
* 代码的学习顺序:
*                由于在if_ethersubr.c中的ether_input函数接到一数据包后,先查看bridge功能是否打开,
*                即判断全局变量do_bridge是否为1(该变量是由上面讲的sysctl来控制的),为真的话就调用
*                本文中的函数bridge_in(详细可看我写的"ethernet网络驱动代码详解"),所以在本文中的
*                bridge_in函数是第一个被调用的.该函数的作用是在上面讲的哪个重要的数据结构中查找
*                目标地址要通过本机的哪块卡发送,当然其中还涉及到多播,广播和是否将网卡进行分组以
*                及是否发送方,目的方经过的本机网卡是否被分在同组中等,在完成后,如果成功找到了发送
*                到目的地机器和本机直接相连的网卡就返回该网卡的ifnet结构指针(该结构可连接所描述的卡
*                的所有信息,见我所写的"关于FreeBSD4.4网络源代码接口层数据结构ifnet分析说明"),然后
*                if_ethersubr.c中的ether_input函数还要查看是否是发给本机的包,如果不是则调用本文的
*                bridge_forward函数进行数据的转发,这就是本文的主要功能.和交换机的原理有点类似.
* 性能分析:
*                由于在进行桥转发的过程中,是一定要使网卡工作在混杂模式的,所以进行网桥工作的卡要选购
*                性能好的网卡,我个人觉得intel,3com等比较适合,其他的如rtl8139卡最好只用来做实验,不要
*                用于实际的工作中(如果要我说明原因,请看看他的驱动程序你就知道了,但单机上网没关系),另
*                外,PCI漕内不要其他的卡(如声卡等),我们知道,网卡驱动程序目前在BSD中工作于中断驱动模式,
*                也就是说,进来一个包就能产生一个中断,而中断的系统开销有多达大家可以查看内核代码,总之
*                是非常大,如果你是四块卡,而且担任网桥及一些过滤功能的话,肯定数据包通过量会比较大,那么
*                中断产生的频率是平常一块卡的十几甚至是几十倍(平常的卡一个是数据量不大,另一个是不在混
*                杂模式下).要想桥工作的效率提高,建议去除本机处理针对本机的高层协议处理,如IP协议等,或改
*                写驱动程序为半轮询模式(使用timeout读卡的数据是否到达),OpenBSD中的bridge是真正的把bridge做
*                为一个设备来编写的,配备了标准的设备驱动程序,不过我还没有完全分析过,大概的看了看,觉得
*                比FreeBSD中的桥功能要强很多啊!
* 如何驱动一个网桥:
*                首先在内核配置文件中加入以下一行:
*                option BRIDGE
*                注:我所使用的4.4版本是必须的,当前版本不需要这样,可以kld动态加载.
*                重新编译核心后重启,使用 sysctl net.link.ether.bridge=1启动桥功能.
*                如果想把网卡编组,使用 sysctl net.link.ether.bridge_cfg="设备:组号,设备:组号,..."即完成.
*/
/*
* 此处略去BSD版权申明
*/
#include <sys/param.h>;
#include <sys/mbuf.h>;
#include <sys/malloc.h>;
#include <sys/protosw.h>;
#include <sys/systm.h>;
#include <sys/socket.h>;
#include <sys/ctype.h>;       
#include <sys/kernel.h>;
#include <sys/sysctl.h>;

#include <net/pfil.h>;       
#include <net/if.h>;
#include <net/if_types.h>;
#include <net/if_var.h>;

#include <netinet/in.h>;
#include <netinet/in_systm.h>;
#include <netinet/in_var.h>;
#include <netinet/ip.h>;
#include <netinet/if_ether.h>;

#include <net/route.h>;
#include <netinet/ip_fw.h>;
#include <netinet/ip_dummynet.h>;
#include <net/bridge.h>;

/*--------------------*/


#define HASH_SIZE 8192        /* HASH表的大小,必须是2的权数 */
/*hash表,该表存放与本机各块卡相连机器的硬件地址*/
typedef struct hash_table {               
    struct ifnet *        name;   /*与某机器相连的本机网卡的ifnet结构指针*/
    u_char                etheraddr[6];  /*某台机器的硬件地址*/
    u_int16_t                used;                /*这是一个是否在用(某机器是否活动)的标志*/
} bdg_hash_table ;

/*
*哈稀函数,我不理解他的算法,难道这样就不会产生同义字了吗?
*/
#define HASH_FN(addr)   (        \
    ntohs( ((u_int16_t *)addr)[1] ^ ((u_int16_t *)addr)[2] ) & (HASH_SIZE -1))

/*
* 下面的结构存储了本机的各卡的硬件地址.
*/
struct bdg_addr {
    u_char        etheraddr[6] ;/*本机卡的硬件地址*/
    u_int16_t        _padding ;/*这个成员还象没看到他用过*/
};

/*
* 这就是我们上面说的组,每块卡都有一个cluster_softc结构
*/
struct cluster_softc {
    u_int16_t        cluster_id;  /*组的ID号*/
    u_int16_t        ports;/*顺序号*/
    bdg_hash_table *ht;/*和该卡所连接的机器MAC地址哈稀表首指针*/
    struct bdg_addr        *my_macs;        /* 本卡的硬件地址 */
};


extern struct protosw inetsw[];                        /* 在netinet/ip_input.c中 */
extern u_char ip_protox[];                        /* 在netinet/ip_input.c中 */

static int n_clusters;                                /* 组的数量*/
static struct cluster_softc *clusters;  /*定义一个组的全局初始指针*/

#define BDG_MUTED(ifp) (ifp2sc[ifp->;if_index].flags & IFF_MUTE) /*检查本机某卡是否桥启用*/
#define BDG_MUTE(ifp) ifp2sc[ifp->;if_index].flags |= IFF_MUTE  /*禁止本机的该卡桥功能*/
#define BDG_CLUSTER(ifp) (ifp2sc[ifp->;if_index].cluster)/*根据卡在核心的唯一序号定位他的cluster_softc结构指针*/

#define BDG_SAMECLUSTER(ifp,src) \
        (src == NULL || BDG_CLUSTER(ifp) == BDG_CLUSTER(src) ) /*俩卡是否在同一组里?*/
/*src==NULL代表数据包来自ether_output函数.*/
#ifdef __i386__
/*比较两个地址是否相同,硬件地址是6个字节,所以他先比较后面的长字(4个字节),再比较前一个字(2个字节)*/
#define BDG_MATCH(a,b) ( \         
    ((u_int16_t *)(a))[2] == ((u_int16_t *)(b))[2] && \  
    *((u_int32_t *)(a)) == *((u_int32_t *)(b)) )
/*以下是比较广播地址*/
#define IS_ETHER_BROADCAST(a) ( \
        *((u_int32_t *)(a)) == 0xffffffff && \
        ((u_int16_t *)(a))[2] == 0xffff )
#else
/* 非i386的机器不一定按长字或字对齐,所以按字节的方式比较. */
#define        BDG_MATCH(a,b)                (!bcmp(a, b, ETHER_ADDR_LEN) )
#define        IS_ETHER_BROADCAST(a)        (!bcmp(a, "\377\377\377\377\377\377", 6))
#endif


/*
*以下两句是调试用的.
*/

#define DDB(x) x
#define DEB(x)

static int bdginit(void);/*申明bridge初始化函数*/
static void parse_bdg_cfg(void);/*申明sysctl的字符参数分解函数*/

static int bdg_ipf;                /* bridge中的IPFilter */
static int bdg_ipfw;

#if 0 /* 调试用的打印信息 */
static char *bdg_dst_names[] = {
        "BDG_NULL    ",
        "BDG_BCAST   ",
        "BDG_MCAST   ",
        "BDG_LOCAL   ",
        "BDG_DROP    ",
        "BDG_UNKNOWN ",
        "BDG_IN      ",
        "BDG_OUT     ",
        "BDG_FORWARD " };
#endif
/*
* 以下系统初始化几个结构
*/

static struct bdg_stats bdg_stats ;/*该结构用于统计信息*/
static struct callout_handle bdg_timeout_h ;/*用于保存timeout函数返回值*/

/* 把一网络接口加到组中,当然如果定义的组不存在的话,就建立一个该组.
*/
static struct cluster_softc *
add_cluster(u_int16_t cluster_id, struct arpcom *ac)
{
    struct cluster_softc *c = NULL;/*这是准备用于返回的加入(没有该组就是建立的)组结构*/
    int i;

    for (i = 0; i < n_clusters ; i++) /*遍历所有组,n_clusters在加入后或建立后会++*/
        if (clusters.cluster_id == cluster_id)/*有该组号吗?*/
            goto found;/*有,跳过建立一个新的,直接到加入该组,此时i+全局变量clusters的内容是发现该组的cluster_softc指针*/

    /* 我们要在此建立一个新的组*/
    c = malloc((1+n_clusters) * sizeof (*c), M_IFADDR, M_NOWAIT | M_ZERO);/*分配这么多干吗,错了吗?没有,看后面就知道了,他进行了举家搬迁,把前面的都搬过来了*/
    if (c == NULL) {/* 分配失败 */
        printf("-- bridge: cannot add new cluster\n");/*应该加上,no memory说明*/
        return NULL;
    }
        /*分配一个HASH表给该卡,要用掉蛮多内存的,即12*8K*/
    c[n_clusters].ht = (struct hash_table *)malloc(HASH_SIZE * sizeof(struct hash_table),M_IFADDR, M_WAITOK | M_ZERO);
    if (c[n_clusters].ht == NULL) {/*没内存了,很少出现此情况*/
        printf("-- bridge: cannot allocate hash table for new cluster\n");
        free(c, M_IFADDR);/*HASH表没分配到,当然前面分配到的cluster_softc结构要释放掉*/
        return NULL;
    }
        /*分配一存放本机网卡硬件地址的表*/
    c[n_clusters].my_macs=(struct bdg_addr *)malloc(BDG_MAX_PORTS * sizeof(struct bdg_addr),M_IFADDR, M_WAITOK | M_ZERO);
    if (c[n_clusters].my_macs == NULL) { /*内存分配不成功*/
        printf("-- bridge: cannot allocate mac addr table for new cluster\n");
        free(c[n_clusters].ht, M_IFADDR);/*上面跟这个结构有关的已分配结构都要释放*/
        free(c, M_IFADDR);
        return NULL;
    }

    c[n_clusters].cluster_id = cluster_id;/*新组的ID号*/
    c[n_clusters].ports = 0;/*在新组中加入的卡*/
    /*
     * 在这个地方就开始了前面说是否错了的处理的地方,意思是把原来分配的组的指针数组拷贝到新的组中.
     */
    if (n_clusters >; 0) {
        for (i=0; i < n_clusters; i++)/*因为n_clusters在上面已经设置完了,不需要i=<*/
            c = clusters;  /*搬家了*/
        /*
         *
         */
        for (i = 0 ; i < if_index && i < BDG_MAX_PORTS; i++)/*if_index是系统内的所有网卡数*/
            if (ifp2sc.cluster != NULL)
                ifp2sc.cluster = c + (ifp2sc.cluster - clusters);
        free(clusters, M_IFADDR);/*释放掉老的cluster_softc*/
    }
    clusters = c;/*重新定位全局变量指针,新的cluster_softc指针数组的头指针为新分配的C*/
    i = n_clusters;               
    n_clusters++;
found:
    c = clusters + i;                /* 刚申请的组指针 */
    bcopy(ac->;ac_enaddr, &(c->;my_macs[c->;ports]), 6);  /*把本网卡的硬件地址存入刚申请的组中*/
    c->;ports++;/*该组的网卡数加一块*/
    return c;
}


/*
* 关闭桥转发, 并在接口卡上去掉混杂模式,HASH表和网卡的分组也去除
*/
static void
bridge_off(void)
{
    struct ifnet *ifp ;
    int i, s;

    DEB(printf("bridge_off: n_clusters %d\n", n_clusters);)
    IFNET_RLOCK();/*新加的,老版本中没有,其定义为mtx_lock(&ifnet_lock),好象是互斥锁,我没有研究过.关于ifnet_lock,是
                                        定义在if.c中,mtx结构,应该是互斥体结构,之所以加上他,应该是和SMP有关系.*/
    TAILQ_FOREACH(ifp, &ifnet, if_link) {/*if_link是ifnet链表中的下一个ifnet*/
        struct bdg_softc *b;

        if (ifp->;if_index >;= BDG_MAX_PORTS)/*一般不会出现这种情况*/
            continue;        /* */
        b = &(ifp2sc[ifp->;if_index]);

        if ( b->;flags & IFF_BDG_PROMISC ) {/*如果网卡在混杂模式就做下面的工作*/
            s = splimp();/*关网络中断*/
            ifpromisc(ifp, 0);/*去掉混杂模式,ifp是要去掉该模式的网卡的ifnet结构指针.*/
            splx(s);/*开网络中断*/
            b->;flags &= ~(IFF_BDG_PROMISC|IFF_MUTE) ;
            DEB(printf(">;>; now %s%d promisc OFF if_flags 0x%x bdg_flags 0x%x\n",
                    ifp->;if_name, ifp->;if_unit,
                    ifp->;if_flags, b->;flags);)
        }
        b->;flags &= ~(IFF_USED) ;/*去掉IFF_USED标志,既不再桥转发了.*/
        b->;cluster = NULL;/*该卡所在的组的指针也置空.*/
        bdg_stats.s[ifp->;if_index].name[0] = '\0';/*当然统计信息也要改了.*/
    }
    IFNET_RUNLOCK();/*解互斥锁,看到这应该明白了,互斥锁是在修改ifnet结构和bdg_stats结构时进行保护的.*/

    s = splimp();
    for (i=0; i < n_clusters; i++) {/*所有组*/
        free(clusters.ht, M_IFADDR);/*把HASH表释放掉*/
        free(clusters.my_macs, M_IFADDR);/*把在组中记录本机网卡硬件地址的空间释放掉*/
    }
    if (clusters != NULL)
        free(clusters, M_IFADDR);/*释放组占用的空间*/
    clusters = NULL;/*置组的头的指针为空*/
    n_clusters =0;/*卡分组的数量也重新置0*/
    splx(s);
}

/*
* 把所有卡都置为混杂模式.
*/
static void
bridge_on(void)
{
    struct ifnet *ifp ;
    int s ;

    IFNET_RLOCK();/*看前面bridge_off函数有说明*/
    TAILQ_FOREACH(ifp, &ifnet, if_link) {/*遍历整个ifnet结构*/
        struct bdg_softc *b = &ifp2sc[ifp->;if_index];

        if ( !(b->;flags & IFF_USED) )/*如果没有在使用*/
            continue ;
        if ( !( ifp->;if_flags & IFF_UP) ) {/*如果接口关闭*/
            s = splimp();
            if_up(ifp);/*打开接口,在if.c中,调用if_route函数,比较复杂,到讲route.c和radix.c的时候再讲*/
            splx(s);
        }
        if ( !(b->;flags & IFF_BDG_PROMISC) ) {/*是否在混杂模式?*/
            int ret ;
            s = splimp();
            ret = ifpromisc(ifp, 1);/*设置混杂模式,1是加上混杂模式,0是取消混杂模式*/
            splx(s);
            b->;flags |= IFF_BDG_PROMISC ;/*在该卡的bdg_softc结构中也加上混杂模式*/
            DEB(printf(">;>; now %s%d promisc ON if_flags 0x%x bdg_flags 0x%x\n",
                    ifp->;if_name, ifp->;if_unit,
                    ifp->;if_flags, b->;flags);)
        }
        if (b->;flags & IFF_MUTE) {/*去掉阻塞*/
            DEB(printf(">;>; unmuting %s%d\n", ifp->;if_name, ifp->;if_unit);)
            b->;flags &= ~IFF_MUTE;
        }
    }
    IFNET_RUNLOCK();
}

/**
*该函数在执行系统命令 sysctl net.link.ether.bridge 和sysctl net.link.ether.bdg_cfg后
*/
static void
reconfigure_bridge(void)
{
    bridge_off();/*先关闭所有卡的桥转发,该函数在上面*/
    if (do_bridge) {/*如果桥转发打开了,就执行分析bdg_cfg设置的字符串*/
        if (if_index >;= BDG_MAX_PORTS) {
            printf("-- sorry too many interfaces (%d, max is %d),"
                " disabling bridging\n", if_index, BDG_MAX_PORTS);
            do_bridge=0;
            return;
        }
        parse_bdg_cfg();/*分析字符串,该函数在下面*/
        bridge_on();/*打开所有卡的桥转发,该函数的描述在上面*/
    }
}

static char bridge_cfg[1024]; /* in BSS so initialized to all NULs */

/*
*分析字符串函数,如:...bdg_cfg=vr0:1,vr1:1,fxp0:2,fxp1:2 也就是说对卡进行分组时,要把卡的名称,设备号,及组号
*分解出来,该函数不和内核有太多牵连,纯粹是字符串分解函数,按照目前的这种分解情况,每块卡只能存在于一个组中,
*如果我们希望他能在多个组中应该怎么办?而且一卡多组的情况是非常有用的,如:
*..........................................|
*..........................................| ...Internet 入口
*................................._____________________
*.................................|.......卡1 ........|
*.................................|.卡2...........卡3.| 透明网桥A
*.................................|___________________|
*...................................|..............|
*...................................|..............|
*................................主机B...........主机C
*说明:网桥A是一个有三块卡的FreeBSD主机,其中卡1通向Internet
*           主机B是认证服务器,主机C是数据库服务器.
*           要求从Internet进入的数据包只能到主机B进行认证,认证后该机IP地址存入主机A的缓冲,才能和C通讯
*           也就是说卡1和卡2是同组,卡1同卡3在认证后将是同组,关于A记录已认证IP地址的方法,我认为最好使用
*           patricia树,但在树中只存储主机路由及认证信息.
*/
static void
parse_bdg_cfg()
{
    char *p, *beg ;
    int l, cluster;
    static char *sep = ", \t";

    for (p = bridge_cfg; *p ; p++) {
                struct ifnet *ifp;
                int found = 0;
                char c;       
                                                                        /*该函数在libc库中,index.c中.如下:*/
                                                                        /*
                                                                        index(p, ch)
                                                                        register const char *p, ch;
                                                                        {
                                                                                for (;; ++p) {
                                                                                        if (*p == ch)
                                                                                                return((char *)p);
                                                                                        if (!*p)
                                                                                                return((char *)NULL);
                                                                                }
                                                                        }
                                                                        */
       

        if (index(sep, *p))        /* 由上面的解释可知道,跳过',号' 和 'TAB键 ' */
            continue ;
        /* 卡名是由小写字母和数字组成,如:vr0,fxp0,等 */
        for ( beg = p ; islower(*p) || isdigit(*p) ; p++ )/*循环开始,是小写或数字时继续*/
            ;
        l = p - beg ;                /* 得到了名字的长度*/
        if (l == 0)                /* 长度是0当然是不行的 */
            break ;
        if ( *p != ':' )        /* 紧接的后面的字符如果不是":",那么就假定默认为组1 */
            cluster = 1 ;
        else               
            cluster = strtoul( p+1, &p, 10);/*字符转换为无符号整数*/
        c = *p; /*暂时把p指针中的东西保存到C中,因为要把0(字符串结尾)放到*p中,以后再换回*/
        *p = '\0';
        /*
         * 开始在接口列表中查找该网卡名
         */
        IFNET_RLOCK();                /* 互斥锁 */
        TAILQ_FOREACH(ifp, &ifnet, if_link) {/*遍历整个ifnet结构*/
            char buf[IFNAMSIZ];

            snprintf(buf, sizeof(buf), "%s%d", ifp->;if_name, ifp->;if_unit);/*把卡名字和子设备号合并放到buf中,如:名字=vr,子设备号=0,合并后为vr0*/
            if (!strncmp(beg, buf, max(l, strlen(buf)))) {/*比较我们参数的设备名和buf中的相等吗?*/
                struct bdg_softc *b = &ifp2sc[ifp->;if_index];
                if (ifp->;if_type != IFT_ETHER && ifp->;if_type != IFT_L2VLAN) {/*不是以太网卡*/
                    printf("%s is not an ethernet, continue\n", buf);
                    continue;
                }
                if (b->;flags & IFF_USED) {/*如果接口卡中有该标志,那他已经用于bridge了.*/
                    printf("%s already used, skipping\n", buf);
                    break;
                }
                b->;cluster = add_cluster(htons(cluster), (struct arpcom *)ifp);/*调用前面的函数,把卡加入到组中.*/
                b->;flags |= IFF_USED ;/*加上bridge开始使用标志*/
                sprintf(bdg_stats.s[ifp->;if_index].name,             /*打印信息到屏幕*/
                        "%s%d:%d", ifp->;if_name, ifp->;if_unit, cluster);

                DEB(printf("--++  found %s next c %d\n",
                    bdg_stats.s[ifp->;if_index].name, c);)
                found = 1;/*置发现标志*/
                break ;
            }
        }
        IFNET_RUNLOCK();/*解互斥锁*/
        if (!found)/*没找到接口,可能是你参数输入错误*/
            printf("interface %s Not found in bridge\n", beg);
        *p = c;/*换回来*/
        if (c == '\0')
            break; /* 到了字符串结尾 */
    }
}


/*
* 如果使用的是SYSCTL_PROC来定义一个控制节点,那么第7个参数是一个处理函数指针,以下这两个函数都是处理函数
*/
static int
sysctl_bdg(SYSCTL_HANDLER_ARGS) /*以下是在sysctl.h中关于SYSCTL_HANDLER_ARGS的说明*/
/*#define SYSCTL_HANDLER_ARGS struct sysctl_oid *oidp, void *arg1, int arg2, struct sysctl_req *req   */
{
    int error, oldval = do_bridge ;/*把do_bridge放到oldval中暂时保存*/

    error = sysctl_handle_int(oidp, oidp->;oid_arg1, oidp->;oid_arg2, req);/*该函数把数据放到全局结构变量oidp中*/
                                                                                                                                                /*由于oidp中有指向do_bridge的指针,所以*/
                                                                                                                                                /*sysctl中的=xxx的值将放到do_bridge中*/
    DEB( printf("called sysctl for bridge name %s arg2 %d val %d->;%d\n",
        oidp->;oid_name, oidp->;oid_arg2,        oldval, do_bridge); )

    if (oldval != do_bridge)/*如果和原来的值不同,就重新设置bridge*/
        reconfigure_bridge();
    return error ;
}

/*
* 和上面是一样的,这里就不多解释了.他们不同之处是一个是整数型,一个是字符串型
*/
static int
sysctl_bdg_cfg(SYSCTL_HANDLER_ARGS)
{
    int error = 0 ;
    char old_cfg[1024] ;/*不同的地方,即是字符串*/

    strcpy(old_cfg, bridge_cfg) ;/*字符串拷贝,已经检查过,没有溢出产生.如有兴趣,可查LIBC库*/

    error = sysctl_handle_string(oidp, bridge_cfg, oidp->;oid_arg2, req);
    DEB(
        printf("called sysctl for bridge name %s arg2 %d err %d val %s->;%s\n",
                oidp->;oid_name, oidp->;oid_arg2,
                error,
                old_cfg, bridge_cfg);
        )
    if (strcmp(old_cfg, bridge_cfg))
        reconfigure_bridge();
    return error ;
}

static int
sysctl_refresh(SYSCTL_HANDLER_ARGS)
{
    if (req->;newptr)
        reconfigure_bridge();/*该函数在上面*/

    return 0;
}


SYSCTL_DECL(_net_link_ether);/*申明一节点,表示下面的SYSCTL将继承该节点*/
SYSCTL_PROC(_net_link_ether, OID_AUTO, bridge_cfg, CTLTYPE_STRING|CTLFLAG_RW,
            &bridge_cfg, sizeof(bridge_cfg), &sysctl_bdg_cfg, "A",
            "Bridge configuration");/*网卡的分组,"A"代表参数是字符串,sysctl_bdg_cfg是处理的函数的名称*/

SYSCTL_PROC(_net_link_ether, OID_AUTO, bridge, CTLTYPE_INT|CTLFLAG_RW,
            &do_bridge, 0, &sysctl_bdg, "I", "Bridging");/*对桥转发开关的控制,sysctl_bdf是控制函数,"I"代表参数是整数型*/

SYSCTL_INT(_net_link_ether, OID_AUTO, bridge_ipfw, CTLFLAG_RW,
            &bdg_ipfw,0,"Pass bridged pkts through firewall");/*对桥的防火墙的开关*/

SYSCTL_INT(_net_link_ether, OID_AUTO, bridge_ipf, CTLFLAG_RW,
            &bdg_ipf, 0,"Pass bridged pkts through IPFilter");/*包过滤的开关*/

/*因为下面都是控制整数型变量,所以做一个宏*/
#define SY(parent, var, comment)                        \  
        static int var ;                                \
        SYSCTL_INT(parent, OID_AUTO, var, CTLFLAG_RW, &(var), 0, comment);
/*以下的SYSCTL大都用于防火墙控制*/
int bdg_ipfw_drops;
SYSCTL_INT(_net_link_ether, OID_AUTO, bridge_ipfw_drop,
        CTLFLAG_RW, &bdg_ipfw_drops,0,"");

int bdg_ipfw_colls;
SYSCTL_INT(_net_link_ether, OID_AUTO, bridge_ipfw_collisions,
        CTLFLAG_RW, &bdg_ipfw_colls,0,"");

SYSCTL_PROC(_net_link_ether, OID_AUTO, bridge_refresh, CTLTYPE_INT|CTLFLAG_WR,
            NULL, 0, &sysctl_refresh, "I", "iface refresh");

#if 1

SY(_net_link_ether, verbose, "Be verbose");
SY(_net_link_ether, bdg_split_pkts, "Packets split in bdg_forward");

SY(_net_link_ether, bdg_thru, "Packets through bridge");

SY(_net_link_ether, bdg_copied, "Packets copied in bdg_forward");
SY(_net_link_ether, bdg_dropped, "Packets dropped in bdg_forward");

SY(_net_link_ether, bdg_copy, "Force copy in bdg_forward");
SY(_net_link_ether, bdg_predict, "Correctly predicted header location");

SY(_net_link_ether, bdg_fw_avg, "Cycle counter avg");
SY(_net_link_ether, bdg_fw_ticks, "Cycle counter item");
SY(_net_link_ether, bdg_fw_count, "Cycle counter count");
#endif

SYSCTL_STRUCT(_net_link_ether, PF_BDG, bdgstats,
        CTLFLAG_RD, &bdg_stats , bdg_stats, "bridge statistics");

static int bdg_loops ;


static void
bdg_timeout(void *dummy)
{
    static int slowtimer; /*会初始化为0*/

    if (do_bridge) {/*桥转发打开了就执行下面的*/
                static int age_index = 0 ;
                int l = age_index + HASH_SIZE/4 ;/*l=2048,因为HASH表内放的是指针,每个指针占用4字节,所以/4表示有多少个指针*/
                int i;
        /*
         */
                if (l >; HASH_SIZE)/*这时候l=2048,怎么可能>;HASH_SIZE(9182)*/
                        l = HASH_SIZE ;

                for (i=0; i<n_clusters; i++) {/*遍历每个组*/
                        bdg_hash_table *bdg_table = clusters.ht;/*该网卡的HASH表*/
                        for (; age_index < l ; age_index++)/*遍历整个HASH表*/
                                if (bdg_table[age_index].used)  /*如果该成员被使用了*/
                                        bdg_table[age_index].used = 0 ;/*清除掉,但我不清楚为什么不同时清除成员name,如果在此时bridge_in正接收*/
                                                                                                        /*到包,会把name保存到old变量中,会不会出问题呢?(可看看下面的bridge_in)*/
                                else if (bdg_table[age_index].name) {
                                        bdg_table[age_index].name = NULL ;
                                }
                }
                if (age_index >;= HASH_SIZE)
                        age_index = 0 ;

                if (--slowtimer <= 0 ) {/*经过5次的bdg_timeout后,才为0*/
                        slowtimer = 5 ;/*由于在初始化时,slowtimer被置为0,所以在函数第一次被调用时,次处总会被执行*/

                        bridge_on() ; /* 打开桥的一些设置,看上面的该函数说明.*/
                        bdg_loops = 0 ;
                }
    }
    bdg_timeout_h = timeout(bdg_timeout, NULL, 2*hz );/*启动监视器*/
}

/*
* 查找包的目的地,返回值如下:
*        BDG_BCAST        广播包,这种包是要发送到每一个接口的
*        BDG_MCAST        多播包
*        BDG_LOCAL        该包是发送给本机的一个包,如果该机做为透明网桥放火墙,应该拦截该包,并做特殊处理
*        BDG_DROP        该包必须抛弃
*        other                其他类型的包
*
*/
static __inline
struct ifnet *
bridge_dst_lookup(struct ether_header *eh, struct cluster_softc *c)
{/*eh是以太网包的头部*/
    struct ifnet *dst ;
    int index ;
    struct bdg_addr *p ;
    bdg_hash_table *bt;                /*HASH表入口指针 */

    if (IS_ETHER_BROADCAST(eh->;ether_dhost))/*是广播地址吗?*/
        return BDG_BCAST ;/*是的就返回广播地址标志*/
    if (eh->;ether_dhost[0] & 1)/*硬件地址的最后一位是1吗?即是多播地址吗*/
        return BDG_MCAST ;/*是的就返回多播地址标志*/
    /*
     * 以下循环是查看本机的所有网卡的硬件地址是否和eh中的目的地址相同,相同就是发送到本机的.
     */
    for (index = c->;ports, p = c->;my_macs; index ; index--, p++ )/*在cluster_softc结构中遍历本机所有网卡*/
        if (BDG_MATCH(p->;etheraddr, eh->;ether_dhost) )/*和这块卡的硬件地址相同吗?*/
            return BDG_LOCAL ;/*相同就返回本地的标志*/
    /*
     * 如果以上都不是,那么在HASH表中查找一下,目的地和本机的那块卡相连.
     */
    index= HASH_FN( eh->;ether_dhost );/*HASH查找,精华部分,查到该地址在HASH表的第index个偏移*/
    bt = &(c->;ht[index]);/*定位该HASH条目的入口*/
    dst = bt->;name;/*得到与目的地机器相连的本机某网卡的ifnet结构指针*/
    if ( dst && BDG_MATCH( bt->;etheraddr, eh->;ether_dhost) )
        return dst ;/*返回该指针*/
    else
        return BDG_UNKNOWN ;/*否则没查到,我不知道什么时候将出现该情况.*/
}

/**
* bridge_in() 函数由if_ethersubr.c中的ether_input函数调用,在该函数中会判断bridge功能是否打开,如果打开
* 既调用该函数.ether_input函数会根据返回值决定是否调用我们即将讲的下一个函数bridge_forward.
* 函数入口:
*   eh                进入以太网包的以太网包头.
*   ifp        ifnet结构,即该包是从哪块卡进来的.(ifnet包含了卡的所有信息)
*
* 函数返回: 目的地要进过本机哪块网卡发送,即那块卡的ifnet结构指针.说明如下
*   BDG_BCAST        广播地址
*   BDG_MCAST  多播地址
*   BDG_LOCAL  不需要转发,该包是发给本机的.
*   BDG_DROP   该包要丢弃
*   ifp        即将发送的网卡的ifnet指针.
*
*/

static struct ifnet *
bridge_in(struct ifnet *ifp, struct ether_header *eh)
{
    int index;
    struct ifnet *dst , *old ;
    bdg_hash_table *bt;                        /* 将用来放置当前HASH表中该地址的HASH指针的位置 */
    int dropit = BDG_MUTED(ifp) ;

    /*
     * HASH_FN宏在上面的函数中已经有描述.不过在这里是查看对方的MAC地址是否以前有记录(即在HASH表中查找)
     */
    index= HASH_FN(eh->;ether_shost);/*这中HASH的查找方法是否有问题,是否会产生同义词?他的算法是MAC地址的*/
                                                                        /*[1]和[2]两字节互补后在和HASH长度-1相与,那他认为是唯一值,这是不可靠的.*/
                                                                        /*我们可以利用该情况生成同义词,进行HASH覆盖,带着次问题我又查看了OpenBSD*/
                                                                        /*的源代码,他的算法又是另外一种,请看OpenBSD的Alley算法(Bob Jenkins):*/
        /*
        #define        mix(a,b,c) \            本人因能力有限,看不懂OpenBSD的算法
        do {                                                \
                a -= b; a -= c; a ^= (c >;>; 13);                \
                b -= c; b -= a; b ^= (a << 8);                \
                c -= a; c -= b; c ^= (b >;>; 13);                \
                a -= b; a -= c; a ^= (c >;>; 12);                \
                b -= c; b -= a; b ^= (a << 16);                \
                c -= a; c -= b; c ^= (b >;>; 5);                \
                a -= b; a -= c; a ^= (c >;>; 3);                \
                b -= c; b -= a; b ^= (a << 10);                \
                c -= a; c -= b; c ^= (b >;>; 15);                \
        } while (0)

        u_int32_t
        bridge_hash(struct bridge_softc *sc, struct ether_addr *addr)   下面的更看不懂了,OpenBSD的哈稀函数
        {
                u_int32_t a = 0x9e3779b9, b = 0x9e3779b9, c = sc->;sc_hashkey;

                b += addr->;ether_addr_octet[5] << 8;
                b += addr->;ether_addr_octet[4];
                a += addr->;ether_addr_octet[3] << 24;
                a += addr->;ether_addr_octet[2] << 16;
                a += addr->;ether_addr_octet[1] << 8;
                a += addr->;ether_addr_octet[0];

                mix(a, b, c);
                return (c & BRIDGE_RTABLE_MASK);
        }
        如果你不懂得以上的算法,那么桥的技术应该说还没精通.本人就是这样,不是谦虚.计算机搞
        到后面基本上就是拼算法的先进与合理性.
        */
    bt = &(ifp2sc[ifp->;if_index].cluster->;ht[index]);/*当然假定index没有同义词,那么就
                                                                                                        /*可以找到该MAC地址在HASH表的入口了*/
    bt->;used = 1 ;/*该MAC的HASH指针开始启用.*/
    old = bt->;name ;/*暂时存放到old中,记住,大家看看timeout中对bt->;name的清除是多么的重要啊*/
    if ( old ) { /* 为真就是以前就填充过,说明该机器以前发过包通过本机. */
                if (!BDG_MATCH( eh->;ether_shost, bt->;etheraddr) ) {/*看看上次对方机器的包的源硬件地址和本次的地址相同吗?*/
                        bdg_ipfw_colls++ ;/*不同,有问题,其实这里的操作有点类似ARP中的.*/
                        bt->;name = NULL ;
                } else if (old != ifp) {/*源地址是对的,但本机接收网卡发生了更换(重新设置了网卡)或源机器移动了.环回也有可能*/
                bt->;name = ifp ; /* 指向新的正确的接收网卡的ifnet结构 */
            printf("-- loop (%d) %6D to %s%d from %s%d (%s)\n",
                        bdg_loops, eh->;ether_shost, ".",
                        ifp->;if_name, ifp->;if_unit,
                        old->;if_name, old->;if_unit,
                        BDG_MUTED(old) ? "muted":"active");/*打印信息到屏幕*/
            dropit = 1 ;/*在本次转发中是否转发,1是不转发,就是说在发现上面的那种情况后,不转发该包*/
            if ( !BDG_MUTED(old) ) {
                        if (++bdg_loops >; 10)
                                BDG_MUTE(old) ;
                        }
                }
    }

    /*
     * 把发送方的地址写到HASH表中.
     */
    if (bt->;name == NULL) {/*因为发送方是第一次发送包.*/
        DEB(printf("new addr %6D at %d for %s%d\n",
            eh->;ether_shost, ".", index, ifp->;if_name, ifp->;if_unit);)
        bcopy(eh->;ether_shost, bt->;etheraddr, 6);/*把发送方的以太网硬件地址放到HASH表中该发送方HASH索引的地方.*/
        bt->;name = ifp ;
    }
    dst = bridge_dst_lookup(eh, ifp2sc[ifp->;if_index].cluster);/*调用上面说明的函数来查找目的地要经过的本机网卡.*/
    /*
     * BDG_STAT是对bdg_port_stat结构进行操作,统计各种包的in的数量(做++操作)
     */
    BDG_STAT(ifp, BDG_IN);
    switch ((uintptr_t)dst) {
    case (uintptr_t)BDG_BCAST:
    case (uintptr_t)BDG_MCAST:
    case (uintptr_t)BDG_LOCAL:
    case (uintptr_t)BDG_UNKNOWN:
    case (uintptr_t)BDG_DROP:
        BDG_STAT(ifp, dst);
        break ;
    default :
        if (dst == ifp || dropit)
            BDG_STAT(ifp, BDG_DROP);
        else
            BDG_STAT(ifp, BDG_FORWARD);
        break ;
    }

    if ( dropit ) {/*不转发为真吗?*/
                if (dst == BDG_BCAST || dst == BDG_MCAST || dst == BDG_LOCAL)
                        dst = BDG_LOCAL ;/*如果是发送给本机的包,即上面那些条件成立,返回该标志由ether_input函数处理*/
                else
                   dst = BDG_DROP ;/*该标志返回给ether_input后,该函数会把包抛弃*/
    } else {
        if (dst == ifp)/*如果包来自该接口,又要发送到该接口,当然应该丢弃该包*/
            dst = BDG_DROP;
    }
    DEB(printf("bridge_in %6D ->;%6D ty 0x%04x dst %s%d\n",eh->;ether_shost, ".",eh->;ether_dhost, ".",ntohs(eh->;ether_type),
        (dst <= BDG_FORWARD) ? bdg_dst_names[(int)dst] :dst->;if_name,(dst <= BDG_FORWARD) ? 0 : dst->;if_unit); )

    return dst ;/*返回的值是给ether_input函数的*/
}

/* 该函数由ether_input函数(if_ethersubr.c中)调用,作用是把包转发到相应的网络接口
* 参数dst是将要被转发的接口,当然,他可以是一个接口,也有可能是一组或所有接口.
* 该函数内是作为放火墙代码的放置地的理想地方.非同组接口过滤,以太层包过滤,IP层包过滤
* 或自己编写的钩子都可以在此实现.
*/
static struct mbuf *
bdg_forward(struct mbuf *m0, struct ifnet *dst)
{
/*该宏的作用是把先前保存的以太网包头部恢复到mbuf中.*/
#define        EH_RESTORE(_m) do {                                                   \
        /*关于M_PREPEND宏我在以前的文章中讲过,该宏是对mbuf进行操作,在此处是在mbuf前申请以太网头部长度的空间*/
    M_PREPEND((_m), ETHER_HDR_LEN, M_DONTWAIT);                                      \
    if ((_m) == NULL) {                                                           \
        bdg_dropped++;                                                           \
        return NULL;                                                           \
    }                                                                           \
    if (eh != mtod((_m), struct ether_header *))                           \
        bcopy(&save_eh, mtod((_m), struct ether_header *), ETHER_HDR_LEN); \
    else                                                                   \
        bdg_predict++;                                                           \
} while (0);
    struct ether_header *eh;  /*暂时存放以太网头部*/
    struct ifnet *src;        /*该包是本机的哪块网卡接收的*/
    struct ifnet *ifp, *last;  /*转发包时要用到的一些临时存放ifnet结构的指针*/
    int shared = bdg_copy ; /* 看前面的sysctl宏 */
    int once = 0;      /* 代表只发送一次 */
    struct ifnet *real_dst = dst ;
    struct ip_fw_args args;
#ifdef PFIL_HOOKS /* PFIL_HOOKS 即包过滤钩子*/
    struct packet_filter_hook *pfh;/* 包过滤钩子结构*/
    int rv;
#endif /* PFIL_HOOKS 即包过滤钩子*/
    struct ether_header save_eh;

    DEB(quad_t ticks; ticks = rdtsc();)

    args.rule = NULL;        /*放火墙规则*/
    /* 关于这一些放火墙及DUMMYNET,我没有研究过,有兴趣的可以自己扩展研究 */
    for (;m0->;m_type == MT_TAG; m0 = m0->;m_next)
        if (m0->;_m_tag_id == PACKET_TAG_DUMMYNET) {
            args.rule = ((struct dn_pkt *)m0)->;rule;
            shared = 0;               
        }
    if (args.rule == NULL)
        bdg_thru++;

    eh = mtod(m0, struct ether_header *);/*eh指向了m0中的以太网头部*/

    src = m0->;m_pkthdr.rcvif;   /*接收该包的本机的网卡接口的ifnet结构指针*/
    if (src == NULL)                        /* 代表包是从ether_output函数输出,即从本机的上层协议输出 */
                dst = bridge_dst_lookup(eh, ifp2sc[real_dst->;if_index].cluster);

    if (dst == BDG_DROP) { /* 这种情况不会发生,因为在ether_input函数中已经对BDG_DROP进行了过滤 */
                printf("xx bdg_forward for BDG_DROP\n");
                m_freem(m0);
                bdg_dropped++;/*统计丢弃的包数量*/
                return NULL;
    }
    if (dst == BDG_LOCAL) { /* 这种情况不会发生,因为在ether_input函数中已经对BDG_DROP进行了过滤 */
                printf("xx ouch, bdg_forward for local pkt\n");
                return m0;
    }
    if (dst == BDG_BCAST || dst == BDG_MCAST) {
                /* need a copy for the local stack */
                shared = 1 ;
    }

    /* 在这是做了一个和ip_output中类似的过滤器,当放火墙已经打开,并且包不是从ether_output输出的时候(
     * 会过滤两次).当然在此处还可以限制一些非IP包,其他链路层的包.
     */
    if (src != NULL && (
#ifdef PFIL_HOOKS
        ((pfh = pfil_hook_get(PFIL_IN, &inetsw[ip_protox[IPPROTO_IP]].pr_pfh)) != NULL && bdg_ipf !=0) ||
#endif
        (IPFW_LOADED && bdg_ipfw != 0)))
        {

                int i;

                if (args.rule != NULL && fw_one_pass)
                        goto forward; /* 包已经处理过了,直接到forward转发 */
                i = min(m0->;m_pkthdr.len, max_protohdr) ;
                if ( shared || m0->;m_len < i) {
                        m0 = m_pullup(m0, i) ;
                        if (m0 == NULL) {
                                printf("-- bdg: pullup failed.\n") ;
                                bdg_dropped++;
                                return NULL ;
                        }
                        eh = mtod(m0, struct ether_header *);
                }

                bcopy(eh, &save_eh, ETHER_HDR_LEN);        /*保存以太网头部,以后用EH_RESTORE恢复 */
                m_adj(m0, ETHER_HDR_LEN);                /* 剥掉头部 */

#ifdef PFIL_HOOKS
        /*
         * NetBSD风格的过滤器
         */
                if (pfh != NULL && m0->;m_pkthdr.len >;= sizeof(struct ip) &&        ntohs(save_eh.ether_type) == ETHERTYPE_IP) {
            /*
             * 调用放火墙前,要确定是IP包.
             */
                        struct ip *ip = mtod(m0, struct ip *);/*指向IP头部*/

                        ip->;ip_len = ntohs(ip->;ip_len);
                        ip->;ip_off = ntohs(ip->;ip_off);

                        do {
                                if (pfh->;pfil_func) {
                                        rv = pfh->;pfil_func(ip, ip->;ip_hl << 2, src, 0, &m0);/*过滤*/
                                        if (m0 == NULL) {
                                                bdg_dropped++;
                                                return NULL;
                                        }
                                        if (rv != 0) {
                                                EH_RESTORE(m0);                /* 恢复以太网头部 */
                                                return m0;
                                        }
                                        ip = mtod(m0, struct ip *);
                                }
                        } while ((pfh = TAILQ_NEXT(pfh, pfil_link)) != NULL);
            /*
             * 到这时,放火墙已经通过了该包, 恢复IP指针和把IP内的一些成员转回到网络字节顺序
             */
                        ip = mtod(m0, struct ip *);
                        ip->;ip_len = htons(ip->;ip_len);
                        ip->;ip_off = htons(ip->;ip_off);
                }
#endif /* PFIL_HOOKS结束 */
                if (!IPFW_LOADED || bdg_ipfw == 0) {
                        EH_RESTORE(m0);        /* 恢复以太网头部 */
                        goto forward;        /* 不使用ipfw, 直接转发 */
                }

        /*
         * 下面的代码和if_ethersubr.c:ether_ipfw_chk()非常类似
         */

                args.m = m0;                /* 将查看的包        */
                args.oif = NULL;        /* 输入的ifnet                */
                args.divert_rule = 0;        /* 目前不支持定向的规则                */
                args.next_hop = NULL;        /* 目前也不支持转发的规则        */
                args.eh = &save_eh;        /* 头部        */
                i = ip_fw_chk_ptr(&args);
                m0 = args.m;       
                if (m0 != NULL)/*通过了*/
                        EH_RESTORE(m0);        /* 恢复以太网头部 */

                if ( (i & IP_FW_PORT_DENY_FLAG) || m0 == NULL) /* 没通过,抛弃 */
                        return m0 ;

                if (i == 0)
                        goto forward ;
                if (DUMMYNET_LOADED && (i & IP_FW_PORT_DYNT_FLAG)) {
                        struct mbuf *m ;

                        if (shared) {
                                m = m_copypacket(m0, M_DONTWAIT);/*共享为真,则做一个备份*/
                                if (m == NULL) {       
                                        bdg_dropped++;
                                        return NULL;
                                }
                        } else {
                                m = m0 ; /* 把原包放到 dummynet 中处理*/
                                m0 = NULL ;
                        }

                        args.oif = real_dst;
                        ip_dn_io_ptr(m, (i & 0xffff),DN_TO_BDG_FWD, &args);
                        return m0 ;
                }
                bdg_ipfw_drops++ ;
                return m0 ;
    }
forward:  /*转发*/
    if ( shared ) {
                int i = min(m0->;m_pkthdr.len, max_protohdr) ;/*取mbuf链表的第一个mbuf的包头部长度与最大协议长度的最小值*/

                m0 = m_pullup(m0, i) ;/*调整m0->;data的指向位置到i*/
                if (m0 == NULL) {/*不成功*/
                        bdg_dropped++ ;
                        return NULL ;
                }
    }

    if (src != NULL)
                real_dst = src ;

    last = NULL;
    IFNET_RLOCK();
    if (dst == BDG_BCAST || dst == BDG_MCAST || dst == BDG_UNKNOWN) {/*如果目的地是广播,多播和不知道的包类型,则全部都要转发*/
                ifp = TAILQ_FIRST(&ifnet) ; /* 从第一个开始吧 */
                once = 0 ;/*该变量是用来控制是否都转发,0为都转发,1为只从1个网卡转发*/
    } else {
                ifp = dst ;/*转发的网卡只要一个*/
                once = 1 ;
    }
    if ((uintptr_t)(ifp) <= (u_int)BDG_FORWARD)/*BDG_FORWARD定义为9*/
                panic("bdg_forward: bad dst");

    for (;;) {/*开始转发,转发过程有可能是对一个接口,也有可能是转发到多个接口,如广播,多播*/
                if (last) { /* 第一次进来的时候,因为last为空,所以跳过他. */
                        struct mbuf *m ;
                        if (shared == 0 && once ) { /* shared代表是否共享,once代表发送一次,此句意思为:当不是广播或多播,并只发送一次时 */
                                m = m0 ;
                                m0 = NULL ; /*  */
                        } else {
                                m = m_copypacket(m0, M_DONTWAIT);
                                if (m == NULL) {
                                        IFNET_RUNLOCK();
                                        printf("bdg_forward: sorry, m_copypacket failed!\n");
                                        bdg_dropped++ ;
                                        return m0 ;
                                }
                        }
                        if (!IF_HANDOFF(&last->;if_snd, m, last)) {/*发送包,早版本的只是用(*ifp->;if_start)(ifp);当前版考虑到SMP,使用了*/
#if 0                                                                                                        /*一些锁技术,该过程跳过了普通的ether_output函数而直接调用驱动程序*/
                        BDG_MUTE(last);
#endif
                        }
                        BDG_STAT(last, BDG_OUT);/*统计*/
                        last = NULL ;
                        if (once)/*只发送一次为真吗?*/
                                break ;
                }
                if (ifp == NULL)
                        break ;
                if ( BDG_USED(ifp) && !BDG_MUTED(ifp) && !_IF_QFULL(&ifp->;if_snd)  &&
             (ifp->;if_flags & (IFF_UP|IFF_RUNNING)) == (IFF_UP|IFF_RUNNING) &&
             ifp != src && BDG_SAMECLUSTER(ifp, real_dst) )/*除了前面的判断接口的正常状态和接口发送对列是否满*/
                        last = ifp ;                                                                        /*主要的判断是对输入和输出接口是否同组接口的判断*/
                ifp = TAILQ_NEXT(ifp, if_link) ;/*下一网卡的ifnet结构指针*/
                if (ifp == NULL)/*代表发完了last所指向的网卡就不发送了.*/
                        once = 1 ;
    }
    IFNET_RUNLOCK();
    DEB(bdg_fw_ticks += (u_long)(rdtsc() - ticks) ; bdg_fw_count++ ;
                if (bdg_fw_count != 0) bdg_fw_avg = bdg_fw_ticks/bdg_fw_count; )/*后面的括号是前一行的DEB的结束哦,不要搞错了.*/
        return m0 ;
#undef EH_RESTORE
}

/*
* 初始化工作.
*/
static int
bdginit(void)
{
    printf("BRIDGE 020214 loaded\n");/*020214是开发的时间2002-02-14吗?*/

    ifp2sc = malloc(BDG_MAX_PORTS * sizeof(struct bdg_softc),/* BDG_MAX_PORTS=128 */
                M_IFADDR, M_WAITOK | M_ZERO );  /*即在此申请bdg_softc结构所用的内存,ifp2sc是该结构的首指针*/
    if (ifp2sc == NULL)
        return ENOMEM ;

    bridge_in_ptr = bridge_in; /*存放桥路经分析函数指针*/
    bdg_forward_ptr = bdg_forward;/*存放桥转发函数指针*/
    bdgtakeifaces_ptr = reconfigure_bridge;/*存放接口设置函数指针*/

    n_clusters = 0; /*初始化桥组的数量为0*/
    clusters = NULL;/*组结构头部为空*/
    do_bridge=0;/*暂时不开bridge*/

    bzero(&bdg_stats, sizeof(bdg_stats) );/*桥状态数据统计结构清0*/
    bdgtakeifaces_ptr();/*在前4行可看到把reconfigure_bridge的指针放入了,该函数在上面*/
    bdg_timeout(0);/*打开定时器*/
    return 0 ;
}

/*
*在bridge模块被静态或动态导入时要执行的初始工作,从这开使,基本上是新加的(和4.4版比)
*/
static int
bridge_modevent(module_t mod, int type, void *unused)/*只用到了type*/
{
        int s;
        int err = 0 ;

        switch (type) {
        case MOD_LOAD:     /*模块加载*/
                if (BDG_LOADED) {
                        err = EEXIST;
                        break ;
                }
                s = splimp();/*关网络中断*/
                err = bdginit();/*执行初始化程序*/
                splx(s);/*开网络中断*/
                break;
        case MOD_UNLOAD:/*在模块卸载时被调用*/
#if !defined(KLD_MODULE)
                printf("bridge statically compiled, cannot unload\n");
                err = EINVAL ;
#else
                s = splimp();
                do_bridge = 0;
                bridge_in_ptr = NULL;/*存放桥路经分析函数指针置为空*/
                bdg_forward_ptr = NULL;/*存放桥转发函数指针置为空*/
                bdgtakeifaces_ptr = NULL;
                untimeout(bdg_timeout, NULL, bdg_timeout_h);/*卸载监视器*/
                bridge_off();
                if (clusters) /*如果你的网卡编了组*/
                    free(clusters, M_IFADDR);/*释放掉组结构占用的空间*/
                free(ifp2sc, M_IFADDR);/*释放掉bdg_softc结构占用的空间*/
                ifp2sc = NULL ;/*并且把指向首bdg_softc结构的指针置为空*/
                splx(s);
#endif
                break;
        default:
                err = EINVAL ;
                break;
        }
        return err;
}

static moduledata_t bridge_mod = {
        "bridge",
        bridge_modevent,
        0
};

DECLARE_MODULE(bridge, bridge_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);/*动态可加载模块*/
MODULE_VERSION(bridge, 1);
/*说实话,写此解析花了我不少时间,自己理解是不难的,要把理解后自己的思路写成文章说明*/
/*是比较难,尤其是该程序是很早以前分析过的,你要写成文章,又不得不重新分析一遍.还要照顾*/
/*到一些基础稍微差一点儿的网友,一些函数需要适当展开.文章中可能有一些错误的地方,希望*/
/*网友们能指正,谢谢*/



网桥原理及源代码详解

Good Paper!

BTW:如果在发送的时候能够将笑脸转换关闭就更好了
__________________________________
中文FreeBSD技术期刊:http://www.cnfug.org/
我的小站:http://matthew.3322.org/



网桥原理及源代码详解

up!
__________________________________
内网QQ  我用飞秋


  首页 » 论坛 » BSD »
Copyright © 2001-2010 ChinaUnix.net All Rights Reserved     联系我们:

感谢所有关心和支持过ChinaUnix的朋友们    转载本站内容请注明原作者名及出处

京ICP证041476号


清除 Cookies - ChinaUnix - Archiver - WAP - TOP

GMT+8, 2010-02-09 20:26, Processed in 0.033809 second(s), 7 queries, Gzip enabled.