NO IMAGE

* 概述:
* 該程式碼在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] = ‘‘;/*當然統計資訊也要改了.*/
  }
  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 = ‘‘;
/*
* 開始在介面列表中查詢該網絡卡名
*/
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 == ‘‘)
  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 << ; /
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);
/*說實話,寫此解析花了我不少時間,自己理解是不難的,要把理解後自己的思路寫成文章說明*/
/*是比較難,尤其是該程式是很早以前分析過的,你要寫成文章,又不得不重新分析一遍.還要照顧*/
/*到一些基礎稍微差一點兒的網友,一些函式需要適當展開.文章中可能有一些錯誤的地方,希望*/
/*網友們能指正,謝謝*/