浅析linux内核网络协议栈--linux bridge(二)

news2024/9/29 1:20:01

6. 网桥数据转发

6.1 网桥数据包入口

网桥是一种2层网络互连设备,而不是一种网络协议。它在协议结构上并没有占有一席之地,因此不能通过向协议栈注册协议的方式来申请网桥数据包的处理。相 反,网桥接口(如上述的eth1)的数据包和一般接口(如eth0)在格式上完全是一样的,不同之处是网桥在2层上就对它进行了转了,而一般接口要在3层 才能根据路由信息来决定是否要转发,如何转发。那么一个网络接口,在驱动处理完数据包后,怎么才知道该接口分配在一个网桥里面呢?其实很简单,当 brctl工具通过ioctl系统调用时,kernel为该添加的设备生成一个bridge_port结构并放到port_list链中,同时将该 bridge_port的值赋予设备net_device的br_port指针。因此,要识别接口是否属于某个网桥,只需判断net_device的 br_port指针是否不为空即可。

现假设PC1向PC2发送其个数据包,数据首先会由eth1网卡接收,此后网卡向CPU发送接收中断。当CPU执行当前指令后(如果开中断的话),马上跳 到网卡的驱动程去。Eth1的网卡驱动首先生成一个skb结构,然后对以太网层进行分析,最后驱动将该skb结构放到当前CPU的输入队列中,唤醒软中断。如果没有其它中断的到来,那么软中断将调用netif_receive_skb函数。代码和分析如下所述:

int netif_receive_skb(struct sk_buff *skb) {
   //当网络设备收到网络数据包时,最终会在软件中断环境里调用此函数
   //检查该数据包是否有packet socket来接收该包,如果有则往该socket
   //拷贝一份,由deliver_skb来完成。
   list_for_each_entry_rcu(ptype, &ptype_all, list) {
     if (!ptype->dev || ptype->dev == skb->dev) {
       if (pt_prev)
         ret = deliver_skb(skb, pt_prev, orig_dev);
       pt_prev = ptype;
     } 
   }
   // 先试着将该数据包让网桥函数来处理,如果该数据包的入口接口确实是网桥接口,
   // 则按网桥方式来处理,并且handle_bridge返回NULL,表示网桥已处理了。
   // 如果不是网桥接口的数据包,则不应该让网桥来处理,handle_bridge返回skb,
   // 后面代码会让协议栈来处理上层协议。
   skb = handle_bridge(skb, &pt_prev, &ret, orig_dev);
   if (!skb)
     goto out;
   skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev);
   if (!skb)
     goto out;
   //对该数据包转达到它L3协议的处理函数
   type = skb->protocol;
   list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) {
     if (ptype->type == type &&
         (!ptype->dev || ptype->dev == skb->dev)) {
       if (pt_prev)
         ret = deliver_skb(skb, pt_prev, orig_dev);
       pt_prev = ptype;
     }
   }
}
【文章福利】小编推荐自己的Linux内核技术交流群: 【977878001】整理一些个人觉得比较好得学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100进群领取,额外赠送一份 价值699的内核资料包(含视频教程、电子书、实战项目及代码)

内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料

学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

6.2 网桥处理逻辑

static inline struct sk_buff *handle_bridge(struct sk_buff *skb,
                                            struct packet_type **pt_prev, int *ret,
                                            struct net_device *orig_dev) {
  struct net_bridge_port *port;
  //如果该数据包产生于本机,而目标同时为本机。
  if (skb->pkt_type == PACKET_LOOPBACK ||
     //如果该数据包的输入接口不是网桥接口
     (port = rcu_dereference(skb->dev->br_port)) == NULL)
     // 以上两种情况都需要让上层协议进行处理
    return skb;
  if (*pt_prev) {
    *ret = deliver_skb(skb, *pt_prev, orig_dev);
    *pt_prev = NULL;
  }
  //数据包的入口接口是网桥接口。下面将按网桥逻辑进行处理。
  //如假包换,数据包转达到真正的网桥处理函数
  //br_handle_frame_hook在网桥模块的init函数被初始化为
  //br_handle_frame
  return br_handle_frame_hook(port, skb);
}

这里回调了br_handle_frame_hook()函数,这个是一个钩子函数。Br_handle_frame_hook()函数在Linux2.6.34\net\bridge\Br_input.c中,br_handle_frame_hook=br_handle_frame,所以实际函数为br_handle_frame.

6.3 br_handle_frame

好,现在我们看看bridge端口的处理函数 br_handle_frame如何处理skb和指示后续操作。该函数位于br_input.c中。

if (!is_valid_ether_addr(eth_hdr(skb)->h_source))
    goto drop;

网桥端口不打算处理回环数据;源地址必须为合法Ethernet地址:源MAC地址不能是全0,不能是MAC广播和多播,是的话就丢弃。

skb = skb_share_check(skb, GFP_ATOMIC);
if (!skb)
    return RX_HANDLER_CONSUMED;

如果skb是共享的,考虑的网桥端口会修改skb,将它clone一份。

接下来数据被分为两类:目的地址是Link Local MAC层多播的数据包括了STP的BPDU,和普通数据。

STP帧(BPDU)和其他保留多播帧

首先是Link Local MAC多播的处理。802.1D有组保留的 Link Local 多播MAC地址,他们用于控制协议,如STP。如果接收到了STP但网桥没有开STP协议,就视为普通数据处理;换句话说,就是本网桥当作自己是不认识STP的网桥,例如Hub或不支持STP的Switch。这时需要Flood STP报文到其他端口,而保证那些支持STP网桥则看不到不支持STP设备的存在。对于其他Kernel不支持的管理帧处理方式类似。

最后能够在此函数直接递交到Local Host的只能STP功能打开情况下收到的STP帧。递交的时候经过Netfilter的NF_BR_LOCAL_IN的 HOOK点,然后是br_handle_local_finish。br_handle_local_finish的处理实际上不如说是“不处理”,它只是在端口处于Learning的情况下学习个skb的源MAC,并且总是返回0指示包 RX_HANDLER_PASS,由netif_receive_skb继续根据ptype_base处理(STP报文)。所有这段代码对于STP的处理也只是学了个源MAC,然后继续有netif_receive_skb处理。并没有处理STP帧(BPDU).

if (unlikely(is_link_local_ether_addr(dest))) { // MAC Link Local地址通常是管理帧
    ... ...
    switch (dest[5]) {
    case 0x00:  /* Bridge Group Address */// 看看STP要怎么弄法,如果真要处理的话不是在这,而是稍后的protocol dipatching(ptype_base)的地方
        /* If STP is turned off,
           then must forward to keep loop detection */
        if (p->br->stp_enabled == BR_NO_STP)
            goto forward;// 没开STP,那STP帧就和普通数据帧一样处理
        break;

    case 0x01:  /* IEEE MAC (Pause) */
        goto drop; // MAC Control帧不能通过网桥

    default:// 其他的保留MAC多播和普通数据帧一样处理
        /* Allow selective forwarding for most other protocols */
        if (p->br->group_fwd_mask & (1u << dest[5]))
            goto forward;
    }

    //如果能到达这,只有一种情况:STP功能打开的情况下,收到了STP帧
    /* Deliver packet to local host only */
    if (NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN, skb, skb->dev,
            NULL, br_handle_local_finish)) { // br_hanle_local_finishq其实只是在Learning状态下学习MAC并返回0
        return RX_HANDLER_CONSUMED; /* consumed by filter */
    } else { // 通常,NF_HOOK(br_handle_local_finish)返回0,于是STB BPDU到此处“pass”,最后由netif_receive_skb根据ptype_base分发到STP协议层。
        *pskb = skb;
        return RX_HANDLER_PASS; /* continue processing */
    }
}

记住,这个函数不会进行STP BPDU的处理!

普通数据帧

走到这里的帧要么是普通数据帧,要么是被视为普通数据的控制帧。它们的处理都是一样的,就是当作普通数据处理。 普通数据帧(非STP帧BPDU), 没有打开STP功能情况下的STP帧,那么就和普通帧一样处理 要么就是其他的保留多播(非MAC Control),那么就和普通帧一样处理

如果目的MAC和网桥设备(而不是网桥端口)的MAC相同,标记为

PACKET_HOST skb->pkt_type = PACKET_HOST;

br_handle_frame分析完了,我们留下两个问题, STP为什么在该函数中没有被处理,并且还去向了ptype_base的流程。 br_handle_frame_finish是做什么的 第一个问题其实好理解,STP作为一种特殊类型的Ethernet packet type,注册了自己的packet_type{}。在br_handler_frame的STP处理只是分流一下不该处理的情况(netif_receive_skb的流程做不到这种分流)。正经的STP处理的方法是在稍后查询ptype_base,找到相应的处理函数。

// net/llc/llc_core.c 
static struct packet_type llc_packet_type __read_mostly = { .type = cpu_to_be16(ETH_P_802_2), .func = llc_rcv, };

反观普通数据流量,普通NIC收到这些数据时应递交到协议栈,即查询ptype_base然后递交。但设备一旦作为网桥端口,就不能这么处理了,可能需要转发的其他端口什么的,所以才要走br_handler_frame及后续函数。我们看看第二个问题,br_handle_frame_finish接下来是怎么处理普通数据流量(或当作普通数据处理的保留多播流量)的 。

6.4 数据帧处理:br_handle_frame_finish

接着br_handle_frame讨论数据帧的处理,这里的数据帧代表非(STP等)控制帧,当然也包括“视为数据帧”的控制帧(例如STP功能关闭的情况下,BPDU就视为普通数据帧处理)。后面就不再罗嗦了,统一称为“数据帧”或“数据流量”。

如果数据来自 br_handle_frame,那么 br_handle_frame_finish被调用的时候端口只能处于两种状态:Learning和Frowarding。STP端口如果处于Learning和Forwarding状态,就需要学习新的源MAC(更新FDB)br_fdb_update(br, p, eth_hdr(skb)->h_source);

如果端口是Learning就说明不是Forwarding,学个MAC就行了,不能继续接收数据。

if (p->state == BR_STATE_LEARNING)
    goto drop;

接下来是链路层广播、多播和单播的处理,这段代码出现两个skb指针:skb2和原来的skb。理解这段代码,只需要时刻明白,skb2代表递交本地host, skb代表需要转发。抓住这个关键即可。

// skb2指针表明,有数据要发往本机的网络接口,即p->br->dev接口。
          skb2 = NULL;
  
  // 如果应用程序要dump本机接口的数据,那么该数据包应往主机发一份,
  // 一个明显的例子就是在用户在运行tcpdump –I br0或类似的程序。
          if (br->dev->flags & IFF_PROMISC)
                  skb2 = skb;
  
          dst = NULL;
  
          if (is_multicast_ether_addr(dest)) {
  //如果该报文是一个L2多播报文(如arp请求),那么它应该转发到
  //该网桥的所有接口。
  //这同样是网桥的一个特点,广播和组播报文要转发到它的所有接口。
                  br->dev->stats.multicast++;
                  skb2 = skb;
          } else if ((dst = __br_fdb_get(br, dest)) && dst->is_local) {
  //__br_fdb_get函数先查MAC-端口映射表,这一步是网桥的关键。
  // 这个报文应从哪个接口转发出去就看它了。
  //如果这个报文应发往本机,那么skb置空。不需要再转发了,
                  skb2 = skb;
                  /* Do not forward the packet since it's local. */
                 skb = NULL;
           }

决定完是不是要转发,是不是要递交到Host,就可以正在的干活了。如果需要转发(skb不为NULL),又在FBI中找到了目的端口,就转发到改端口。否则就flooding。如果需要递交,就调用br_pass_frame_up。

if (skb) {
    if (dst) {
        dst->used = jiffies;
        br_forward(dst->dst, skb, skb2);// 数据转发到FDB查询到的端口
    } else
        br_flood_forward(br, skb, skb2, unicast);// 数据Flood到所有端口
}  

if (skb2)
    return br_pass_frame_up(skb2);// 数据递交到本地Host

... ...

顺便提一下,目前为止skb->dev还么有改变,因为不能确定要交换的skb->dev是哪个,如果是本地递交,就会被替换成网桥设备,如果是转发或者flooding则需要换成对应端口设备,而且skb可能还需要再clone。

6.5 本地递交:br_pass_frame_up

进入br_pass_frame_up的skb是打算经由Bridge设备,输入到本地Host的。数据包从网桥端口设备进入,经过网桥设备,然后再进入协议栈,其实是“两次经过net_device”,一次是端口设备,另一次是网桥设备。现在数据包离开网桥端口进入网桥设备,需要修改skb->dev字段。

indev = skb->dev;
skb->dev = brdev;

skb->dev 起初是网桥端口设备,现在离开网桥端口进入网桥的时候,被替换为网桥设备的net_device。如果设备是TX,或者从一个端口转发的另一个skb->dev也会相应改变。不论数据的流向如何,skb->dev总是指向目前所在的net_device{}。

递交的最后一步是经过NF_BR_LOCAL_IN钩子点,然后是我们熟悉的netif_receive_skb,只不过这次进入该函数的时候skb->dev已经被换成了Bridge设备。这可以理解为进入了Bridge设备的处理。

return NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN, skb, indev, NULL,netif_receive_skb);

Bridge Local In的数据被修改skb->dev后再次进入netif_receive_skb,原来那个netif_receive_skb因为rx_handler返回CONSUMED而结束。

6.6 数据转发到端口:br_forward

我们再看看 br_handle_frame_finish的另一个支流,转发支流,首先是转发到单个端口的情况,出现这种精确的转发,意味着FDB里面有目的MAC对应的条目,找到了目的端口。直接转发的某个端口通过函数br_forward。

转发前需要做几个检查,必须同时满足以下条件:

a.不能转发给自己 (ingress/egress端口 不能相同)除非目的端口设置了HAIRPIN模式。

b.如果出口端口的状态不是Forwarding,则不能转发出去。如果一个网桥没有启用STP功能,并且网络接口的状态为UP,那么它网桥端口的状态为Forwarding。如果启用STP,每个端口都有一个严格的状态,规定那些端口在什么情况下才能成为Forwarding状态,否则容易造成环路,产生网络风暴。

以上检查由should_deliver()完成。

接着调用函数_br_forward(),_br_forward函数也没干什么,就是调用了br_forward_finish()函数。Br_forward_finish()函数调用了br_dev_queue_push_xmit()函数。

int br_dev_queue_push_xmit(struct sk_buff *skb)
  {
          /* drop mtu oversized packets except gso */
          if (packet_length(skb) > skb->dev->mtu && !skb_is_gso(skb))
                  kfree_skb(skb);
          else {
                  /* ip_refrag calls ip_fragment, doesn't copy the MAC header. */
                  if (nf_bridge_maybe_copy_header(skb))
                          kfree_skb(skb);
                  else {
                          skb_push(skb, ETH_HLEN);
  
                          dev_queue_xmit(skb);
                  }
          }
  
          return 0;
 }

该函数主要完成如下工作:

  1. 做些必要的检查工作。例如,报文的长度比出口端口的MTU还大,则丢掉该报文。
  2. 网桥在处理数据包里,只需拆包来获得目标MAC地址,而不需要 更改数据包的任何内容。但在入口网卡的驱动中已将以太网头部 剥掉,现在需要将它套上。Skb_push函数实现这一功能。
  3. 放到网卡输出队列里,该网卡驱动将它送出去。

6.7 Flooding到各个端口:br_flood_forwards

br_flood_forwards只是函数br_flood的包裹函数。br_flood()遍历每个网桥端口,如果可以的话(满足刚刚说过的should_deliver的要求),就用__packet_hook( __br_forward())转发之。不过函数实现的时候用了一个小技巧,判断为能不能转发后先不急着转发,而是看看下一个端口,如果下一个端口也需要转发,才把数据转发到上次那个要转发到端口。这么做的原因也是减少一次clone。如果没有后续可以转发的端口,就不需要clone了。

struct net_bridge_port *p;
         struct net_bridge_port *prev;
 
         prev = NULL;
 
         list_for_each_entry_rcu(p, &br->port_list, list) {
                 if (should_deliver(p, skb)) {
                         if (prev != NULL) {
                                 struct sk_buff *skb2;
 
                                 if ((skb2 = skb_clone(skb, GFP_ATOMIC)) == NULL) {
                                         br->dev->stats.tx_dropped++;
                                         kfree_skb(skb);
                                         return;
                                 }
 
                                 __packet_hook(prev, skb2);
                         }
 
                         prev = p;
                 }
         }
 
         if (prev != NULL) {
                 __packet_hook(prev, skb);
                 return;
         }

此外br_flood也会使用__br_forward最终转发数据帧,和br_forward一样。

6.8 网桥数据流小节

两次经过net_device{}小节,再谈谈skb经过两次net_device{}这事。 输入路径经过两次net_device{}分别是网桥端口的和网桥设备的,也就是两次调用netif_receive_skb。 和输入路径一样,输出的帧同样会经过两次net_device,即先网桥设备后网桥端口,对输出而言的函数是两次调用dev_queue_xmit; 如果将这个概念扩展,其实对于转发(forward)的数据帧也是两次经过net_device,两次都是网桥端口的net_device{},函数的话,一次是netif_receive_skb,一次是dev_queue_xmit)。

7. 转发数据库

转发数据库用于记录MAC地址端口映射。网桥通过地址学习,将学习到的MAC地址和相应端口加入该数据库;网桥端口本身的MAC会被永久的加入到FDB中(br_add_if());用户还可以配置静态的映射。FDB和是否打开STP无关,只不过打开STP后,只有Learning/Forwardnig才会学习。

记录下的MAC地址(数据库条目)会被更新,并且有老化时间(默认是300秒,也就是5min),如果使用旧STP算法,拓扑变化的时候该老化时间被设置成15秒,如果使用RSTP,FDB中,某端口相关所有条目会被清除。虽然之前已经介绍过net_device_fdb_entry{},我们还是罗列一下.

struct net_bridge_fdb_entry {
	struct hlist_node hlist; // FDB的各个Entry使用哈希表组织,这个是bucket冲突元素链表节点 ;
	struct net_bridge_port *dst; // 条目对应的网桥端口(没有直接使用端口ID);
	struct rcu_head         rcu;
	unsigned long           updated;  // 最后一次更新的时间,会与Bridge的老化定时器比较。
	unsigned long           used;
	mac_addr                addr;      // 条目对应的MAC地址
	unsigned char           is_local;  // 是否是本地端口的MAC
	unsigned char           is_static; // 是否是静态配置的MAC
}

这里重申一下FDB是网桥的属性,因此保存在net_bridge{}中,保存的方式是一个Hash表。

struct net_bridge { ... ... struct hlist_head hash[BR_HASH_SIZE]; ... ... };

FDB条目的添加、删除,查询,更新操作本身想必不会太复杂,无非是哈希表链表操作。关键是搞弄清楚FDB访问和修改的场景。

7.1 FDB初始化,查找

FDB的初始化非常简单,为net_bridge_fdb_entry{}结构初始化一个cache以便快速分配条目。另外还以随机值生成一个salt,这个salt在hash的时候使用,引入随机值可以分散各个Hash键,并且防止DoS攻击。

int __init br_fdb_init(void)
  {
          br_fdb_cache = kmem_cache_create("bridge_fdb_cache",
                                           sizeof(struct net_bridge_fdb_entry),
                                           0,
                                           SLAB_HWCACHE_ALIGN, NULL);

          if (!br_fdb_cache)
                  return -ENOMEM;
  
          get_random_bytes(&fdb_salt, sizeof(fdb_salt));
          return 0;
  }

7.2 地址老化

我们知道网桥学到地址都有一个老化的过程。网桥维护了几个超期时间值,包括老化时间br->ageing_time,默认300秒;和转发延迟br->foward_delay,默认15秒。FDB中的每个地址如果自上次跟新(记录于net_bridge_fdb_entry->updated)以来,流逝的时间超过了“保持时间”(由hold_time(),返回可能是老化时间或者短老化时间),地址就需要被删除。hold_time()在正常情况下返回老化时间br->ageing_time,但是如果检测到了拓扑变化,这将老化时间缩短为br->forward_delay,后者也称为“短老化定时器(short aging timer)”。

7.2.1 注册、打开垃圾收集定时器

网桥在什么时候检查FDB中的各个地址是否老化、并将老化的地址从FDB中移除呢?Kernel将这个工作交由“垃圾收集定时器”来完成。gc_timer保存在net_bridge{}中。

struct net_bridge { ... ... struct timer_list gc_timer; ... ... };

网桥设备被创建并初始化的时候,具体说来是br_dev_setup的时候,通过br_stp_timer_init初始化STP相关的几个定时器,其中包括了垃圾收集定时器。

br_add_bridge + |- alloc_netdev + |- br_dev_setup + |- br_stp_timer_init + |- ... HELLO Timer ... |- ... TCN Timer ... |- ... Topology Change Timer ... - setup_timer(&br->gc_timer, br_fdb_cleanup, (unsigned long) br);

setup_timer函数将timer->function和timer->data设置为:br_fdb_cleanup和net_bridge{}。要注意的是,不论STP协议是否运行,地址老化(垃圾收集)都是必要的。这里只是设置各个timer的回调函数和私有数据。并没有启动Timer。

在网桥设备打开的时候,br_stp_enable_bridge会把各个timer打开,包括gc_timer,

br_dev_open + |- br_stp_enable_bridge + |- ... |- mod_timer(&br->gc_timer, jiffies + HZ/10); // gc_timer第一次启动的地方 |- ...

第一次打开的时候,在1/10秒后br_fdb_cleanup被调用;此后回调函数br_fdb_cleanup将timer自己设置为每br->aging_time或者“最近的一个条目到期时间”调用。这个timer的实现是值得学习的,因为它不是完全周期性的timer,而是根据条目中需要检查的时间结合一个最大默认周期来进行。

7.2.2 地址老化处理

我们看看br_fdb_cleanup()是怎么实现的,顺便也提一下hold_time()。

void br_fdb_cleanup(unsigned long _data) { 
	struct net_bridge *br = (struct net_bridge *)_data; 
	unsigned long delay = hold_time(br);// 地址老化时间,MIN {ageing_time, forward_delay} unsigned long next_timer = jiffies + br->ageing_time;// 预设下次收集时间为 ageing_time秒后,稍后可能调整 int i;
	spin_lock(&br->hash_lock);
	for (i = 0; i < BR_HASH_SIZE; i++) {// 遍历所有FDB Hash Bucket
	    struct net_bridge_fdb_entry *f;
	    struct hlist_node *n;
	
	    hlist_for_each_entry_safe(f, n, &br->hash[i], hlist) {  // 遍历所有FDB Hash冲突链表
	        unsigned long this_timer;
	        if (f->is_static)// 静态条目,包括端口地址和用户设置的条目,不会老化、删除。
	            continue;
	        this_timer = f->updated + delay;// 条目老化到期的时间
	        if (time_before_eq(this_timer, jiffies))// 已经到期(到期时间在当前时间之前),就把它删除
	            fdb_delete(br, f); // 这就是清除到期FDB Entry的地方
	        else if (time_before(this_timer, next_timer))
	            next_timer = this_timer;// 如果FDB中的某个条目中默认的下次检查时间之前,就将下次收集时间提前
	    }  
	}  
	spin_unlock(&br->hash_lock);
	
	mod_timer(&br->gc_timer, round_jiffies_up(next_timer));// 设置下次垃圾收集的时间
}

7.3 “本地”FDB条目

网桥设备、网桥端口设备的MAC地址作为“Local”条目添加到FDB表,其is_local和is_static都需要置1,不会老化。这类FDB Entry通过fdb_insert添加,并且在地址改变的时候,需要做相应的更新。

从下图我们发现,并没有添加“网桥设备”MAC FDB的地方,这是因为网桥的MAC因默认情况下是其端口之一的地址,因此无需加入FDB。但是如果网桥端口地址改变时则需要更新。

对于网桥的地址加入,或者不加入FDB对于入口流量的影响,我们应该了解到, 只要帧的目的MAC是网桥或者各个网桥端口的MAC之一,帧就是要被递交到本地Host的。

了解了何时“插入”本地且静态的网桥端口、网桥的地址后,我们看看fdb_insert的实现,

static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source, const unsigned char *addr, u16 vid) { 
	struct hlist_head *head = &br->hash[br_mac_hash(addr, vid)];// FDB是Per-VLAN的,addr和vid都作为Hash键 struct net_bridge_fdb_entry *fdb;
	
	if (!is_valid_ether_addr(addr))// 要插入的地址必须是合法的Ethernet地址
	    return -EINVAL;
	
	fdb = fdb_find(head, addr, vid);// 在某个VLAN中,地址是否已经存在
	if (fdb) { // 地址已经存在?
	    /* it is okay to have multiple ports with same
	     * address, just use the first one.
	     */
	    if (fdb->is_local)  // 并且是Local的
	        return 0;       // 允许多个端口用于同一个地址
	    br_warn(br, "adding interface %s with same address "
	           "as a received packet\n",     
	           source ? source->dev->name : br->dev->name);
	    fdb_delete(br, fdb);// 但如果地址和分本地地址冲突,就需要将非本地地址的条目删除
	} 
	
	fdb = fdb_create(head, source, addr, vid);// 创建新的net_bridge_fdb_entry{},并插入FDB(br->hash)中
	if (!fdb)
	    return -ENOMEM;      
	
	fdb->is_local = fdb->is_static = 1;// “插入”的地址一定是本地且静态的
	fdb_notify(br, fdb, RTM_NEWNEIGH);
	return 0;
}

7.4 地址学习

除了网桥端口和网桥的MAC地址,用户还能手动添加静态(通过netlink套接字),已经网桥字段学习地址的过程,

fdb_create的实现也不难理解,

static struct net_bridge_fdb_entry *fdb_create(struct hlist_head *head, struct net_bridge_port *source, const unsigned char *addr,
__u16 vid)
{ 	struct net_bridge_fdb_entry *fdb;
	fdb = kmem_cache_alloc(br_fdb_cache, GFP_ATOMIC);
	if (fdb) {
	    memcpy(fdb->addr.addr, addr, ETH_ALEN);
	    fdb->dst = source;   
	    fdb->vlan_id = vid;  
	    fdb->is_local = 0;   
	    fdb->is_static = 0;
	    fdb->updated = fdb->used = jiffies;
	    hlist_add_head_rcu(&fdb->hlist, head);
	}
	return fdb;
}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/55605.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mysql—MHA原理与实现

官方介绍&#xff1a;https://code.google.com/p/mysql-master-ha/ MySQL复制集群中的master故障时&#xff0c;MHA按如下步骤进行故障转移&#xff1a; 从上图可总结MHA工作步骤为&#xff1a; -从宕机崩溃的master保存二进制日志事件(binlogevents)。 -识别含有最新更新的s…

macOS Electron 环境安装时的错误 Cannot find module ‘macos-alias‘ 解决

macOS Electron 环境安装时的错误 Cannot find module ‘macos-alias’ 解决 一、问题描述 在想 make 并发布软件版本的时候&#xff0c;现出这个错误&#xff1a; ❯ Making distributables✖ Making a dmg distributable for darwin/arm64› Cannot find module macos-ali…

SSM处理过程

SSM框架是spring MVC &#xff0c;spring和mybatis框架的整合&#xff0c;是标准的MVC模式&#xff0c;将整个系统划分为表现层&#xff0c;controller层&#xff0c;service层&#xff0c;DAO层四层 使用spring MVC负责请求的转发和视图管理 spring实现业务对象管理&#xf…

DSP篇--C6678功能调试系列之TIMER、UART调试

目录 1、TIMER计时器调试 2、UART串口调试 2.1 核传输 2.2 EDMA传输 1、TIMER计时器调试 The TMS320C6678 device has sixteen 64-bit timers in total. Timer0 through Timer7 are dedicated to each of the eight CorePacs as a watchdog timer and can also be used as g…

黄东旭:开发者的“技术无感化”时代,从 Serverless HTAP 数据库开始 | PingCAP DevCon 2022

12 月 1 日&#xff0c;以"去发现&#xff0c;去挑战"为主题的 PingCAP DevCon 2022 主论坛在线上成功举办&#xff0c;为数万观众带来一场技术盛宴。PingCAP 联合创始人兼 CTO 黄东旭&#xff0c;在大会上分享了“The Future of Database”的主题演讲&#xff0c;分…

Unity记录几个5.x升级到2018问题

XLua的 ILType Emit等C#框架问题 本来是Unity5.x&#xff0c;貌似是最旧的代码 。Net 2.0 无奈升级用了Unity2018之后&#xff0c;只支持4.x&#xff0c; 在PlayerSetting面板&#xff0c;改成.Net3.5&#xff0c;居然阔以了 (Unity2018.7 - 支持的最低&#xff0c;.Net 3.x…

使用 Learner Lab - 学生

使用 Learner Lab - 学生 AWS Academy Learner Lab 是提供一个帐号让学生可以自行使用 AWS 的服务&#xff0c;让学生可以在 100 USD的金额下&#xff0c;自行练习所要使用的 AWS 服务&#xff0c;AWS Academy 学习平台建立 Learner Lab &#xff0d; 教师 这篇文章介绍老师如…

【JVM】 类加载器 ClassLoader

一、JVM 类加载器 JVM 具有 4 种类加载器&#xff1a; 引导类加载器 &#xff1a; 负责加载支持 JVM 运行的位于 JRE 的 lib 目录下的核心类库&#xff0c;比如 rt.jar 、charsets.jar 等等扩展类加载器&#xff1a;负责加载支撑 JVM 运行的位于 JRE 的 lib 目录下的 ext 扩展…

java计算机毕业设计ssm宁夏源沣医药线上销售平台thd3v(附源码、数据库)

java计算机毕业设计ssm宁夏源沣医药线上销售平台thd3v&#xff08;附源码、数据库&#xff09; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&a…

戳进来,带你走近飞凌嵌入式旗舰级AIoT芯RK3588开发板

飞凌嵌入式OK3588-C开发板现已正式发售&#xff01;真8K、强大算力、出色的多媒体性能、丰富的用户资料......多重优势为您带来更优质的体验&#xff01; OK3588-C开发板基于Rockchip旗舰级AIoT处理器RK3588设计开发&#xff0c;先进的8nm制程工艺、Big.Little大小核架构以及L3…

[附源码]计算机毕业设计springboot学分制环境下本科生学业预警帮扶系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

基于PHP+MySQL班级信息发布和管理系统的设计与实现

一直以来我国领导人提倡以人为本的治国方案&#xff0c;而大学是未来人才的培养基地&#xff0c;如何能够更好的对学生信息进行管理&#xff0c;是很多高校一直在研究的一个问题&#xff0c;只有更加科学的对学生信息进行管理&#xff0c;才能够更加积极的培养国家的栋梁之才。…

VIAVI唯亚威SmartPocket V2 OLS-35V2/-36V2 光学光功率计

OLS-3xV2 是一系列小巧、坚固耐用的光学光源 (OLS)&#xff0c;用于快速、轻松、方便地进行现场插入损耗测量和连续性检查。SmartPocket V2 OLS 是与 OLP-3xV2 光功率计相辅相成的理想工具&#xff0c;可在单模 (SM) 和多模 (MM) 光纤网络中实现插入损耗和连续性测试。坚固小巧…

[附源码]计算机毕业设计springboot兴达五金日杂批发商店管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Linux 磁盘空间异常爆满,该怎么查?

在服务器运维过程中&#xff0c;我们时常会遇到这样的情况&#xff0c;收到服务器磁盘空间告警&#xff1a; 登录服务器&#xff0c;通过df -Hl查看 和告警信息一致&#xff0c;接着我们就是要找到导致磁盘空间满的目录或文件。 如何找到占用空间大的目录或文件&#xff1f; …

【Python基础系列】Part1. 基本数据类型与变量

一、变量与简单数据类型 1.Hello World hello_world.py中输入&#xff1a; print("Hello World")运行&#xff0c;然后会看到一下输出&#xff1a; Hello World具体流程&#xff1a;运行hello_world.py时&#xff0c;末尾的.py表明这是一个python程序&#xff0c…

如何选择正确的哈希算法?

程序员宝藏库&#xff1a;https://gitee.com/sharetech_lee/CS-Books-Store 你想要的&#xff0c;这里都有&#xff01; DevWeekly收集整理每周优质开发者内容&#xff0c;包括开源项目、资源工具、技术文章等方面。 每周五定期发布&#xff0c;同步更新到知乎&#xff1a;Jac…

RabbitMQ--延迟队列--使用/原理

原文网址&#xff1a;RabbitMQ--延迟队列--使用/原理_IT利刃出鞘的博客-CSDN博客 简介 本文介绍RabbitMQ的延迟队列的用法。 本内容也是Java后端面试中常见的问题。 概述 延迟队列用来存放延迟消息。延迟消息&#xff1a;指当消息被发送以后&#xff0c;不想让消费者立刻拿到…

业务:财会业务知识之借贷记账法

一、为什么要学习借贷记账法 如果你是一位金融行业的IT从业者&#xff0c;那你经常会接触到借贷的概念&#xff0c;只要复式记账的概念&#xff0c;其实也够用了&#xff0c;在记录交易流水时&#xff0c;跟业务问清楚哪些交易记录哪个借贷符号&#xff0c;记录哪些科目既可以…