Linux網橋原始碼框架分析初步(轉)
Linux網橋原始碼框架分析初步(轉)[@more@]今天處理網橋的STP的問題遇到了麻煩,對這個東東理論的倒是看了不少,沒有真真學習到它的源理,來看Linux的實現,手頭沒有資料,看了兩個鐘頭,只把網橋的框架結構看完,所以想先貼出來,希望有研究這塊的大哥們討論,繼續把它寫完,九賤好學習一下:
版本:Linux 2.4.18
一、呼叫
在src/net/core/dev.c的軟中斷函式static void net_rx_action(struct softirq_action *h)中:
line 1479
#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
if (skb->dev->br_port != NULL &&
br_handle_frame_hook != NULL) {
handle_bridge(skb, pt_prev);
dev_put(rx_dev);
continue;
}
#endif
如果定義了網橋或網橋模組,則由handle_bridge函式處理
skb->dev->br_port :接收該資料包的埠是網橋埠組的一員
br_handle_frame_hook :定義了網橋處理函式
二、初始化
src/net/bridge/br.c:
static int __init br_init(void)
{
printk(KERN_INFO "NET4: Ethernet Bridge 008 for NET4.0 ";
br_handle_frame_hook = br_handle_frame;
br_ioctl_hook = br_ioctl_deviceless_stub;
#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE)
br_fdb_get_hook = br_fdb_get;
br_fdb_put_hook = br_fdb_put;
#endif
register_netdevice_notifier(&br_device_notifier);
return 0;
}
初始化函式指明瞭網橋的處理函式是br_handle_frame
ioctl處理函式是:br_ioctl_deviceless_stub
三、br_handle_frame(br_input.c)
/*網橋處理函式*/
void br_handle_frame(struct sk_buff *skb)
{
struct net_bridge *br;
unsigned char *dest;
struct net_bridge_port *p;
/*獲取目的MAC地址*/
dest = skb->mac.ethernet->h_dest;
/*skb->dev->br_port用於指定接收該資料包的埠,若不是屬於網橋的埠,則為NULL*/
p = skb->dev->br_port;
if (p == NULL) /*埠不是網橋組埠中*/
goto err_nolock;
/*本埠所屬的網橋組*/
br = p->br;
/*加鎖,因為在轉發中需要讀CAM表,所以必須加讀鎖,避免在這個過程中另外的核心控制路徑(如多處理機上另外一個CPU上的系統呼叫)修改CAM表*/
read_lock(&br->lock);
if (skb->dev->br_port == NULL) /*前面判斷過的*/
goto err;
/*br->dev是網橋的虛擬網路卡,如果它未UP,或網橋DISABLED,p->state實際上是橋的當前埠的STP計算判斷後的狀態*/
if (!(br->dev.flags & IFF_UP) ||
p->state == BR_STATE_DISABLED)
goto err;
/*源MAC地址為255.X.X.X,即源MAC是多播或廣播,丟棄之*/
if (skb->mac.ethernet->h_source[0] & 1)
goto err;
/*眾所周之,網橋之所以是網橋,比HUB更智慧,是因為它有一個MAC-PORT的表,這樣轉發資料就不用廣播,而查表定埠就可以了
每次收到一個包,網橋都會學習其來源MAC,新增進這個表。Linux中這個表叫CAM表(這個名字是其它資料上看的)。
如果橋的狀態是LEARNING或FORWARDING(學習或轉發),則學習該包的源地址skb->mac.ethernet->h_source,
將其新增到CAM表中,如果已經存在於表中了,則更新定時器,br_fdb_insert完成了這一過程*/
if (p->state == BR_STATE_LEARNING ||
p->state == BR_STATE_FORWARDING)
br_fdb_insert(br, p, skb->mac.ethernet->h_source, 0);
/*STP協議的BPDU包的目的MAC採用的是多播目標MAC地址:從01-80-c2-00-00-00(Bridge_group_addr:網橋組多播地址)開始
所以這裡是如果開啟了STP,而當前資料包又是一個BPDU
(!memcmp(dest, bridge_ula, 5), unsigned char bridge_ula[6] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 },
則交由相應函式處理*/
if (br->stp_enabled &&
/*這裡只比較前5個位元組,沒有仔細研究過STP是使用了全部多播地址(從0 1 : 0 0 : 5 e : 0 0 : 0 0 : 0 0到0 1 : 0 0 : 5 e : 7 f : ff : ff。),還是隻使用了一部份,這裡看來似乎只是一部份,沒去深究了*/
!memcmp(dest, bridge_ula, 5) &&
!(dest[5] & 0xF0)) /*01-80-c2-00-00-F0 是一個什麼地址?為什麼要判斷呢?*/
goto handle_special_frame;
/*處理鉤子函式,然後轉交br_handle_frame_finish函式繼續處理*/
if (p->state == BR_STATE_FORWARDING) {
NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,
br_handle_frame_finish);
read_unlock(&br->lock);
return;
}
err:
read_unlock(&br->lock);
err_nolock:
kfree_skb(skb);
return;
handle_special_frame:
if (!dest[5]) {
br_stp_handle_bpdu(skb);
return;
}
kfree_skb(skb);
}
四、br_handle_frame_finish
static int br_handle_frame_finish(struct sk_buff *skb)
{
struct net_bridge *br;
unsigned char *dest;
struct net_bridge_fdb_entry *dst;
struct net_bridge_port *p;
int passedup;
/*前面基本相同*/
dest = skb->mac.ethernet->h_dest;
p = skb->dev->br_port;
if (p == NULL)
goto err_nolock;
br = p->br;
read_lock(&br->lock);
if (skb->dev->br_port == NULL)
goto err;
passedup = 0;
/*如果網橋的虛擬網路卡處於混雜模式,那麼每個接收到的資料包都需要克隆一份
送到AF_PACKET協議處理體(網路軟中斷函式net_rx_action中ptype_all鏈的處理)。*/
if (br->dev.flags & IFF_PROMISC) {
struct sk_buff *skb2;
skb2 = skb_clone(skb, GFP_ATOMIC);
if (skb2 != NULL) {
passedup = 1;
br_pass_frame_up(br, skb2);
}
}
/*目的MAC為廣播或多播,則需要向本機的上層協議棧傳送這個資料包,這裡有一個標誌變數passedup
用於表示是否傳送過了,如果已傳送過,那就算了*/
if (dest[0] & 1) {
br_flood_forward(br, skb, !passedup);
if (!passedup)
br_pass_frame_up(br, skb);
goto out;
}
/*Linux中的MAC-PORT表是CAM表,這裡根據目的地址來查表,以確定由哪個介面把包轉發出去
每一個表項是透過結構struct net_bridge_fdb_entry來描述的:
struct net_bridge_fdb_entry
{
struct net_bridge_fdb_entry *next_hash; //用於CAM表連線的連結串列指標
struct net_bridge_fdb_entry **pprev_hash; //為什麼是pprev不是prev呢?還沒有仔細去研究
atomic_t use_count; //此項當前的引用計數器
mac_addr addr; //MAC地址
struct net_bridge_port *dst; //此項所對應的物理埠
unsigned long ageing_timer; //處理MAC超時
unsigned is_local:1; //是否是本機的MAC地址
unsigned is_static:1; //是否是靜態MAC地址
};*/
dst = br_fdb_get(br, dest);
/*查詢CAM表後,如果能夠找到表項,並且目的MAC是到本機的虛擬網路卡的,那麼就需要把這個包提交給上層協議,
這樣,我們就可以透過這個虛擬網路卡的地址來遠端管理網橋了*/
if (dst != NULL && dst->is_local) {
if (!passedup)
br_pass_frame_up(br, skb);
else
kfree_skb(skb);
br_fdb_put(dst);
goto out;
}
/*查到表了,且不是本地虛擬網路卡的,轉發之*/
if (dst != NULL) {
br_forward(dst->dst, skb);
br_fdb_put(dst);
goto out;
}
/*如果表裡邊查不到,那麼只好學習學習HUB了……*/
br_flood_forward(br, skb, 0);
out:
read_unlock(&br->lock);
return 0;
err:
read_unlock(&br->lock);
err_nolock:
kfree_skb(skb);
return 0;
}
基本框架就是這樣了,與那些講網橋原理的書上講的基本差不多……
網橋之所以是網橋,主要靠這兩個函式:
br_fdb_insert
br_fdb_get
一個學習,一個查表;
另外,支援STP,處理BPDU,需要用到函式br_stp_handle_bpdu
哪位有這三個函式的細節分析,可否送九賤一份,免得下午那麼辛苦再去啃程式碼……
掃了一下 br_fdb_insert,結構還是很清析,如果當前項已存在於hash表項中,則更新它(__fdb_possibly_replace),如果是新項,則插入,實際是一個雙向連結串列的維護過程(__hash_link):
void br_fdb_insert(struct net_bridge *br,
struct net_bridge_port *source,
unsigned char *addr,
int is_local)
{
struct net_bridge_fdb_entry *fdb;
int hash;
hash = br_mac_hash(addr);
write_lock_bh(&br->hash_lock);
fdb = br->hash[hash];
while (fdb != NULL) {
if (!fdb->is_local &&
!memcmp(fdb->addr.addr, addr, ETH_ALEN)) {
__fdb_possibly_replace(fdb, source, is_local);
write_unlock_bh(&br->hash_lock);
return;
}
fdb = fdb->next_hash;
}
fdb = kmalloc(sizeof(*fdb), GFP_ATOMIC);
if (fdb == NULL) {
write_unlock_bh(&br->hash_lock);
return;
}
memcpy(fdb->addr.addr, addr, ETH_ALEN);
atomic_set(&fdb->use_count, 1);
fdb->dst = source;
fdb->is_local = is_local;
fdb->is_static = is_local;
fdb->ageing_timer = jiffies;
__hash_link(br, fdb, hash);
write_unlock_bh(&br->hash_lock);
}
同樣,查表也是一個遍歷連結串列,進行地址匹配的過程:
struct net_bridge_fdb_entry *br_fdb_get(struct net_bridge *br, unsigned char *addr)
{
struct net_bridge_fdb_entry *fdb;
read_lock_bh(&br->hash_lock);
fdb = br->hash[br_mac_hash(addr)];
while (fdb != NULL) {
if (!memcmp(fdb->addr.addr, addr, ETH_ALEN)) {
if (!has_expired(br, fdb)) {
atomic_inc(&fdb->use_count);
read_unlock_bh(&br->hash_lock);
return fdb;
}
read_unlock_bh(&br->hash_lock);
return NULL;
}
fdb = fdb->next_hash;
}
read_unlock_bh(&br->hash_lock);
return NULL;
}
版本:Linux 2.4.18
一、呼叫
在src/net/core/dev.c的軟中斷函式static void net_rx_action(struct softirq_action *h)中:
line 1479
#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
if (skb->dev->br_port != NULL &&
br_handle_frame_hook != NULL) {
handle_bridge(skb, pt_prev);
dev_put(rx_dev);
continue;
}
#endif
如果定義了網橋或網橋模組,則由handle_bridge函式處理
skb->dev->br_port :接收該資料包的埠是網橋埠組的一員
br_handle_frame_hook :定義了網橋處理函式
二、初始化
src/net/bridge/br.c:
static int __init br_init(void)
{
printk(KERN_INFO "NET4: Ethernet Bridge 008 for NET4.0 ";
br_handle_frame_hook = br_handle_frame;
br_ioctl_hook = br_ioctl_deviceless_stub;
#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE)
br_fdb_get_hook = br_fdb_get;
br_fdb_put_hook = br_fdb_put;
#endif
register_netdevice_notifier(&br_device_notifier);
return 0;
}
初始化函式指明瞭網橋的處理函式是br_handle_frame
ioctl處理函式是:br_ioctl_deviceless_stub
三、br_handle_frame(br_input.c)
/*網橋處理函式*/
void br_handle_frame(struct sk_buff *skb)
{
struct net_bridge *br;
unsigned char *dest;
struct net_bridge_port *p;
/*獲取目的MAC地址*/
dest = skb->mac.ethernet->h_dest;
/*skb->dev->br_port用於指定接收該資料包的埠,若不是屬於網橋的埠,則為NULL*/
p = skb->dev->br_port;
if (p == NULL) /*埠不是網橋組埠中*/
goto err_nolock;
/*本埠所屬的網橋組*/
br = p->br;
/*加鎖,因為在轉發中需要讀CAM表,所以必須加讀鎖,避免在這個過程中另外的核心控制路徑(如多處理機上另外一個CPU上的系統呼叫)修改CAM表*/
read_lock(&br->lock);
if (skb->dev->br_port == NULL) /*前面判斷過的*/
goto err;
/*br->dev是網橋的虛擬網路卡,如果它未UP,或網橋DISABLED,p->state實際上是橋的當前埠的STP計算判斷後的狀態*/
if (!(br->dev.flags & IFF_UP) ||
p->state == BR_STATE_DISABLED)
goto err;
/*源MAC地址為255.X.X.X,即源MAC是多播或廣播,丟棄之*/
if (skb->mac.ethernet->h_source[0] & 1)
goto err;
/*眾所周之,網橋之所以是網橋,比HUB更智慧,是因為它有一個MAC-PORT的表,這樣轉發資料就不用廣播,而查表定埠就可以了
每次收到一個包,網橋都會學習其來源MAC,新增進這個表。Linux中這個表叫CAM表(這個名字是其它資料上看的)。
如果橋的狀態是LEARNING或FORWARDING(學習或轉發),則學習該包的源地址skb->mac.ethernet->h_source,
將其新增到CAM表中,如果已經存在於表中了,則更新定時器,br_fdb_insert完成了這一過程*/
if (p->state == BR_STATE_LEARNING ||
p->state == BR_STATE_FORWARDING)
br_fdb_insert(br, p, skb->mac.ethernet->h_source, 0);
/*STP協議的BPDU包的目的MAC採用的是多播目標MAC地址:從01-80-c2-00-00-00(Bridge_group_addr:網橋組多播地址)開始
所以這裡是如果開啟了STP,而當前資料包又是一個BPDU
(!memcmp(dest, bridge_ula, 5), unsigned char bridge_ula[6] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 },
則交由相應函式處理*/
if (br->stp_enabled &&
/*這裡只比較前5個位元組,沒有仔細研究過STP是使用了全部多播地址(從0 1 : 0 0 : 5 e : 0 0 : 0 0 : 0 0到0 1 : 0 0 : 5 e : 7 f : ff : ff。),還是隻使用了一部份,這裡看來似乎只是一部份,沒去深究了*/
!memcmp(dest, bridge_ula, 5) &&
!(dest[5] & 0xF0)) /*01-80-c2-00-00-F0 是一個什麼地址?為什麼要判斷呢?*/
goto handle_special_frame;
/*處理鉤子函式,然後轉交br_handle_frame_finish函式繼續處理*/
if (p->state == BR_STATE_FORWARDING) {
NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,
br_handle_frame_finish);
read_unlock(&br->lock);
return;
}
err:
read_unlock(&br->lock);
err_nolock:
kfree_skb(skb);
return;
handle_special_frame:
if (!dest[5]) {
br_stp_handle_bpdu(skb);
return;
}
kfree_skb(skb);
}
四、br_handle_frame_finish
static int br_handle_frame_finish(struct sk_buff *skb)
{
struct net_bridge *br;
unsigned char *dest;
struct net_bridge_fdb_entry *dst;
struct net_bridge_port *p;
int passedup;
/*前面基本相同*/
dest = skb->mac.ethernet->h_dest;
p = skb->dev->br_port;
if (p == NULL)
goto err_nolock;
br = p->br;
read_lock(&br->lock);
if (skb->dev->br_port == NULL)
goto err;
passedup = 0;
/*如果網橋的虛擬網路卡處於混雜模式,那麼每個接收到的資料包都需要克隆一份
送到AF_PACKET協議處理體(網路軟中斷函式net_rx_action中ptype_all鏈的處理)。*/
if (br->dev.flags & IFF_PROMISC) {
struct sk_buff *skb2;
skb2 = skb_clone(skb, GFP_ATOMIC);
if (skb2 != NULL) {
passedup = 1;
br_pass_frame_up(br, skb2);
}
}
/*目的MAC為廣播或多播,則需要向本機的上層協議棧傳送這個資料包,這裡有一個標誌變數passedup
用於表示是否傳送過了,如果已傳送過,那就算了*/
if (dest[0] & 1) {
br_flood_forward(br, skb, !passedup);
if (!passedup)
br_pass_frame_up(br, skb);
goto out;
}
/*Linux中的MAC-PORT表是CAM表,這裡根據目的地址來查表,以確定由哪個介面把包轉發出去
每一個表項是透過結構struct net_bridge_fdb_entry來描述的:
struct net_bridge_fdb_entry
{
struct net_bridge_fdb_entry *next_hash; //用於CAM表連線的連結串列指標
struct net_bridge_fdb_entry **pprev_hash; //為什麼是pprev不是prev呢?還沒有仔細去研究
atomic_t use_count; //此項當前的引用計數器
mac_addr addr; //MAC地址
struct net_bridge_port *dst; //此項所對應的物理埠
unsigned long ageing_timer; //處理MAC超時
unsigned is_local:1; //是否是本機的MAC地址
unsigned is_static:1; //是否是靜態MAC地址
};*/
dst = br_fdb_get(br, dest);
/*查詢CAM表後,如果能夠找到表項,並且目的MAC是到本機的虛擬網路卡的,那麼就需要把這個包提交給上層協議,
這樣,我們就可以透過這個虛擬網路卡的地址來遠端管理網橋了*/
if (dst != NULL && dst->is_local) {
if (!passedup)
br_pass_frame_up(br, skb);
else
kfree_skb(skb);
br_fdb_put(dst);
goto out;
}
/*查到表了,且不是本地虛擬網路卡的,轉發之*/
if (dst != NULL) {
br_forward(dst->dst, skb);
br_fdb_put(dst);
goto out;
}
/*如果表裡邊查不到,那麼只好學習學習HUB了……*/
br_flood_forward(br, skb, 0);
out:
read_unlock(&br->lock);
return 0;
err:
read_unlock(&br->lock);
err_nolock:
kfree_skb(skb);
return 0;
}
基本框架就是這樣了,與那些講網橋原理的書上講的基本差不多……
網橋之所以是網橋,主要靠這兩個函式:
br_fdb_insert
br_fdb_get
一個學習,一個查表;
另外,支援STP,處理BPDU,需要用到函式br_stp_handle_bpdu
哪位有這三個函式的細節分析,可否送九賤一份,免得下午那麼辛苦再去啃程式碼……
掃了一下 br_fdb_insert,結構還是很清析,如果當前項已存在於hash表項中,則更新它(__fdb_possibly_replace),如果是新項,則插入,實際是一個雙向連結串列的維護過程(__hash_link):
void br_fdb_insert(struct net_bridge *br,
struct net_bridge_port *source,
unsigned char *addr,
int is_local)
{
struct net_bridge_fdb_entry *fdb;
int hash;
hash = br_mac_hash(addr);
write_lock_bh(&br->hash_lock);
fdb = br->hash[hash];
while (fdb != NULL) {
if (!fdb->is_local &&
!memcmp(fdb->addr.addr, addr, ETH_ALEN)) {
__fdb_possibly_replace(fdb, source, is_local);
write_unlock_bh(&br->hash_lock);
return;
}
fdb = fdb->next_hash;
}
fdb = kmalloc(sizeof(*fdb), GFP_ATOMIC);
if (fdb == NULL) {
write_unlock_bh(&br->hash_lock);
return;
}
memcpy(fdb->addr.addr, addr, ETH_ALEN);
atomic_set(&fdb->use_count, 1);
fdb->dst = source;
fdb->is_local = is_local;
fdb->is_static = is_local;
fdb->ageing_timer = jiffies;
__hash_link(br, fdb, hash);
write_unlock_bh(&br->hash_lock);
}
同樣,查表也是一個遍歷連結串列,進行地址匹配的過程:
struct net_bridge_fdb_entry *br_fdb_get(struct net_bridge *br, unsigned char *addr)
{
struct net_bridge_fdb_entry *fdb;
read_lock_bh(&br->hash_lock);
fdb = br->hash[br_mac_hash(addr)];
while (fdb != NULL) {
if (!memcmp(fdb->addr.addr, addr, ETH_ALEN)) {
if (!has_expired(br, fdb)) {
atomic_inc(&fdb->use_count);
read_unlock_bh(&br->hash_lock);
return fdb;
}
read_unlock_bh(&br->hash_lock);
return NULL;
}
fdb = fdb->next_hash;
}
read_unlock_bh(&br->hash_lock);
return NULL;
}
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10617542/viewspace-947308/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- [轉帖]Linux核心原始碼分析分享專題Linux原始碼
- MJRefresh原始碼框架分析原始碼框架
- linux原始碼分析Linux原始碼
- Uber RIBs框架原始碼分析框架原始碼
- Java 集合框架------ArrayList原始碼分析Java框架原始碼
- Uncode-Schedule框架原始碼分析框架原始碼
- workerman 網路框架原始碼核心分析和註解 over 篇框架原始碼
- Java容器類框架分析(1)ArrayList原始碼分析Java框架原始碼
- Java容器類框架分析(2)LinkedList原始碼分析Java框架原始碼
- Java容器類框架分析(5)HashSet原始碼分析Java框架原始碼
- Java類集框架 —— ArrayList原始碼分析Java框架原始碼
- Java類集框架 —— LinkedList原始碼分析Java框架原始碼
- Linux下init程式原始碼分析Linux原始碼
- Android Hook框架Xposed原理與原始碼分析AndroidHook框架原始碼
- Java類集框架 —— HashSet、LinkedHashSet原始碼分析Java框架原始碼
- 圖片載入框架Picasso - 原始碼分析框架原始碼
- Spark RPC框架原始碼分析(一)簡述SparkRPC框架原始碼
- 圖片載入框架Picasso原始碼分析框架原始碼
- Android 外掛化框架 DynamicLoadApk 原始碼分析Android框架APK原始碼
- workerman 框架原始碼核心分析和註解框架原始碼
- 如何優雅的分析 ThinkPHP 框架原始碼PHP框架原始碼
- 原始碼分析:同步基礎框架——AbstractQueuedSynchronizer(AQS)原始碼框架AQS
- 原始碼分析Gateway請求轉發原始碼Gateway
- Riffa學習——Linux Driver原始碼分析Linux原始碼
- Spark RPC框架原始碼分析(三)Spark心跳機制分析SparkRPC框架原始碼
- Andriod 網路框架 OkHttp 原始碼解析框架HTTP原始碼
- Android 網路框架 Retrofit 原始碼解析Android框架原始碼
- Linux Netfilter框架分析LinuxFilter框架
- shell初步(轉)
- Retrofit原始碼分析三 原始碼分析原始碼
- Seata 分散式事務框架 TCC 模式原始碼分析分散式框架模式原始碼
- Rxjava 2.x 原始碼系列 - 基礎框架分析RxJava原始碼框架
- 比特幣原始碼分析-網路(一)比特幣原始碼
- Redis網路模型的原始碼分析Redis模型原始碼
- 【轉】istio原始碼分析——mixer遙測報告原始碼
- Linux程式排程邏輯與原始碼分析Linux原始碼
- Linux核心原始碼分析之set_arch (一)Linux原始碼
- Linux核心原始碼分析之setup_arch (四)Linux原始碼
- Linux核心原始碼分析之setup_arch (三)Linux原始碼