本文作者 / yogazhao
爱自然科学,赞叹于大师级码农高超的艺术境界;爱生命科学,诚服于古圣先贤的天地气象。
可能大多数瓜农都对《艺伎回忆录》比较熟悉,作为一个IT界的码农,当然也有自己类似的经历,该文分为上篇和下篇,此篇为《OVS BUG撸码回忆录•上篇》
回忆录缘起
以前为排查ovs的某个bug,无奈撸了把相关内核流程。当时因为调用链太多,脑袋栈溢出,处理不过来,所以临时用txt比较零散的记录了下关键点,做完了就丢了。后面想起来,无奈用everything找了好久才找到, 再读之,发现忘了很多,临时整理这零散的笔记,也算给自己加深下意识,因为是附带研究,如有错误,恳请指正。
<内核版本>
3.10
回忆录之收包流程
网卡种类较多,当时取了e1000这种比较通用的网卡为例。下述以精简代码的形式展示核心流程。
网卡收到报文->e1000_intr->__napi_schedule(&adapter->napi)
{
local_irq_save(flags); // 关中断
____napi_schedule(&__get_cpu_var(softnet_data), n);
local_irq_restore(flags); // 开中断
preempt_check_resched_rt();
}
____napi_schedule --> raise NET_RX_SOFTIRQ中断触发软中断NET_RX_SOFTIRQ处理函数net_rx_action被调用
net_rx_action
{
n = list_first_entry(&sd->poll_list, struct napi_struct, poll_list);
...
work = n->poll(n, weight); // e1000e_poll
}
e1000e_poll->e1000_receive_skb->napi_gro_receive->netif_receive_skb->__netif_receive_skb->__netif_receive_skb_core
{
skb_reset_network_header(skb);//把L3、L4的头都指向data数据结构,到这里的时候skb已经处理完L2层的头了
...
another_round: // vlan报文处理,循环剥掉vlan头,比如vlan over vlan 两个vlan都会剥掉
...
if (skb->protocol == cpu_to_be16(ETH_P_8021Q) ||
skb->protocol == cpu_to_be16(ETH_P_8021AD)) {
skb = vlan_untag(skb); //洞房花烛夜,赤诚相对
if (unlikely(!skb))
goto out;
}
list_for_each_entry_rcu(ptype, &ptype_all, list) { //把包交给特定协议相关的处理函数前,先调用ptype_all中注册的函数
if (!ptype->dev || ptype->dev == skb->dev) {//最常见的为tcpdump,该工具就是从这里拿到所有收到的包的,所以tcpdump抓包在投递到协议栈处理前。
tcpdump这种经常使用的,激起了我的肾上腺素,顺便追根溯源看一把究竟
对于ptype_all
对于tcpdump,应用层调用
socket(PF_PACKET,SOCK_RAW,768)=3
对应内核,创建socket最终会调用
packet_create
{
po->prot_hook.func = packet_rcv;
...
if (proto) {
po->prot_hook.type = proto;
register_prot_hook(sk)
{
dev_add_pack(&po->prot_hook);
}
}
}
static inline struct list_head *ptype_head(const struct packet_type *pt)
{
if (pt->type == htons(ETH_P_ALL)) // Protocol类型为ETH_P_ALL(ntohs后为768), 即所有包,则放入ptype_all链表中
return &ptype_all;
else
return &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK];//Protocol类型为其他值,则放入ptype_base hash表中
}
void dev_add_pack(struct packet_type *pt)
{
struct list_head *head = ptype_head(pt);
spin_lock(&ptype_lock);
list_add_rcu(&pt->list, head);
spin_unlock(&ptype_lock);
}
Protocol类型为ETH_P_ALL(ntohs后为768), 即所有包,则放入ptype_all链表中
static struct packet_type arp_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_ARP),
.func = arp_rcv,
};
static int arp_proc_init(void);
void __init arp_init(void)
{
neigh_table_init(&arp_tbl);
dev_add_pack(&arp_packet_type); // 就是添加到ptype_all
arp_proc_init();
ifdef CONFIG_SYSCTL
neigh_sysctl_register(NULL, &arp_tbl.parms, "ipv4", NULL);
endif
register_netdevice_notifier(&arp_netdev_notifier);
}
static struct packet_type ip_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_IP),
.func = ip_rcv,
};
static int __init inet_init(void)
{
...
dev_add_pack(&ip_packet_type);
...
}
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype; //pt_prev的加入是为了优化,只有当找到下一个匹配的时候,才执行这一次的回调函数
}
}
// rx_handler是重中之重,关注、关注、关注。linux内核为了高性能,很喜欢用rwlock的改进版rcu,这种思想是值得借鉴的。
rx_handler = rcu_dereference(skb->dev->rx_handler);
if (rx_handler){
...
switch (rx_handler(&skb)) // 交给rx_handler处理,例如ovs, linux bridge等 此类接口处理报文在协议栈之前,因此netfilter对此类接口不起作用,所以在云环境(openstack)中,需要在虚拟机tap口与虚拟交换机之间增加Linux bridge设备来使报文经过协议栈(netfilter起作用)来实现security group。
肾上腺素爆棚,rx_handler必须要一探究竟。
- 看看【ovs】的rx_handler
int netdev_rx_handler_register(struct net_device *dev,
rx_handler_func_t *rx_handler,
void *rx_handler_data)
{
ASSERT_RTNL();
if (dev->rx_handler)
return -EBUSY;
/* Note: rx_handler_data must be set before rx_handler */
rcu_assign_pointer(dev->rx_handler_data, rx_handler_data);
rcu_assign_pointer(dev->rx_handler, rx_handler);
return 0;
}
./net/openvswitch/vport-netdev.c:104: err = netdev_rx_handler_register(netdev_vport->dev, netdev_frame_hook,
static struct vport *netdev_create(const struct vport_parms *parms)
{
...
err = netdev_rx_handler_register(netdev_vport->dev, netdev_frame_hook,
vport);
...
}
总结之就是 rx_handler=netdev_frame_hook
- 看看【bridge】的rx_handler
./net/bridge/br_if.c:375:err= netdev_rx_handler_register(dev, br_handle_frame, p);
也即 rx_handler=br_handle_frame
}
/*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); // 根据具体协议投递包(如 arp、ip)
pt_prev = ptype; //很喜欢这种写法
}
}
}->deliver_skb{
pt_prev->func // 如ip,则对应的就是ip_rcv ---- 进入第三层
}
ip_rcv
{
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
}->ip_rcv_finish->dst_input->ip_local_deliver
{
/*
* Reassemble IP fragments. //重组分片
*/
if (ip_is_fragment(ip_hdr(skb))) {
if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))
return 0;
}
return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, skb, skb->dev, NULL,
ip_local_deliver_finish);
}->ip_local_deliver_finish{
ipprot->handler(skb)//对应的就是udp_rcv, 进入第四层
}
udp_rcv->__udp4_lib_rcv->udp_queue_rcv_skb->encap_rcv/vxlan_udp_encap_recv
vxlan_socket_create
{
udp_sk(sk)->encap_type = 1;
udp_sk(sk)->encap_rcv = vxlan_udp_encap_recv;
}
盘丝洞翻遍终于让我找到了你: vxlan_udp_encap_recv
vxlan_udp_encap_recv
{
/* pop off outer UDP header */ -- 去udp头
__skb_pull(skb, sizeof(struct udphdr));
不是vxlan丢弃,不符合条件的vni丢弃
...
__skb_pull(skb, sizeof(struct vxlanhdr));//剥掉vxlan头
// 没有l2头则丢弃(这里的l2头指的是original l2 frame的l2头)
if (!pskb_may_pull(skb, ETH_HLEN)) {
vxlan->dev->stats.rx_length_errors++;
vxlan->dev->stats.rx_errors++;
goto drop;
}
/*重置skb mac数据,因为解包了(既然真爱,那就真面目相见)*/
skb_reset_mac_header(skb);
/*学习报文记录到本地 vxlan fdb表*/
if ((vxlan->flags & VXLAN_F_LEARN) &&
vxlan_snoop(skb->dev, oip->saddr, eth_hdr(skb)->h_source))
goto drop;
/*更改skb收包接口*/
__skb_tunnel_rx(skb, vxlan->dev);
...
/*skb处理完成,进入主要函数*/
netif_rx(skb);
}
这里有点绕:前面有讲在包递交到协议栈之前,ovs有截获。而这里netif_rx又是走的本机协议栈处理,到底走哪个?
[root@node01 ~]# ovs-dpctl show
system@ovs-system:
lookups: hit:79272720733 missed:2990364171 lost:830495930
flows: 70550
masks: hit:623377534012 total:7 hit/pkt:7.58
port 0: ovs-system (internal)
port 1: qvoab699415-b9
port 2: qvof45c0031-aa
port 112: qvo318ac212-4a
port 113: br-int (internal)
port 114: qvo860fecf7-cf
port 115: qvod7e180b2-db
port 333: qvo59a425ee-ae
port 334: vxlan_sys_4789 (vxlan: df_default=false, ttl=0)
port 335: br-tun (internal)
ovs的port有好几种类型,br-tun为internal类型, internal接口报文一般会从system类型接口传入,从system接口收包处理过程继续,internal类型接口定义的send函数为’internal_dev_recv’。static int internal_dev_recv(struct vport *vport, struct sk_buff *skb) { struct net_device *netdev = netdev_vport_priv(vport)->dev; int len;
len = skb->len;
skb_dst_drop(skb);
nf_reset(skb);
secpath_reset(skb);
skb->dev = netdev;
skb->pkt_type = PACKET_HOST;
skb->protocol = eth_type_trans(skb, netdev);
netif_rx(skb);
return len;
}
const struct vport_ops ovs_internal_vport_ops = {
.type = OVS_VPORT_TYPE_INTERNAL,
.create = internal_dev_create,
.destroy = internal_dev_destroy,
.get_name = ovs_netdev_get_name,
.send = internal_dev_recv,
};
internal类型的接口没有设置'rx_handler', 因此也就是没有走截获,而直接走的正常网络栈流程,最终跑到vxlan_udp_encap_recv
如果是vport接口,其注册了rx_handler,故会走到netdev_frame_hook
至此上篇完结。
下篇预告: 回忆OVS内部内核态与用户态的流程及发包处理流程。
猜你还想看这些内容
· END ·
点它!