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来维护转发表,从而实现自己的大三层网络拓扑