网口状态标志位详解(Part 1/2)

常见问题

我们在平常定位问题,经常会通过ip linkifconfig查看网络环境和配置。其中网口的标志位是其中很重要的一个检查环节。在输出结果里,会有两组标志位信息

  • 一组是尖括弧里包含的值<BROADCAST,MULTICAST,UP,LOWER_UP\>,有很多标志位的组合。
  • 另一组是 state UP。还有其他状态如DOWN, LOWERLAYERDOWN等。
    图1:ip link 输出结果
    这些信息有时候看起来是一致的, 有时候是重复的,甚至矛盾的。难免让人产生一些疑惑,这两组标志位分别代表什么?他们是什么关系?下面我们就这两组标志,逐个展开分析下。
    我们先来看几个场景里,网口在不同状态下,输出的标志位有哪些差异。

场景1:网线没插,显示NO-CARRIER

图2: 拔掉网线后的网口输出结果

  • 【Q1】 网口状态标志位里有个明显特征,有个NO-CARRIER标志,但是状态里一个UP,一个DOWN,why? :(

Read More

struct-group

__struct_group

浏览IPv6代码时候,看到这样一个新玩法,
__struct_group

1
2
3
4
5
6
7
8
9
118 struct ipv6hdr {
...
132 __u8 hop_limit;
133
134 __struct_group(/* no tag */, addrs, /* no attrs */,
135 struct in6_addr saddr;
136 struct in6_addr daddr;
137 );
138 };

用法

再看一下用法:

 922 /* copy IPv6 saddr & daddr to flow_keys, possibly using 64bit load/store
 923  * Equivalent to :      flow->v6addrs.src = iph->saddr;
 924  *                      flow->v6addrs.dst = iph->daddr;
 925  */
 926 static inline void iph_to_flow_copy_v6addrs(struct flow_keys *flow,
 927                                             const struct ipv6hdr *iph)
 928 {
 ...
 932         memcpy(&flow->addrs.v6addrs, &iph->addrs, sizeof(flow->addrs.v6addrs));
 ...
 
1
2
3

###

11 /** 12 * __struct_group() - Create a mirrored named and anonyomous struct 13 * 14 * @TAG: The tag name for the named sub-struct (usually empty) 15 * @NAME: The identifier name of the mirrored sub-struct 16 * @ATTRS: Any struct attributes (usually empty) 17 * @MEMBERS: The member declarations for the mirrored structs 18 * 19 * Used to create an anonymous union of two structs with identical layout 20 * and size: one anonymous and one named. The former's members can be used 21 * normally without sub-struct naming, and the latter can be used to 22 * reason about the start, end, and size of the group of struct members. 23 * The named struct can also be explicitly tagged for layer reuse, as well 24 * as both having struct attributes appended. 25 */ 26 #define __struct_group(TAG, NAME, ATTRS, MEMBERS...) \ 27 union { \ 28 struct { MEMBERS } ATTRS; \ 29 struct TAG { MEMBERS } ATTRS NAME; \ 30 } ATTRS ```

内核如何管理同网口下多个ip地址(网口别名)

总结

内核如何管理 网口上的IPv4 地址:

  • 每个网口下面有多个IPv4地址,每个IP地址存放到一个struct in_ifaddr的结构体里。这个结构体里存放别名、ip地址和掩码信息。
  • 每个网口下面有一个单链表,每个ifa 节点通过ifa_next指向下一个 ifa 节点。通过这个链表把多个 ifa 地址链接在一起。
  • 每个 net_namespace下有多个网口,通过一个 hash 数据链表(net->ipv4.inet_addr_lst),把各个网口下的全部ifa节点,放到这个hash链里面。hash值相同的 ifa 节点,通过addr_list 挂在到一个hash 桶下。
  • 别名并不会创建一个独立的网口,只是在同一个网口下增加一个对应的 ifa结构体并挂载到上面的两个链表中。跟通过ip link 命令给一个网口增加一个地址相同,唯一不同在于这个地址起了一个别名或者叫label。

内核如何管理多网口多 IP 地址

命令行操作

有时候我们需要给一个网口配置的多个 IP 地址,这时候我们有两种配置方法:

  • 使用 ifconfig 配置到网口别名上。
  • 使用 ip addr 命令直接配置到网口上。

两种方法最终在内核实现是一样的,存储位置也一样,并且可以相互读写配置结果。

比如将9.9.9.199/24配置到 lo 口上,并起个别名lo:9

1
2
3
4
5
6
[root@VM-0-12-centos ~]# ifconfig  lo:9 9.9.9.199/24
[root@VM-0-12-centos ~]# ifconfig lo:9
lo:9: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 9.9.9.199 netmask 255.255.255.0
loop txqueuelen 1000 (Local Loopback)
[root@VM-0-12-centos ~]#

ifconfig命令的配置结果,也可以通用ip link命令来查看

1
2
3
4
5
6
7
8
9
[root@VM-0-12-centos ~]# ip a show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet 9.9.9.199/24 scope global lo:9
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever

网口的别名在ip命令里被当做label输出,放在scope字段后面

Read More

bfp 在内核运行的核心入口函数及其变形

bpf prog内核运行核心入口函数

总结:___bpf_prog_run

bfp 在内核运行的核心入口函数:___bpf_prog_run
___bpf_prog_run是bfp的核心函数入口,该函数被多个不同stack size的函数调用。
函数指针数组interpreters这把上面的这些函数汇集到一起。
当bpf程序被加载到内核时候,内核创建为它一个bpf_prog结构体,根据prog的stacksize,选择对应的interpreters里的对应的
函数,并保存到bpf_prog里的bpf_func上。
这样后续hook点运行bpf_prog程序时候,就使用bpf_func运行。

Read More

IPv6: how to support IPv6 ext header

概述

与IPv4的几点不同

IPv6在扩展头和协议处理上跟IPv4还有些不一样。

  1. IPv6报文格式设计上,上层协议和扩展头都作为IPv6的nexthdr类型串联在一起,不像IPv4那样扩展头是单独的option,上层协议类型放到ip头里的proto字段。
  2. IPv6的扩展头大部分采用tlv格式,大部分扩展头前几个字节会保存nexthdr,本扩展的长度这两个信息,然后跟本扩展头相关的一些数据。
  3. 我们无法像IPv4那样通过IP头里的字段就能简洁的判读出报文四层协议类型以及4层协议的offer和lenth等信息。必须逐个解析全部的扩展头。

原理

协议栈通过一个inet6_protocol类型的数组,保存IPv6所有的4层处理协议入口。

1
2
struct inet6_protocol __rcu *inet6_protos[MAX_INET_PROTOS] __read_mostly;
EXPORT_SYMBOL(inet6_protos)
  1. 数组inet6_protos的下标对应的就是每个扩展协议在IPv6扩展头里的nexthdr

inet6_protocol结构体

  1. struct inet6_protocol结构体里的handler是扩展头的处理入口函数。
  2. flags字段有两个标志位: INET6_PROTO_NOPOLICYINET6_PROTO_FINAL
  • INET6_PROTO_FINAL: 这个扩展是否可以作为IPv6的最后一个扩展, 比如
    TCP可以, 但是IPPROTO_DSTOPTS不可以。
  • INET6_PROTO_NOPOLICY: 这个要求必须有对应的IPsec/xfrm XFRM_POLICY_IN的规则
    这里有个疑问, esp6和ah6 为什么也有这个标志位, 我理解不应该有这个标志位, 有esp和ah头的,必须要有xfrm规则才可。
1
2
3
4
5
6
7
8
9
10
11
12
13
53 struct inet6_protocol {
54 int (*handler)(struct sk_buff *skb);
55
56 /* This returns an error if we weren't able to handle the error. */
57 int (*err_handler)(struct sk_buff *skb,
58 struct inet6_skb_parm *opt,
59 u8 type, u8 code, int offset,
60 __be32 info);
61
62 unsigned int flags; /* INET6_PROTO_xxx */
63 u32 secret;
64 };
65
1
2
66 #define INET6_PROTO_NOPOLICY    0x1
67 #define INET6_PROTO_FINAL 0x2

函数调用栈

1
2
3
4
5
6
7
==> ipv6_rcv
==> ==> ip6_rcv_finish
==> ==> ==> dst_input(skb); <== ip6_input
==> ==> ==> ==> ip6_input
==> ==> ==> ==> ==> ip6_input_finish
==> ==> ==> ==> ==> ==> ip6_protocol_deliver_rcu(net, skb, 0, false);
==> ==> ==> ==> ==> ==> ==> ipprot = rcu_dereference(inet6_protos[`nexthdr`]);

注册和注销

1
2
3
4
5
6
7
8
9
10
11
52 struct inet6_protocol {
53 int (*handler)(struct sk_buff *skb);
54
55 /* This returns an error if we weren't able to handle the error. */
56 int (*err_handler)(struct sk_buff *skb,
57 struct inet6_skb_parm *opt,
58 u8 type, u8 code, int offset,
59 __be32 info);
60
61 unsigned int flags; /* INET6_PROTO_xxx */
62 };
1
2
3
4
5
28 int inet6_add_protocol(const struct inet6_protocol *prot, unsigned char protocol)
29 {
30 return !cmpxchg((const struct inet6_protocol **)&inet6_protos[protocol],
31 NULL, prot) ? 0 : -1;
32 }
1
2
3
4
5
6
7
8
9
10
11
35 int inet6_del_protocol(const struct inet6_protocol *prot, unsigned char protocol)
36 {
37 int ret;
38
39 ret = (cmpxchg((const struct inet6_protocol **)&inet6_protos[protocol],
40 prot, NULL) == prot) ? 0 : -1;
41
42 synchronize_net();
43
44 return ret;
45 }

内核总共支持的IPv6四层协议类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
➜  linux git:(master) grep inet6_add_protocol net include -Rw
net/dccp/ipv6.c: err = inet6_add_protocol(&dccp_v6_protocol, IPPROTO_DCCP);
net/l2tp/l2tp_ip6.c: err = inet6_add_protocol(&l2tp_ip6_protocol, IPPROTO_L2TP);
net/sctp/ipv6.c: if (inet6_add_protocol(&sctpv6_protocol, IPPROTO_SCTP) < 0)
net/ipv6/udplite.c: ret = inet6_add_protocol(&udplitev6_protocol, IPPROTO_UDPLITE);
net/ipv6/xfrm6_protocol.c: if (inet6_add_protocol(netproto(protocol), protocol)) {
net/ipv6/exthdrs.c: ret = inet6_add_protocol(&rthdr_protocol, IPPROTO_ROUTING);
net/ipv6/exthdrs.c: ret = inet6_add_protocol(&destopt_protocol, IPPROTO_DSTOPTS);
net/ipv6/exthdrs.c: ret = inet6_add_protocol(&nodata_protocol, IPPROTO_NONE);
net/ipv6/ip6mr.c: if (inet6_add_protocol(&pim6_protocol, IPPROTO_PIM) < 0) {
net/ipv6/udp.c: ret = inet6_add_protocol(&net_hotdata.udpv6_protocol, IPPROTO_UDP);
net/ipv6/ip6_gre.c: err = inet6_add_protocol(&ip6gre_protocol, IPPROTO_GRE);
net/ipv6/reassembly.c: ret = inet6_add_protocol(&frag_protocol, IPPROTO_FRAGMENT);
net/ipv6/protocol.c:int inet6_add_protocol(const struct inet6_protocol *prot, unsigned char protocol)
net/ipv6/protocol.c:EXPORT_SYMBOL(inet6_add_protocol);
net/ipv6/tcp_ipv6.c: ret = inet6_add_protocol(&net_hotdata.tcpv6_protocol, IPPROTO_TCP);
net/ipv6/icmp.c: if (inet6_add_protocol(&icmpv6_protocol, IPPROTO_ICMPV6) < 0)
net/ipv6/tunnel6.c: if (inet6_add_protocol(&tunnel6_protocol, IPPROTO_IPV6)) {
net/ipv6/tunnel6.c: if (inet6_add_protocol(&tunnel46_protocol, IPPROTO_IPIP)) {
net/ipv6/tunnel6.c: inet6_add_protocol(&tunnelmpls6_protocol, IPPROTO_MPLS)) {
include/net/protocol.h:int inet6_add_protocol(const struct inet6_protocol *prot, unsigned char num);
➜ linux git:(master)

IPPROTO_ROUTING 字段处理

1
2
3
4
835 static const struct inet6_protocol rthdr_protocol = {
836 .handler = ipv6_rthdr_rcv,
837 .flags = INET6_PROTO_NOPOLICY,
838 };
1
ret = inet6_add_protocol(&rthdr_protocol, IPPROTO_ROUTING);

TCP协议处理

1
2
3
4
5
6
2406         net_hotdata.tcpv6_protocol = (struct inet6_protocol) {
2407 .handler = tcp_v6_rcv,
2408 .err_handler = tcp_v6_err,
2409 .flags = INET6_PROTO_NOPOLICY | INET6_PROTO_FINAL,
2410 };
2411 ret = inet6_add_protocol(&net_hotdata.tcpv6_protocol, IPPROTO_TCP);

IPV6-in-IPv4 tunnel协议处理

1
2
3
4
5
6
7
8
9
10
11
12
13
239 static const struct inet6_protocol tunnel6_protocol = {
240 .handler = tunnel6_rcv,
241 .err_handler = tunnel6_err,
242 .flags = INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL,
243 };
...

257 static int __init tunnel6_init(void)
258 {
259 if (inet6_add_protocol(&tunnel6_protocol, IPPROTO_IPV6)) {
...

52 IPPROTO_IPV6 = 41, /* IPv6-in-IPv4 tunnelling */

内核OVS的学习总结


OVS里最重要的几个元素:vport,flow,datapath。
其中datapth是vport和flow的桥梁。

kernel ovs 核心结构体及其关联

VPORT

内核包含多个datapath(brige),上面包含一个或者多个vport。
其中一个VPORT表示一个端口,一个vport只能归于一个特定的datapath。
每个vport有自己的type, 对应不同的vport ops.
每个内核网口被注册为vport的时候。

Read More

net rx drop

问题来源

接OP问题报告,监控到部分机器的net rx drop统计值异常,触发报警,
需排查具体原因,并确认是否影响业务。

问题分析

复现问题

跟OP同学确认,通过采集/proc/net/dev下的rx drop。
登录到出问题的机器上, 确认内核该统计值确实异常。

  • 异常报文个数不多,大约1s一个左右。
  • 不是所有机器都有异常,有部分机器drop统计为0.

相关内核代码

当一个数据报文经过对端设备(交换机或者网卡)传输到本机物理网卡时候,需要经过
网卡-- 网卡驱动-- 网络协议栈
这几个模块的处理。我们看到的drop统计值在增加,是在网络协议栈的入口处理部分产生异常导致。

Read More