linux vxlan设备工作原理

vxlan处理包的原理:以k8s cni flannel组件创建的vxlan设备为例

1、k8s cni组件创建flannel设备flannel.1,且这个设备为vxlan类型的设备

root@10.10.10.12:/home/ubuntu#  ethtool -i flannel.1
driver: vxlan
version: 0.1
firmware-version:
expansion-rom-version:
bus-info:
supports-statistics: no
supports-test: no
supports-eeprom-access: no
supports-register-dump: no
supports-priv-flags: no

2、vxlan设备创建的时候是有绑带其vtep通信的设备,比如:eth0

root@10.10.10.12:/home/ubuntu# ip -d link show flannel.1
6: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default
    link/ether 7e:b8:27:c7:4b:76 brd ff:ff:ff:ff:ff:ff promiscuity 0 minmtu 68 maxmtu 65535
    vxlan id 1 local 10.10.10.12 dev eth0 srcport 0 0 dstport 8472 nolearning ttl auto ageing 300 udpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535

3、flannel的agent会维护fdb表,查看flannel.1设备的fdb表

root@10-234-68-12:/home/ubuntu# bridge fdb show dev flannel.1
3e:5f:25:8e:e0:cd dst 10.10.10.12 self permanent
16:1d:36:f4:ab:26 dst 10.10.10.13 self permanent
a6:a0:56:1f:aa:4c dst 10.10.10.14 self permanent
76:50:cc:8d:7a:67 dst 10.10.10.15 self permanent

4、每个vxlan设备创建的时候会绑定一个udp socket,此时会有udp的vxlan端口的在监听,从而接收udp隧道包进行处理

# vxaln监听着8472的vxlan默认udp端口:
ubuntu@10.10.10.12:~$ netstat -uanv
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
udp        0      0 0.0.0.0:53851           0.0.0.0:*                          
udp        0      0 127.0.0.53:53           0.0.0.0:*                          
udp        0      0 0.0.0.0:111             0.0.0.0:*                          
udp        0      0 0.0.0.0:2049            0.0.0.0:*                          
udp        0      0 0.0.0.0:37543           0.0.0.0:*                          
udp        0      0 0.0.0.0:37554           0.0.0.0:*                          
udp        0      0 0.0.0.0:39762           0.0.0.0:*                          
udp        0      0 0.0.0.0:8472            0.0.0.0:*                          
udp6       0      0 :::52508                :::*                               
udp6       0      0 :::54535                :::*                               
udp6       0      0 :::59285                :::*                               
udp6       0      0 :::111                  :::*                               
udp6       0      0 :::2049                 :::*                               
udp6       0      0 :::40104                :::*

细节剖析

1、vxlan模块回调函数在哪里设置:在vxlan 模块初始化中做:vxlan_init_module

1、register_pernet_device内部执行init函数

rc = register_pernet_device(&vxlan_net_ops);
static struct pernet_operations vxlan_net_ops = {
	.init = vxlan_init_net,
	.exit = vxlan_exit_net,
	.id   = &vxlan_net_id,
	.size = sizeof(struct vxlan_net),
};

2、设置收包的vxlan回调函数

/* Disable multicast loopback */
inet_sk(sk)->mc_loop = 0;

/* Mark socket as an encapsulation socket. */
udp_sk(sk)->encap_type = 1;
udp_sk(sk)->encap_rcv = vxlan_udp_encap_recv;

2、包如何来到vxlan的回调处理函数

已经知道vxlan是 MAC IN UDP中的封装,因此,在解封装之前,一切按照原有流程走

2-1、从网卡收到包来到协议栈开始:__netif_receive_skb_core**

/*type,二层封装内的协议,IP为 0x0800*/
type = skb->protocol;
/*获取协议注册的入口函数,ip为 ip_rcv,声明的变量为 ip_packet_type*/
list_for_each_entry_rcu(ptype,
                        &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
  if (ptype->type == type &&
      (ptype->dev == null_or_dev || ptype->dev == skb->dev ||
       ptype->dev == orig_dev)) {
    if (pt_prev)
      ret = deliver_skb(skb, pt_prev, orig_dev);
    pt_prev = ptype;
  }
}

2-2、ip_rcv

此函数只是对报文进行可靠性验证,最后到 钩子函数 ‘NF_HOOK’。
钩子函数中就是配置的netfilter,通过验证就会直接进入函数 ‘ip_rcv_finish’。

2-3、ip_rcv_finish

/*sysctl_ip_early_demux 是二进制值,该值用于对发往本地数据包的优化。
当前仅对建立连接的套接字起作用。*/
if (sysctl_ip_early_demux && !skb_dst(skb) && skb->sk == NULL) {
		const struct net_protocol *ipprot;
		int protocol = iph->protocol;

		ipprot = rcu_dereference(inet_protos[protocol]);
		if (ipprot && ipprot->early_demux) {
			ipprot->early_demux(skb);
			/* must reload iph, skb->head might have changed */
			iph = ip_hdr(skb);
		}
	}
/*这一部分时查找路由,判断是local in还是 forwarding。本次分析按照 local in分析*/
if (!skb_dst(skb)) {
		int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
					       iph->tos, skb->dev);
		if (unlikely(err)) {
			if (err == -EXDEV)
				NET_INC_STATS_BH(dev_net(skb->dev),
						 LINUX_MIB_IPRPFILTER);
			goto drop;
		}
	}
……
	/*按照local in分析,则此处相当于调用 ip_local_deliver
	(可深入 查找路由函数,里面有函数指针赋值)*/
	return dst_input(skb);

2-4、ip_local_deliver

钩子函数检测,不深入,直接到最后。

2-5、ip_local_deliver_finish

ipprot = rcu_dereference(inet_protos[protocol]);
……
			ret = ipprot->handler(skb);
……			
到了传输层注册的入口函数。UDP入口函数为 ‘udp_rcv’。

2-6、__udp4_lib_rcv

/*根据源目端口号及IP查找 插口*/
sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable);
……
		/*进入udp队列收包流程*/
		int ret = udp_queue_rcv_skb(sk, skb);
……

2-7、udp_queue_rcv_skb

/*插口如果是封装类型,vxlan等,则进入封装处理入口,下面开始分析vxlan部分代码*/
encap_rcv = ACCESS_ONCE(up->encap_rcv);
if (skb->len > sizeof(struct udphdr) && encap_rcv != NULL) {
    int ret;

    ret = encap_rcv(sk, skb);
    if (ret <= 0) {
    UDP_INC_STATS_BH(sock_net(sk),
    UDP_MIB_INDATAGRAMS,
    is_udplite);
    return -ret;
  }
}

3、vxlan的udp并不会上送到udp socket监听的进程,与传统用户态监听udp socket不同

vxlan的udp并不会上送到udp socket监听的进程,因为没有这个进程,vxlan设备是为udp socket注册了回调,让协议栈调这个回调来处理这个包,而不是上送到用户进程。这个回调函数进行包的拆解后把原包再经过协议栈,然后上送用户态进程

UDP有两条处理路径:

  • 1、放到sk的接收队列,通知等待进程
  • 2、调用udp_sock的encap_rcv函数,类似vxlan的实现,可以直接在内核中进行处理

如果udp_sock定义了encap_rcv函数,将会把报文交给该函数处理,而不是传统的保存到sock队列,唤醒进程收包。

总结:linux对vxlan包的处理过程

  • 创建vxlan设备的时候会创建udp socket来向内核注册对udp包的处理,这个udp socket还绑定了vxlan处理的回调函数
  • 当udp包来了,内核发现有相应的udp socket在监听,就将包给这个udp socket指定的回调来处理,如果有设置回调的话,vxlan的udp socker设置的vxlan包回调处理函数就会在这里调用,
    从而有机会处理vxlan udp隧道包。
  • 包到了之后通过udp socket指定的vxlan回调处理函数进行包的拆解然后才是把里面的原包上送协议栈,从而送到用户态进程
  • vxlan的收包和发包是从另外的设备那里拿和发到那个设备的,这个设备在创建vxlan设备的时候,用dev参数指定的
  • vxlan设备创建的时候没有指定remote端vetp的信息就会去查转发表,此时就可以通过自己的agent来维护转发表,从而实现自己的大三层网络拓扑