面已经分析,接口添进网桥时,户空间调ioctl(br_socket_fd, SIOCBRADDIF, &ifr)注意void br_dev_setup(struct net_device *dev)已经dev->do_ioctl进行赋值,即:dev->do_ioctl = br_dev_ioctl进行ioctl进行访问时候,进入br_dev_ioctl: (net/brige/br_ioctl.c)int br_dev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) { struct net_bridge *br = netdev_priv(dev); switch(cmd) { case SIOCDEVPRIVATE: return old_dev_ioctl(dev, rq, cmd); //添加个接口 case SIOCBRADDIF: //除个接口 case SIOCBRDELIF: return add_del_if(br, rq->ifr_ifindex, cmd == SIOCBRADDIF); } pr_debug("Bridge does not support ioctl 0x%xn", cmd); return -EOPNOTSUPP; }
们户空间使标志SIOCBRADDIF。所以流程进入add_del_if()static int add_del_if(struct net_bridge *br, int ifindex, int isadd) { struct net_device *dev; int ret; if (!capable(CAP_NET_ADMIN)) return -EPERM; dev = dev_get_by_index(ifindex); if (dev == NULL) return -EINVAL; if (isadd) ret = br_add_if(br, dev); else ret = br_del_if(br, dev); dev_put(dev); return ret; }
因cmd == SIOCBRADDIF真,所以调br_add_if():int br_add_if(struct net_bridge *br, struct net_device *dev) (net/brige/br_if.c)) { struct net_bridge_port *p; int err = 0; //回环。或非以及网接口 if (dev->flags & IFF_LOOPBACK || dev->type != ARPHRD_ETHER) return -EINVAL; //构造数据包函数网桥类型 if (dev->hard_start_xmit == br_dev_xmit) return -ELOOP; //此接口已经存于网桥 if (dev->br_port != NULL) return -EBUSY; //dev 创建网桥接口.dev->br_port。指向所属网桥端口 //dev->br_port->br:指向所属网桥 //该接口创建net_bridge_port if (IS_ERR(p = new_nbp(br, dev, br_initial_port_cost(dev)))) return PTR_ERR(p); //更新port->MAC应表 if ((err = br_fdb_insert(br, p, dev->dev_addr, 1))) destroy_nbp(p); else if ((err = br_sysfs_addif(p))) del_nbp(p); else { //设置接口混杂模式 dev_set_promiscuity(dev, 1); //p->list更新至br->port_list list_add_rcu(&p->list, &br->port_list); spin_lock_bh(&br->lock); br_stp_recalculate_bridge_id(br); if ((br->dev->flags & IFF_UP) && (dev->flags & IFF_UP) && netif_carrier_ok(dev)) br_stp_enable_port(p); spin_unlock_bh(&br->lock); dev_set_mtu(br->dev, br_min_mtu(br)); } return err; }
接口创建net_bridge_port函数new_nbp。这个函数比较简单:static struct net_bridge_port *new_nbp(struct net_bridge *br, struct net_device *dev, unsigned long cost) { int index; struct net_bridge_port *p; index = find_portno(br); if (index < 0) return ERR_PTR(index); p = kmalloc(sizeof(*p), GFP_KERNEL); if (p == NULL) return ERR_PTR(-ENOMEM); memset(p, 0, sizeof(*p)); p->br = br; dev_hold(dev); p->dev = dev; p->path_cost = cost; p->priority = 0x8000 >> BR_PORT_BITS; dev->br_port = p; p->port_no = index; br_init_port(p); p->state = BR_STATE_DISABLED; kobject_init(&p->kobj); return p; }
,加入接口应mac接口作本机静态项加入prot—mac应表。这br_fdb_insert()实现int br_fdb_insert(struct net_bridge *br, struct net_bridge_port *source, const unsigned char *addr, int is_local) { int ret; spin_lock_bh(&br->hash_lock); ret = fdb_insert(br, source, addr, is_local); spin_unlock_bh(&br->hash_lock); return ret; }
操作存异步性,插入加锁。具体插入fdb_insert实现static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source, const unsigned char *addr, int is_local) { struct hlist_node *h; struct net_bridge_fdb_entry *fdb; int hash = br_mac_hash(addr); //判断否有效mac 地址 if (!is_valid_ether_addr(addr)) return -EADDRNOTAVAIL; hlist_for_each_entry(fdb, h, &br->hash[hash], hlist) { //如果表已经包含此项 if (!memcmp(fdb->addr.addr, addr, ETH_ALEN)) { //如果本机MAC /* attempt to update an entry for a local interface */ if (fdb->is_local) { /* it is okay to have multiple ports with same * address, just don't allow to be spoofed. */ if (is_local) return 0; if (net_ratelimit()) printk(KERN_WARNING "%s: received packet with " " own address as source addressn", source->dev->name); return -EEXIST; } //如果添加本机IP if (is_local) { printk(KERN_WARNING "%s adding interface with same address " "as a received packetn", source->dev->name); goto update; } //如果添加静态MAC //则不更新相关信息 if (fdb->is_static) return 0; /* move to end of age list */ list_del(&fdb->u.age_list); goto update; } } fdb = kmem_cache_alloc(br_fdb_cache, GFP_ATOMIC); if (!fdb) return ENOMEM; memcpy(fdb->addr.addr, addr, ETH_ALEN); atomic_set(&fdb->use_count, 1); hlist_add_head_rcu(&fdb->hlist, &br->hash[hash]); if (!timer_pending(&br->gc_timer)) { br->gc_timer.expires = jiffies + hold_time(br); add_timer(&br->gc_timer); } update: fdb->dst = source; fdb->is_local = is_local; fdb->is_static = is_local; fdb->ageing_timer = jiffies; if (!is_local) list_add_tail(&fdb->u.age_list, &br->age_list); return 0; }
此函数先判断插入项否存,若已存,且不静态项,具更新应项。若不存该项,则分配个net_bridge_fdb_entry,插入CAM表先分析net_bridge_port结构:struct net_bridge_port { //当端口所briage struct net_bridge *br; //此端口应物理端口 struct net_device *dev; //同桥端口链表? struct list_head list; /* STP */ u8 priority; u8 state; u16 port_no; unsigned char topology_change_ack; unsigned char config_pending; port_id port_id; port_id designated_port; bridge_id designated_root; bridge_id designated_bridge; u32 path_cost; u32 designated_cost; struct timer_list forward_delay_timer; struct timer_list hold_timer; struct timer_list message_age_timer; struct kobject kobj; struct rcu_head rcu; };
应net_bridge_fdb_entry结构://CAM表应数据结构 struct net_bridge_fdb_entry { //于CAM表连接链表指针 struct hlist_node hlist; //此项应物理出口 struct net_bridge_port *dst; union { struct list_head age_list; struct rcu_head rcu; } u; //此项当引计数 atomic_t use_count; //超时时间 unsigned long ageing_timer; //MAC地址 mac_addr addr; //否主机地址 unsigned char is_local; //否静态地址 unsigned char is_static; }; struct net_bridge_port { //当端口所briage struct net_bridge *br; //此端口应物理端口 struct net_device *dev; //同桥端口链表? struct list_head list; /* STP */ u8 priority; u8 state; u16 port_no; unsigned char topology_change_ack; unsigned char config_pending; port_id port_id; port_id designated_port; bridge_id designated_root; bridge_id designated_bridge; u32 path_cost; u32 designated_cost; struct timer_list forward_delay_timer; struct timer_list hold_timer; struct timer_list message_age_timer; struct kobject kobj; struct rcu_head rcu;};
这样,就往桥添加个接口,从图以反应出接口桥间关系。们以brctl show指令看当所有桥,以及桥里相应接口。ifconfig br0以看当桥状态,如果细心点以看,bro已经有应MAC。这怎么呢?桥MAC地址更新:注意br_add_if调函数br_stp_recalculate_bridge_id()面代码分析,简化分析,stp相关流程忽略掉,现们看这个函数做些什么/* called under bridge lock */ void br_stp_recalculate_bridge_id(struct net_bridge *br) { const unsigned char *addr = br_mac_zero; struct net_bridge_port *p; //遍历桥所有端口 list_for_each_entry(p, &br->port_list, list) { //所有接口MAC最少值 if (addr == br_mac_zero || memcmp(p->dev->dev_addr, addr, ETH_ALEN) < 0) addr = p->dev->dev_addr; } //如果不现桥MAC相同 if (memcmp(br->bridge_id.addr, addr, ETH_ALEN)) br_stp_change_bridge_id(br, addr); }
这个函数比较简单,就遍历桥应所有接口,然最MAC。然判断最MAC跟现MAC否相同继续跟踪br_stp_change_bridge_idstatic void br_stp_change_bridge_id(struct net_bridge *br, const unsigned char *addr) { unsigned char oldaddr[6]; struct net_bridge_port *p; int wasroot; wasroot = br_is_root_bridge(br); memcpy(oldaddr, br->bridge_id.addr, ETH_ALEN); memcpy(br->bridge_id.addr, addr, ETH_ALEN); //注意这里,呵呵,桥MAC更新 memcpy(br->dev->dev_addr, addr, ETH_ALEN); list_for_each_entry(p, &br->port_list, list) { if (!memcmp(p->designated_bridge.addr, oldaddr, ETH_ALEN)) memcpy(p->designated_bridge.addr, addr, ETH_ALEN); if (!memcmp(p->designated_root.addr, oldaddr, ETH_ALEN)) memcpy(p->designated_root.addr, addr, ETH_ALEN); } br_configuration_update(br); br_port_state_selection(br); if (br_is_root_bridge(br) && !wasroot) br_become_root_bridge(br); }
看 面 注释 吧,桥 MAC就 这里得 更新 ,所以,桥 MAC地址 得 所有接口 最 值
网桥 接收数据 处理:
回本章开始handle_bridge函数,调br_handle_frame_hook进行接收数据处理网桥始化代码,br_handle_frame_hook赋值br_handle_frame没,这就网桥处理函数。跟进个函数nt br_handle_frame(struct net_bridge_port *p, struct sk_buff **pskb) { struct sk_buff *skb = *pskb; //目mac地址 const unsigned char *dest = eth_hdr(skb)->h_dest; //端口禁 if (p->state == BR_STATE_DISABLED) goto err; //源mac 播或广播,丢弃 //FF.XX.XX.XX.XX.XX形式 if (eth_hdr(skb)->h_source[0] & 1) goto err; //如果状态学习或转发,则学习源mac 更新CAM 表 if (p->state == BR_STATE_LEARNING || p->state == BR_STATE_FORWARDING) // br_fdb_insert函数们面已经分析过 br_fdb_insert(p->br, p, eth_hdr(skb)->h_source, 0); //stp 处理,stp-enabled 否启stp 协议 //bridge_ula stp使播mac地址 if (p->br->stp_enabled && !memcmp(dest, bridge_ula, 5) && !(dest[5] & 0xF0)) { if (!dest[5]) { NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, skb->dev, NULL, br_stp_handle_bpdu); return 1; } } else if (p->state == BR_STATE_FORWARDING) { //始化,并末br_should_route_hook进行赋值 //所以br_should_route_hook假 if (br_should_route_hook) { if (br_should_route_hook(pskb)) return 0; skb = *pskb; dest = eth_hdr(skb)->h_dest; } //目地址桥地址相同。则传层处理 //置skb->pkt_type = PACKET_HOST if (!memcmp(p->br->dev->dev_addr, dest, ETH_ALEN)) skb->pkt_type = PACKET_HOST; //网桥NF_BR_PRE_ROUTING点netfiter处理 NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL, br_handle_frame_finish); return 1; } err: kfree_skb(skb); return 1; }
这个函数里,进行相关入口判断,当数据包源MAC接口应更新CAM表,更新函数br_fdb_insert()面已经分析过,不太明白以倒过去看,不过注意,这不做静态项插入。接着判断包不传给本机,如果,则置包pkt_typePACKET_HOST关于NF_HOOK()宏,们以netfiter有专题分析。这们只知道,常数据包流进br_handle_frame_finish()进行处理/* note: already called with rcu_read_lock (preempt_disabled) */ int br_handle_frame_finish(struct sk_buff *skb) { //得目MAC地址 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; //混杂模式 /*如果网桥虚拟网卡处于混杂模式,那么每个接收数据包都需克隆份 送AF_PACKET协议处理体(网络软断函数net_rx_actionptype_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传送标志,1 时表示已经传过 if (dest[0] & 1) { br_flood_forward(br, skb, !passedup); if (!passedup) br_pass_frame_up(br, skb); goto out; } //查询CAM 表 dst = __br_fdb_get(br, dest); //本机? 传至层协议处理 if (dst != NULL && dst->is_local) { if (!passedup) br_pass_frame_up(br, skb); else kfree_skb(skb); goto out; } //不本机数据,则转发 if (dst != NULL) { br_forward(dst->dst, skb); goto out; } //如果查询不,其端口都发送此包 br_flood_forward(br, skb, 0); out: return 0; }
这里函数里,通过查找CAM表,得发送端口,如果当CAM表里没有目MAC端口,则其端口都发送此数据包。这个函数里,们看,查询CAM表函数:__br_fdb_get()接着分析此函数struct net_bridge_fdb_entry *__br_fdb_get(struct net_bridge *br, const unsigned char *addr) { struct hlist_node *h; struct net_bridge_fdb_entry *fdb; //遍历应MAC哈希项fdb hlist_for_each_entry_rcu(fdb, h, &br->hash[br_mac_hash(addr)], hlist) { if (!memcmp(fdb->addr.addr, addr, ETH_ALEN)) { if (unlikely(has_expired(br, fdb))) break; return fdb; } } return NULL; }
这个函数非常容易,首先 得目 MAC 应 哈希项。然 再遍历里面 数据,查看 否含有目 地址 项。如果 送给本机 数据包,则传至 层协议,如不 ,则需 转发。
|