?? bridge_current.c
字號:
/* 解析: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[i].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[i] = clusters[i]; /*搬家了*/ /* * */ for (i = 0 ; i < if_index && i < BDG_MAX_PORTS; i++)/*if_index是系統內的所有網卡數*/ if (ifp2sc[i].cluster != NULL) ifp2sc[i].cluster = c + (ifp2sc[i].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 voidbridge_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[i].ht, M_IFADDR);/*把HASH表釋放掉*/ free(clusters[i].my_macs, M_IFADDR);/*把在組中記錄本機網卡硬件地址的空間釋放掉*/ } if (clusters != NULL) free(clusters, M_IFADDR);/*釋放組占用的空間*/ clusters = NULL;/*置組的頭的指針為空*/ n_clusters =0;/*卡分組的數量也重新置0*/ splx(s);}/* * 把所有卡都置為混雜模式. */static voidbridge_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 voidreconfigure_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 voidparse_bdg_cfg(){
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -