bonding原理
发送端:
使用网卡bond3模式(广播模式BOND_MODE_BROADCAST)将报文从两个网卡同时发出,无需修改报文。
接收端:
根据发送节点时间的链路通断状态,接收端设置一条线路为活动线,另一条为备份线,并将从活动线收到的报文上送到协议栈,备份线的报文被丢弃。
需要考虑的问题
1)网关MAC地址可配置 当发送端节点和接收端节点不在同一网段,需通过网关转发时,需确认网关MAC地址是否可配置,若网关MAC地址可配置,即两个网关可配置相同IP和MAC地址,构成两个完全对称的网络,报文无需进行特殊处理。
2)网关MAC地址不可配置 若网关MAC地址不可修改,则三层交换机虽配置了相同的IP地址,但对应的MAC地址却各不相同,由于ARP学习过程中,发送端节点只能学习到其中一个网关地址的MAC地址,例SW1的MAC地址,则K1从两个网络发出报文的目的MAC地址只是其中某个网关的MAC地址(如SW1的MAC地址),而永远无法到达另个网关(如SW2)。因此需要设计网关MAC地址自动学习模块,将报文的目的MAC地址替换为对应网络的网关MAC地址,才能顺利让报文通过两个网络到达对端。
数据发送时网关MAC地址自动修正:Bond驱动将数据通过物理网卡发送到网络上时,通过比较报文中的目的MAC地址是否与网关MAC地址表中的某一项网关MAC地址一致,来判断报文是否是发往网关。如果报文是发往网关的,则根据网关MAC地址表来修正报文中的目的MAC地址。物理网卡A发送的报文,将用物理网卡A对应的网关MAC地址来修正报文中的目的MAC地址。
bonding核心代码实现 发送端
在bond_xmit_broadcast函数中通过bond_for_each_slave_rcu(bond, slave, iter)遍历所有bond的网卡并调用bond_dev_queue_xmit通过该网卡进行报文发送。
bond各个模式的含义:
BOND_MODE_ROUNDROBIN(循环调度):数据包按照循环顺序分发到不同的网络接口上,实现负载均衡。
BOND_MODE_ACTIVEBACKUP(主备模式):只有一个网络接口处于活动状态,其他接口作为备份。当活动接口故障时,备份接口会接管数据传输。
BOND_MODE_XOR(异或模式):使用异或操作将源和目的 MAC 地址的一部分与网络接口数量进行运算,将数据包分发到不同的接口。
BOND_MODE_BROADCAST(广播模式):将数据包广播到所有网络接口,实现广播传输。
BOND_MODE_8023AD(IEEE 802.3ad 动态链接聚合):使用 IEEE 802.3ad 协议实现链路聚合,将多个网络接口绑定成一个逻辑接口,提供更高带宽和冗余。
BOND_MODE_TLB(传输负载平衡):根据传输流的源和目的 IP 地址,将数据包分发到不同的网络接口,以实现负载均衡。
BOND_MODE_ALB(TLB + RLB 组合模式):结合传输负载平衡(TLB)和接收负载平衡(RLB),实现传输和接收两个方向上的负载平衡。
bonding核心代码实现 接收端
在bond_open中根据bond模式将回调函数设置为bond->recv_probe = bond_link_probe;
对每个bond的网卡注册__netif_receive_skb_core接收报文时的回调函数bond_handle_frame。例如有两个网卡A和Bbond,当这两个网卡接收到报文时会在进入协议栈之前先进行bond_handle_frame函数的处理。
在__netif_receive_skb_core函数中最开始会经过trace_netif_receive_skb即trace追踪报文,如何经过list_for_each_entry_rcu(ptype, &ptype_all, list)即tcpdump抓包点,然后再经过rx_handler回调函数,并根据其返回值进行后续处理,最后经过deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,&ptype_base[ntohs(type) &PTYPE_HASH_MASK]);即ip协议栈处理。
其中
RX_HANDLER_CONSUMED: 表示当前处理函数已经处理完此数据包,不需要其他处理函数再处理。
RX_HANDLER_ANOTHER: 表示当前处理函数已经处理了部分数据包内容,但仍需要其他函数处理剩余内容。
RX_HANDLER_EXACT: 表示当前处理函数与数据包匹配程度很高, strongly suggests给下一个函数。但它还可以继续处理。
RX_HANDLER_PASS: 表示当前处理函数与数据包不匹配,直接跳过该处理函数,传给下一个函数处理。
bond_handle_frame函数即为bond接收报文的核心处理函数。以BOND_MODE_BROADCAST模式为例,recv_probe回调函数为bond_link_probe。其中skb->dev = bond->dev;表示通过该源ip进行报文响应时由于skb->dev改为了bond->dev,所以发送报文xmit的回调函数也改为了bond->dev的发送报文回调函数。
bond_active_link_filter函数的含义为,为每个源ip报文创建一个struct link_state_entry表项。初始时由于memset(entry, 0, sizeof(*entry));所以最开始entry->cur_index为0,其0号对应的网卡为entry->state_tlb[0].line = slave;。然后每来一个报文更新一下网卡对应state_tlb的状态。即entry->state_tlb数组的每个元素代表每个网卡。在bond_entry_update_state函数中总共有两个for循环两个if判断,当if判断满足时就会return退出,所以只会更新该网卡对应state_tlb的信息。例如最开始网卡A先kmalloc申请struct link_state_entry,即entry->state_tlb[0].line为网卡A且entry->cur_index为0;然后网卡B再收到同样的报文,经过bond_entry_update_state函数更新状态,即entry->state_tlb[1].line为网卡B。
最后在if( entry->state_tlb[entry->cur_index].line != slave )判断中只有网卡A的报文会被上送到协议栈,网卡B的报文由于返回值为RX_HANDLER_CONSUMED会被丢弃。
bond_link_state_reselect_one函数即为链路切换流程,通过if(time_after(tmp->last_active,best->last_active +msecs_to_jiffies(bond->params.cycle_time * bond->params.switch_cycle)))判断,当遍历到的网卡tmp->last_active时间小于阈值时间时if( tmp->state_count++ >= bond->params.delay_cycle )即代表网卡tmp连续state_count次正常收到报文,即将活动网卡即active切换到该网卡即best_index = i;最后再entry->cur_index = best_index;。
回到bond_active_link_filter函数的if( entry->state_tlb[entry->cur_index].line != slave )判断中,由于entry->cur_index值发生了改变,进而使活丢弃报文的网卡发生改变。例如原来为网卡A上送报文到协议栈网卡B丢弃报文,切换后变为网卡B上送报文到协议栈网卡A丢弃报文。
问题排查
服务端使用bond进行tcp通信,服务端connect连接不成功,但是可以互相ping通。通过tcpdump抓包,用测试程序进行测试发现服务端bond绑定的两物理个网卡都能收到客户端connect系统调用发送报文,但是服务端bond创建的虚拟网卡没有收到该报文,即服务端将两个物理网卡接收到的connect连接报文都丢弃了没有进一步上送到协议栈中。
但是用ssh连接时也用的tcp协议,两个物理网卡都能收到报文,并能正确将其中一个网卡的报文上送给协议栈将另一个网卡的报文丢弃。
ICMP_ECHOREPLY (类型 0): 回显应答。用于回应ICMP ECHO请求消息,通常用于网络连通性测试。
ICMP_DEST_UNREACH (类型 3): 目的地不可达。用于通知发送方无法到达目标主机或网络。
ICMP_SOURCE_QUENCH (类型 4): 源抑制。用于通知发送方降低发送速率,以防止网络拥塞。
ICMP_REDIRECT (类型 5): 重定向。用于通知发送方更改路由以优化数据包传输。
ICMP_ECHO (类型 8): 回显请求。用于发送方向目标主机发送一个回显请求,以测试网络延迟和可达性。
ICMP_TIME_EXCEEDED (类型 11): 超时。用于通知数据包在传输过程中超过了允许的生存时间。
ICMP_PARAMETERPROB (类型 12): 参数问题。用于通知发送方数据包中存在错误或不支持的参数。
ICMP_TIMESTAMP (类型 13): 时间戳请求。用于发送方向目标主机请求时间戳信息。
ICMP_TIMESTAMPREPLY (类型 14): 时间戳应答。用于目标主机回应时间戳请求。
ICMP_INFO_REQUEST (类型 15): 信息请求。用于发送方向目标主机请求附加信息。
ICMP_INFO_REPLY (类型 16): 信息应答。用于目标主机回应信息请求。
ICMP_ADDRESS (类型 17): 地址掩码请求。用于发送方向目标主机请求子网掩码信息。
ICMP_ADDRESSREPLY (类型 18): 地址掩码应答。用于目标主机回应子网掩码请求
通过上述信息可知道,主要是开了nft防火墙限制了tcp服务端绑定的端口。可通过systemctl stop firewalld.service停止该服务,或者firewall-cmd --add-port=10000/tcp添加白名单端口。即可进行tcp通信。
从上述信息可以看到tcpdump抓包可以抓到bond的两个网卡,网卡A和网卡B都能抓到请求和响应icmp报文,但是bond3网卡只能抓到响应icmp报文。原理即为两个bond的网卡都能收到对端发送的icmp请求报文,但是bond3的响应报文tcpdump抓包点都能抓到包。
dev_queue_xmit_nit即为发送报文的抓包点,netdev_start_xmit最开始为bond3的发送报文是会被抓包回调函数即bond_start_xmit然后再通过两个物理网卡发送出去,即实际物理网卡发送报文时会再次被tcpdump抓包。