谈谈NIC的多播接收

最近尝试了dpdkkni驱动,使得非高速转发路径的数据能利用Kernel协议栈,这确实方便了非面向性能的应用。不过在kni虚拟接口上运行多播应用的时候的时候发现,根本不工作?

谈谈NIC的多播接收

接收多播对于一个网卡来说是必须的,然而初始情况下Ethernet网卡只接收两类数据帧:

  • 目的MAC是网卡物理地址的帧;
  • 以及以太网广播帧。

除非,

  • 打开了混杂模式,那么所有见到的帧都会接收(例如用tcpdump时);
  • 或者,明确指示它接收某个以太网多播(或某组多播或其他Unicast)。

这也是为什么多播的效率好于广播的原因,本机不关心的多播在硬件层就ignore,不必麻烦协议栈。由于应用程序或协议(IGMP/MLDIPv6 SLAAC/NDOSPF等)本身的需求,要加入一个多播组的时候,例如224.0.0.1,将其转化为以太网多播地址(01:00:5e:00:00:01)然后告知网卡要接受这个多播帧。

网卡一般会有一个表维护要加入哪些多播,因为资源的限制,不可能做到为每个以太网多播维护一个很大的表,会采用哈希的形式多对一映射,或者限制多播表的大小。

怎么kni设备收不到多播?

回到kni的情况,网卡本身显然是支持多播的,以防万一,查了下DPDK的NIC驱动也是支持的;那么只能怀疑kni虚拟设备的驱动了,果然,kni驱动“实现了”设置多播的函数ndo_set_rx_mode(),只不过是个空函数而已。

ndo_xxx系列函数(包括ndo_set_rx_mode)是Kernel net_device层和具体设备驱动的一个Callback(虚函数),实现ndo_set_rx_mode目的是控制网卡的接收模式。换句话说就是设置NIC地址过滤表,除了自己地址和广播外,额外接收哪些硬件地址,比如,

  • Secondary Unicast(自己硬件地址之外的地址)
  • Promiscuous Mode (全都给收上来吧)
  • Multicast Mode (告诉我哪些多播要收,还是Multicast全收)

kni是虚拟设备,转交PMD设备的包,不关心是不是多播,本来是“没必要”实现这个逻辑的。不过既然KNI是建立在实际PMD设备之上的虚拟Linux设备,Linux只能操作到kni驱动,需要把DPDK PMD控制下的物理网卡的多播打开才行。

Double check下最新的Release,没有填坑的迹象。

那么只能自己动手了

网卡硬件是OK的,PDM驱动也是支持多播的(虽然只是提供了个API让用户自己设置)。那么目标很明确,通过kni驱动把需要加入的MAC多播告知实际控制网卡的PMD驱动即可。分成几个步骤,

  • 从Kernel取出MAC多播列表
  • 通过某种方式从kni驱动通知到PMD驱动
  • 通过PMD驱动提供的API将多播列表设置到实际网卡中。

Kernel把多播列表存在哪?

去设置多播的地方找找(除非你知道它们就在net_device->mc里面),

ip_mc_join_group()
    |- ip_mc_find_dev
    |- ip_mc_inc_group
          |- IPv4多播被加入了dev.in_device.mc_hash/.mc_list
          |- igmp_group_added
                |- ip_mc_filter_add
                     |- dev_mc_add
                           |- __hw_addr_add_ex
                           |     分配netdev_hw_addr加入dev->mc列表
                           |- __dev_set_rx_mode
                                |- dev.ops.ndo_set_rx_mode
                                   设置具体驱动的RX Mode

硬件多播被作为netdev_hw_addr保存在了dev->mc列表之中,并通过之前提到的ndo_set_rx_mode传递给具体的驱动。“Helper”宏netdev_for_each_mc_addr可以用来遍历该链表。

kni怎么通知PMD

DPDK的PMD是用户态驱动,kni驱动是内核态的,他们之间的通过内存映射实现的FIFO来通信,包括数据包和控制消息。具体可以参考kni的文档和代码。

kniPMD控制通道是sync FIFO,对应了kni_dev.sync_kva/va。具体说来是用了上面的那段内存来传输rte_kni_request结构的,Kernel可以使用API kni_net_process_request发送请求到用户态;用户态由rte_kni_handle_request处理请求,返回处理结果。

但是Requset结构目前只支持通知 MTU改变和link状态改变;为了传递多播列表需要自定义一个消息类型以及保存多播的结构放到Requset结构。

考虑到毕竟MTU和Link UP/Down这种消息相对一个多播列表而言数据量很小,担心一个多播列表保存Request超过kni_dev.sync_kva/va的大小(毕竟表面上看不出它到底多大呀!)。追查了分配这个buffer的地方:rte_kni_init()/memzone "kni_sync_%d",差不多有64K大,放心了。

利用sync FIFO是不是好?不好说,只是使用别的kernel/user-space通信方式,也有点繁琐。

定义数据结构,从net_dev->mc提取MAC多播,用Request传递给PMD,记得处理Response。

PMD驱动多播列表设置到实际网卡中

这个简单,在rte_kni_handle_request接收到Kernel的消息直接调用API即可。

最后还有一点要注意,ndo_set_rx_mode是在原子(spinlock)上下文,而kni_net_process_request调用了mutex,原子上下文不能使用mutex这种会引发上下文切换的函数。所以在ndo_set_rx_mode里将多播列表暂时保存起来然后利用knikthread线程去检查并发送请求,后者访问dev->mc数据的时候记得上锁(netdev_addr_lock_bh)并一次性取出。

多播在kni虚拟接口上正常接收,可以,这很DIY

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值