最近在定位一个dhcpv6 server收到的dhcpv6 solicit报文源ip被修改的问题,学习了一下udp6的收包过程。
ipv6收包的入口是 net/ipv6/ip6_input.c
int ipv6_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
在 net/ipv6/af_inet6.c,static int __init inet6_init(void) 中,调用 ipv6_packet_init() 注册ipv6协议的packet type。
static struct packet_type ipv6_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_IPV6),
.func = ipv6_rcv,
};
static int __init ipv6_packet_init(void)
{
dev_add_pack(&ipv6_packet_type);
return 0;
}
读取/proc/net/ptype可以,获得所有注册的packet type及其处理函数。
# cat /proc/net/ptype
Type Device Function
0800 ip_rcv
0011 llc_rcv
8863 0x8c75c72c
8864 0x8c75ca04
0004 llc_rcv
0806 arp_rcv
86dd ipv6_rcv
在 net/core/dev.c 的 __netif_receive_skb_core()函数中,数据包将递给packet type对应的处理函数处理
deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
&ptype_base[ntohs(type) &
PTYPE_HASH_MASK]);
做一些合法性检查,把不合法的包丢掉,然后计算传输层头的偏移:
hdr = ipv6_hdr(skb);
skb->transport_header = skb->network_header + sizeof(*hdr);
然后,执行PRE_ROUTING hook
return NF_HOOK(NFPROTO_IPV6, NF_INET_PRE_ROUTING,
net, NULL, skb, dev, NULL,
ip6_rcv_finish);
pre routing hook执行完毕后,执行 ip6_rcv_finish(),也就定义在ip6_rcv()的前面。
这个函数执行skb的路由表查找,查找结果决定包是发给本地,还是进行转发。
if (!skb_valid_dst(skb))
ip6_route_input(skb);
return dst_input(skb);
在 net/ipv6/route.c 的 static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg) 中:
if (addr_type & IPV6_ADDR_MULTICAST)
rt->dst.input = ip6_mc_input;
else if (cfg->fc_flags & RTF_LOCAL)
rt->dst.input = ip6_input;
else
rt->dst.input = ip6_forward;
如果目的ip是本地ip,将调用ip6_input,如果目的ip是组播ip,将调用 ip6_mc_input。这两个函数也均定义在 net/ipv6/ip6_input.c
直接就是执行 LOCAL IN
return NF_HOOK(NFPROTO_IPV6, NF_INET_LOCAL_IN,
dev_net(skb->dev), NULL, skb, skb->dev, NULL,
ip6_input_finish);
执行完毕后,调用 ip6_input_finish。
计算出传输层协议,然后执行其handler。
如下,nhoff是传输层协议在ipv6 header中的偏移。nexthdr就是传输层的协议号。
const struct inet6_protocol *ipprot;
nhoff = IP6CB(skb)->nhoff;
nexthdr = skb_network_header(skb)[nhoff];
raw = raw6_local_deliver(skb, nexthdr);
ipprot = rcu_dereference(inet6_protos[nexthdr]);
ipprot->handler(skb);
ipv6 udp的代码位于:net/ipv6/udp.c
在int __init udpv6_init(void) 函数中,注册udp协议和处理函数:
ret = inet6_add_protocol(&udpv6_protocol, IPPROTO_UDP);
static const struct inet6_protocol udpv6_protocol = {
.handler = udpv6_rcv,
.err_handler = udpv6_err,
.flags = INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL,
};
这里最终会找到socket,将skb queue到socket的buffer,等待应用层接收。
参考:
https://blogchiasekienthuc.wordpress.com/2016/06/23/path-of-a-packet-in-linux-kernel-stack-part-1/
https://blogchiasekienthuc.wordpress.com/2016/08/31/path-of-a-packet-in-linux-kernel-stack-part-2/