linux 虚拟接口驱动介绍
本文主要从Linux内核驱动层面介绍不同linux 接口类型的底层代码逻辑
common info
再内核 driver/net/ 目录下面有不同的内核网络虚拟设备的驱动. ip link help.
TYPE := { vlan | veth | vcan | vxcan | dummy | ifb | macvlan | macvtap | bridge | bond | team | ipoib | ip6tnl | ipip | sit | vxlan | gre | gretap | erspan | ip6gre | ip6gretap | ip6erspan | vti | nlmon | team_slave | bond_slave | bridge_slave | ipvlan | ipvtap | geneve | vrf | macsec | netdevsim | rmnet | xfrm }
register_pernet_subsys
该函数的主要作用是将一个网络协议子系统添加到网络命令空间对应的全局链表pernet_list中,并针对每一个注册在net_namespace_list链表中的网络命令空间,均执行其ops->init程序进行初始化,一般其ops->init会在其对应的proc目录下,生成一个网络协议模块对应的proc文件或proc目录,并执行一些协议初始化相关的函数
register_netdevice_notifier
功能: 在内核通知链netdev_chain上注册消息块,用来接收有关网络设备的注册状态等信息
rtnl_link_register
int rtnl_link_register(struct rtnl_link_ops *ops) rtnl_link_register 函数与 rtnl_link_unregister 函数都涉及到了对 link_ops 的操作。 rtnl_link_ops 通过 struct net_device 中的 rtnl_link_ops 指针与一个 netdev 关联起来,注册一个 rtnl_link_ops 并不涉及与 netdev 的关联,只需要在 link_ops 链表中添加一个节点就行,而当删除一个 rtnl_link_ops 时,就需要对 netdev 中使用到待删除的 rtnl_link_ops 的网络设备进行相应处理。
这里也说明其实 rtnl_link_ops 类似于一个框架性的功能,netdev 是它的客户
net device
驱动接口 struct net_device_ops veth_netdev_ops struct ethtool_ops veth_ethtool_ops
vlan interface
实现在目录net/802.1q 下 ,主要梳理下vlan 端口创建以及报文收发的处理
rtnl_link_ops vlan_link_ops —》vlan_setup—》 dev->netdev_ops = &vlan_netdev_ops;
1:发送报文 vlan_dev_hard_start_xmit 查看原始报文里面的tag是否和端口vlan id一致或者有无tag 来添加tag, 然后交给 vlan依赖的那个真实设备skb->dev = vlan->real_dev; 然后发送ret = dev_queue_xmit(skb);
2:接收报文的过程中net receive rx 接口处理的时候判断报文的vlan tag ,带tag的从real-dev 去掉tag给对应端口的协议栈处理
veth interface
注册: rtnl_link_ops veth_link_ops–>veth_setup–> dev->netdev_ops = &veth_netdev_ops;
1: 发送接收报文 .ndo_start_xmit = veth_xmit, 报文到达dev后,获得peer设备 rcv = rcu_dereference(priv->peer); 然后让peer设备直接发送到协议栈去 if (likely(veth_forward_skb(rcv, skb, rq, rcv_xdp) == NET_RX_SUCCESS)) -->netif_rx
loopback interface
其他虚拟网络设备大部分需要上层命令去调用,然后去创建,而loopback端口内核直接创建好了一个,也不能通过ip link手动去创建,系统默认创建一个loopback端口 lo dev = alloc_netdev(0, “lo”, NET_NAME_UNKNOWN, loopback_setup); loopback_setup–> dev->ethtool_ops = eth_ops; dev->header_ops = hdr_ops; dev->netdev_ops = dev_ops;
1:发送报文 .ndo_start_xmit = loopback_xmit, 查看loopback_xmit 代码可以看到 if (likely(netif_rx(skb) == NET_RX_SUCCESS)) 直接把报文发送到了协议栈处理。
vxlan interface
注册流程 : rtnl_link_register(&vxlan_link_ops); --》vxlan_newlink–》__vxlan_dev_create–》vxlan_dev_configure–》vxlan_config_apply–》struct net_device_ops vxlan_netdev_ether_ops
1:发送报文 vxlan_xmit 解析报文,获取vxlan 隧道信息,根据目的ip 查看对应mac地址,如果找不到发送arp请求,vxlan 接口收到arp报文后,封装arp报文为vxlan报文,外部smac为vtep接口的mac, dmac 为到隧道vtep 吓一跳的mac addr,sip为本vtep的ip , dsip为 对端vtep的ip addr
2: 接收报文 在注册vxlan设备设备的时候,会调用 vxlan_open 里面会通过vxlan_sock_add 进行vxlan接收逻辑的处理,并且如何用户设置了vxlan使用组播的方法建立隧道,发起igmp join建立组播连接。 __vxlan_sock_add–》vxlan_socket_create --》tunnel_cfg.encap_rcv = vxlan_rcv;–》setup_udp_tunnel_sock(net, sock, &tunnel_cfg); 函数调用逻辑参考 https://blog.csdn.net/u013743253/article/details/119734633 /* Callback from net/ipv4/udp.c to receive packets */ static int vxlan_rcv(struct sock *sk, struct sk_buff *skb) 主要逻辑为查找到skb对应的vxlan设备,解包后的原始报文送到对应的出口,做为vxlan网关修改smac dmac outdip等发送到vxlan 隧道对端的vtep.
vrf interface
vrf 引入一种主从设备的概念,创建一个vrf设备做为master, 把从属设备做为slave, 主要逻辑在l3mdev vrf introduce: https://www.kernel.org/doc/html/latest/networking/vrf.html vrf driver init process vrf_init_module–>vrf_net_ops, vrf_link_ops–>vrf_setup–> dev->netdev_ops = &vrf_netdev_ops; dev->l3mdev_ops = &vrf_l3mdev_ops; dev->ethtool_ops = &vrf_ethtool_ops;
1:接收报文 ,netif_receive_skb,ip_rcv等调用,直到ip_rcv_finish被调用之前,VRF的逻辑和非VRF逻辑并没有任何不同支持,在ip_rcv_finish中 skb = l3mdev_ip_rcv(skb); l3mdev_ip_rcv会走入 vrf_l3mdev_ops 里面的vrf_l3_rcv,主要逻辑为找到对应slave网卡的master设备,修改skb->dev, 然后继续后面报文的处理逻辑,在查找路由表阶段 arg->table = dev->l3mdev_ops->l3mdev_fib_table(dev); 在对应的路由表里面查找
2:发送报文,ip_queue_xmit–>__ip_route_output_key_hash调用中的路由查找失败而负责,为其暂时绑定一个dummy dst_entry,从而逻辑可以到达__ip_local_out -->l3mdev_ip_out -->vrf_ip_out–>很简单,在这里仅仅是将skb的那个dummy dst_entry换成了和VRF设备绑定的那个dst_entry -->dev_hard_xmit–>.ndo_start_xmit = vrf_xmit,–>vrf_process_v4_outbound --> rt = ip_route_output_flow(net, &fl4, NULL); ret = vrf_ip_local_out(dev_net(skb_dst(skb)->dev), skb->sk, skb); --> err = nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, net, sk, skb, NULL, skb_dst(skb)->dev, dst_output);
bond interface
在bond_main.c文件中进行bond驱动的注册,bonding_init–》bond_net_ops bond_netlink_init bond_link_ops–>bond_setup–>bond_dev->netdev_ops = &bond_netdev_ops
在bond接口添加第一个slave接口的时候,bond接口的mac地址copy slave设备的mac地址,后面收发包都根据bond接口的逻辑来处理。 1: receive frame, 在添加slave设备的时候会注册dev的 rx_handler函数, res = netdev_rx_handler_register(slave_dev, bond_handle_frame, new_slave); 当slave设备接收到报文的时候,调用bond_handle_frame 进入对应的bond master处理逻辑里面。 rx_handler 的返回值 enum rx_handler_result { RX_HANDLER_CONSUMED, //skb was consumed by rx_handler, do not process it further. RX_HANDLER_ANOTHER, //Do another round in receive path. This is indicated in
- case skb->dev was changed by rx_handler
RX_HANDLER_EXACT, //Force exact delivery, no wildcard RX_HANDLER_PASS, //Do nothing, pass the skb as if no rx_handler was called. };
在 bond_handle_frame 中如果报文是发送本机的做RX_HANDLER_CONSUMED 处理,如果不是,修改skb->dev 返回RX_HANDLER_ANOTHER
2:xmit frame:当调用到bond接收的发送接口的时候.ndo_start_xmit = bond_start_xmit, 根据bond接口的模式进行发送 switch (BOND_MODE(bond)) { case BOND_MODE_ROUNDROBIN: return bond_xmit_roundrobin(skb, dev); case BOND_MODE_ACTIVEBACKUP: return bond_xmit_activebackup(skb, dev);
macvlan
macvlan 本身是 linxu kernel 模块,其功能是允许在同一个物理网卡上配置多个 MAC 地址,即多个 interface,每个 interface 可以配置自己的 IP。macvlan 本质上是一种网卡虚拟化技术(最大优点是性能极好) macvlan mode Bridge:属于同一个parent接口的macvlan接口之间挂到同一个bridge上,可以二层互通(macvlan接口都无法与parent 接口互通)。 VPEA(Virtual Ethernet Port Aggregator):所有接口的流量都需要到外部switch才能够到达其他接口。 Private:接口只接受发送给自己MAC地址的报文。 Passthru: 父接口和相应的MacVLAN接口捆绑在一起,这种模式每个父接口只能和一个 Macvlan 虚拟网卡接口进行捆绑,并且 Macvlan 虚拟网卡接口继承父接口的 MAC 地址。
以常用的bridge模式为例 1:接收报文,判断报文的目的mac地址是否为macvlan对应的macaddr,是的转给对应macvlan interface 上层协议栈处理,不是的话,走底层母设备的协议栈。 2:发送报文,此处底层母设备做为bridge用,查找dmac,然后通过母设备发给对应macvlan interface
ipvlan
IPVlan 和 macvlan 类似,都是从一个主机接口虚拟出多个虚拟网络接口。一个重要的区别就是所有的虚拟接口都有相同的 macv 地址,而拥有不同的 ip 地址。因为所有的虚拟接口要共享 mac 地址,所有有些需要注意的地方:
DHCP 协议分配 ip 的时候一般会用 mac 地址作为机器的标识。这个情况下,客户端动态获取 ip 的时候需要配置唯一的 ClientID 字段,并且 DHCP server 也要正确配置使用该字段作为机器标识,而不是使用 mac 地址 ipvlan mode L2:
ipvlan L2 模式和 macvlan bridge 模式工作原理很相似,父接口作为交换机来转发子接口的数据。同一个网络的子接口可以通过父接口来转发数据,而如果想发送到其他网络,报文则会通过父接口的路由转发出去。
L3:
ipvlan 有点像路由器的功能,它在各个虚拟网络和主机网络之间进行不同网络报文的路由转发工作。只要父接口相同,即使虚拟机/容器不在同一个网络,也可以互相 ping 通对方,因为 ipvlan 会在中间做报文的转发工作。
macsec
内核驱动注册流程 macsec_init–》macsec_link_ops–>macsec_setup–>dev->netdev_ops = &macsec_netdev_ops;
1:rx 由于在创建macsec端口的时候注册了 rx_handler macsec_handle_frame, 报文在经过macsec的母设备的__netif_receive_skb_core 的时候调用macsec_handle_frame,判断skb是否是macsec报文,走不同逻辑,非macsec,报文目的地址如果是macsec端口macaddr,修改报文dev,重新走一遍接收流程,非macsec addr,送上层协议栈进行处理。 是macsec报文的话,进行sectag其他字段的检查,有问题的话丢弃报文,没的话解包macsec报文,送上层协议栈处理。
2:tx .ndo_start_xmit = macsec_start_xmit, macsec_start_xmit 判断硬件有没封装macsec的逻辑,有的话交给母设备进行加密发包处理,否则软逻辑去加密用户报文,然后交给母设备进行发送。
tap/tun
TUN/TAP 为用户空间程序提供数据包接收和传输。它可以看作是一个简单的点对点或以太网设备,它不是从物理媒体接收数据包,而是从用户空间程序接收数据包,而不是通过物理媒体发送数据包,而是将它们写入用户空间程序。
为了使用驱动程序,程序必须打开 /dev/net/tun 并发出相应的 ioctl() 以向内核注册网络设备。网络设备将显示为 tunXX 或 tapXX,具体取决于所选的选项。当程序关闭文件描述符时,网络设备和所有对应的路由都会消失。
根据选择的设备类型,用户空间程序必须读/写 IP 数据包(使用 tun)或以太网帧(使用 tap)。使用哪一个取决于 ioctl() 给出的标志。
bridge/ovs
rx rx_handler里面注册 bridge/ovs自己的处理逻辑 tx br_netdev_ops
补充文献
https://blog.xuegaogg.com/posts/497/
|