网络子系统34_网桥设备的传输与接收
// 网桥设备驱动程序的hard_start_xmit函数 // 函数主要任务: // 1.广播或多播地址,在所有端口上扩散 // 2.存在转发项,在指定端口上发送 // 3.没有找到转发项,在所有端口上扩散 1.1 int br_dev_xmit(struct sk_buff *skb, struct net_device *dev) { struct net_bridge *br = netdev_priv(dev); const unsigned char *dest = skb->data; struct net_bridge_fdb_entry *dst; //更新网桥设备的统计信息 br->statistics.tx_packets++; br->statistics.tx_bytes += skb->len; //更新l2头位置 skb->mac.raw = skb->data; skb_pull(skb, ETH_HLEN); rcu_read_lock(); if (dest[0] & 1)//l2地址的第一个字节的第一位为1,则为多播地址 br_flood_deliver(br, skb, 0);//在所有端口上发送此报文 else if ((dst = __br_fdb_get(br, dest)) != NULL)//如果转发数据中存在目标地址的记录 br_deliver(dst->dst, skb);//通过与目标地址相关的端口发送 else br_flood_deliver(br, skb, 0); rcu_read_unlock(); return 0; } // 在所有端口扩散skb // 参数:__packet_hook, __br_deliver // 调用路径:br_dev_xmit->br_flood_deliver->br_flood 1.3 static void br_flood(struct net_bridge *br, struct sk_buff *skb, int clone, void (*__packet_hook)(const struct net_bridge_port *p, struct sk_buff *skb)) { struct net_bridge_port *p; struct net_bridge_port *prev; //指示是否复制一份skb,br_dev_xmit->br_flood_deliver->br_flood此调用路径clone=0 if (clone) { struct sk_buff *skb2; if ((skb2 = skb_clone(skb, GFP_ATOMIC)) == NULL) { br->statistics.tx_dropped++; return; } skb = skb2; } prev = NULL; //遍历所有的端口 list_for_each_entry_rcu(p, &br->port_list, list) { if (should_deliver(p, skb)) {//skb的输出设备非此端口,并且此端口处于转发模式 if (prev != NULL) {//在第一次运行时,prev=NULL,直接复制prev=p struct sk_buff *skb2; if ((skb2 = skb_clone(skb, GFP_ATOMIC)) == NULL) {//克隆sk_buff,共享packet data br->statistics.tx_dropped++; kfree_skb(skb); return; } __packet_hook(prev, skb2);//调用__br_deliver,执行一系列的netfilter调用点,最后由dev_queue_xmit完成传输 } prev = p; } } if (prev != NULL) { __packet_hook(prev, skb);//对最后一个端口调用__br_deliver return; } kfree_skb(skb); } // 在网桥端口传输 // 调用路径:br_flood->hook->br_dev_queue_push_xmit 1.6 int br_dev_queue_push_xmit(struct sk_buff *skb) { if (skb->len > skb->dev->mtu)//如果skb的长度大于出口设备的mtu,则直接丢弃封包 kfree_skb(skb); else { skb_push(skb, ETH_HLEN); dev_queue_xmit(skb);//输出 } return 0; } // 判断端口是否应该发送数据帧 // 端口发送数据帧的条件: // 1.skb的入口设备非此端口 // 2.端口处于转发模式 1.7 static inline int should_deliver(const struct net_bridge_port *p, const struct sk_buff *skb) { if (skb->dev == p->dev ||//skb的接收端口为此端口 p->state != BR_STATE_FORWARDING)//端口非转发模式 return 0; return 1; } // 网桥入口数据帧处理 // 调用路径:netif_receive_skb->handle_bridge // // 由网桥接收入口数据的条件: // 1. 入口设备非回环地址 // 2. 入口设备为网桥端口 2.1 static __inline__ int handle_bridge(struct sk_buff **pskb, struct packet_type **pt_prev, int *ret) { struct net_bridge_port *port; if ((*pskb)->pkt_type == PACKET_LOOPBACK || (port = rcu_dereference((*pskb)->dev->br_port)) == NULL)//如果设备为网桥的端口,则dev->br_port为网桥端口控制块 return 0; if (*pt_prev) { *ret = deliver_skb(*pskb, *pt_prev); *pt_prev = NULL; } //此设备为网桥的端口 return br_handle_frame_hook(port, pskb); } // 网桥处理入口流量 // 参数: // p,接收到此入口流量的端口 // pskb, 入口数据帧 // // 返回值: // 0,网桥没有处理此pskb,netif_receive_skb继续处理pskb // 1,网桥已经处理此pskb,netif_receive_skb无需继续处理此pskb // // 调用路径 : netif_receive_skb->handle_bridge->br_handle_frame // // 函数主要任务: // 1.检查分包合法性: // 1.1 接收端口开启状态 // 1.2 源地址非多播或广播地址 // 2.向转发数据库添加转发项 // 3.识别配置bpdu,处理配置bpdu // 4.决定入口流量被路由还是桥接 // 5.如果数据帧需要被桥接,则由桥接处理程序继续处理。 // 注: // 1.只有转发态(BR_STATE_FORWARDING),学习态(BR_STATE_LEARNING)的网桥端口,可以向转发数据库添加转发项。 // 2.bpdu的识别办法:以太网帧的目的地址为[0x01, 0x80, 0xc2, 0x00, 0x00, 0x00] // 3.决定入口流量被路由还是桥接,通过ebtable_broute决定 // 4.只有转发态(BR_STATE_FORWARDING)的网桥端口,可以处理普通入口流量。 2.2 int br_handle_frame(struct net_bridge_port *p, struct sk_buff **pskb) { struct sk_buff *skb = *pskb; const unsigned char *dest = eth_hdr(skb)->h_dest; if (p->state == BR_STATE_DISABLED)//端口被关闭 goto err; if (eth_hdr(skb)->h_source[0] & 1)//源地址为广播地址,说明错误 goto err; if (p->state == BR_STATE_LEARNING ||//如果当前端口处于学习状态,或者转发状态 p->state == BR_STATE_FORWARDING) br_fdb_insert(p->br, p, eth_hdr(skb)->h_source, 0);//在转发数据库中添加一条新转发项,或者更新现有的转发项 if (p->br->stp_enabled &&//网桥的stp协议开启 !memcmp(dest, bridge_ula, 5) &&//网桥组播地址的前5个字节 !(dest[5] & 0xF0)) {//bridge_ula[6] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 },通过入口帧的目标地址识别bpdu数据帧 if (!dest[5]) { NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, skb->dev, NULL, br_stp_handle_bpdu);//入口bpdu的数据帧,可以被任何端口接收,只要这个端口没有通过管理性手段关闭 return 1; } } else if (p->state == BR_STATE_FORWARDING) {//当前端口处于转发状态,一个封包是被路由还是桥接,通过br_should_route_hook函数决定 if (br_should_route_hook) {//在ebtable_broute模块中被设置 if (br_should_route_hook(pskb)) return 0; skb = *pskb; dest = eth_hdr(skb)->h_dest; } if (!memcmp(p->br->dev->dev_addr, dest, ETH_ALEN))//目标地址为本地 skb->pkt_type = PACKET_HOST; NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL, br_handle_frame_finish);//默认情况下,被绑定的设备所接收到的网络流量会分配给它的指定网桥 return 1; } err: kfree_skb(skb); return 1; } // 桥接入口流量 // 调用路径: br_handle_frame->br_handle_frame_finish // 函数主要任务: // 1.网桥端口均处于混杂模式,向网桥本地接收模块传递一份此数据帧 // 2.多播或广播目的地址,在除接收端口外的所有端口,扩散此入口数据帧,退出 // 3.如果,数据帧目的地址为本机,向网桥本地接收模块传递一份此数据帧,退出 // 4.否则,如果网桥知道能到达目的地址的输出端口,从输出端口发送此数据帧,退出 // 5.否则,如果网桥不知道使用哪个输出端口,向所有端口扩散,退出 2.3 int br_handle_frame_finish(struct sk_buff *skb) { const unsigned char *dest = eth_hdr(skb)->h_dest; struct net_bridge_port *p = skb->dev->br_port; struct net_bridge *br = p->br; struct net_bridge_fdb_entry *dst; int passedup = 0; if (br->dev->flags & IFF_PROMISC) {//设备处于混杂模式 struct sk_buff *skb2; skb2 = skb_clone(skb, GFP_ATOMIC);//复制一份sk_buff,共享packet data if (skb2 != NULL) { passedup = 1; br_pass_frame_up(br, skb2);//向上层协议传递skb } } if (dest[0] & 1) {//以太网多播地址 br_flood_forward(br, skb, !passedup);//从各个端口扩散此skb if (!passedup) br_pass_frame_up(br, skb);//如果处于混杂模式,则已经向上传递了一份skb goto out; } dst = __br_fdb_get(br, dest);//非多播地址的skb,查询转发数据库 if (dst != NULL && dst->is_local) {//本地地址 if (!passedup) br_pass_frame_up(br, skb);//向上层协议传递一份skb,由于网桥端口为混杂模式,之前会传递一份,所以此处不会再传递 else kfree_skb(skb); goto out; } if (dst != NULL) { br_forward(dst->dst, skb);//非本地地址,从通往目标地址的端口发送此skb goto out; } br_flood_forward(br, skb, 0);//非以太网多播,没有明确的出口设备,从所有端口上扩散 out: return 0; } // 网桥向本地传递流量 // 调用路径:br_handle_frame_finish->br_pass_frame_up // 函数主要任务: // 1.skb->dev设置为网桥设备,为skb重新走一遍netif_receive_skb // 注:网桥端口处于混杂模式,所有数据帧均会向上传递,并且一个数据帧,只会向本机传递一次。 2.4 static void br_pass_frame_up(struct net_bridge *br, struct sk_buff *skb) { struct net_device *indev; //增加统计数据 br->statistics.rx_packets++; br->statistics.rx_bytes += skb->len; indev = skb->dev; skb->dev = br->dev;//修改接收此skb的设备为网桥设备 NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, indev, NULL, br_pass_frame_up_finish); } // 网桥向本地传递流量 // 使用积压设备的方式 // 调用路径:br_pass_frame_up->br_pass_frame_up_finish 2.5 static int br_pass_frame_up_finish(struct sk_buff *skb) { //挂载积压设备到poll_list,skb到input_pkt_queueu, 触发接收软中断 netif_rx(skb); return 0; }
最后更新:2017-04-03 14:53:52