我正在 NixOS 上工作,用 C 创建一些函数来以编程方式操作 veth 对并分配 IPv6 地址,以便创建一个为单个主机上的容器设置网络的程序。
为此,我使用 libnftnl 和 libmnl 来处理构建 rtnetlink 数据包的细节,并setns()
在执行诸如启动接口之类的操作之前将调用线程的网络命名空间设置为正确的网络命名空间。
该程序的一般流程如下:
- create veth pairs in the host network namespace
- move one end of each veth pair to the corresponding network namespace
- set all the veth endpoints in the host network namespace to UP
- call `setns()` into each other network namespace to set
the loopback interface and vethpair end to UP
- add an IPv6 address to each of the veth ends in the host network namespace
- call `setns()` into each other network namespace to set the IPv6 address
of the other veth endpoints
问题
当我运行该程序时,主机网络命名空间中的 IPv6 地址并不总是分配给接口。前面的所有步骤都发生并存在,甚至 IPv6 地址也分配给其他网络命名空间中的所有接口但并不总是在主机网络命名空间中。
当我运行程序、删除所有接口并在大约一分钟内重新运行程序时,就会发生这种情况。
我提出的一些解决方案是,如果我让程序休眠(例如 5 秒),然后在将地址分配给主机命名空间中的接口之前,我可以运行该程序,删除接口,然后不间断地重新运行该程序和地址都被分配了。我还可以在删除接口后等待一段时间,然后重新运行程序,这也会导致所有 IPv6 地址被正确分配。
其他奇怪的东西
运行程序时,有时会添加主机网络命名空间中的某些 IPv6 地址,而另一些则未添加。为数据包返回的 netlink ACK 消息也表明没有发生错误,因此在ip address
程序退出后运行之前我无法判断地址分配失败。
由于该程序最终将成为更大的容器编排系统的一部分,因此我正在寻找是否有一种方法可以检测或防止此类错误。
我考虑过这可能是重复地址检测,或者 IP 地址在删除后仍存储在内核中的某个位置一段时间,但我没有收到来自 netlink 模块的错误。
编辑1
下面是一些代码,显示了处理创建 rtnetlink 消息以及发送和接收 netlink 消息的 C 代码。
1 #include <stdlib.h>
2 #include <time.h>
3 #include <poll.h>
4
5 /* Linux specific headers */
6 #include <linux/if_link.h>
7 #include <linux/rtnetlink.h>
8
9 /* Libmnl dependency */
10 #include <libmnl/libmnl.h>
11
13 int send_recv(struct mnl_socket *sock,
14 struct mnl_nlmsg_batch *batch,
15 void *receive_buffer, uint32_t receive_size,
16 const int portid)
17 {
18 int fd = mnl_socket_get_fd(sock);
19 int timeout = 0;
20 int status;
21 nfds_t nfds = 1;
22 ssize_t receive_size;
23 struct pollfd fds[nfds];
24
25 /* Send the buffered data via a pre-created netlink socket. */
26 status = mnl_socket_sendto(sock, mnl_nlmsg_batch_head(batch),
27 mnl_nlmsg_batch_size(batch));
28 if (status < 0) return -1;
29
30 fds[0].fd = fd;
31 fds[0].events = POLLIN;
32
33 while (poll(fds, nfds, timeout) > 0) {
34 /* Receive the response from the kernel. */
35 receive_size = mnl_socket_recvfrom(sock, receive_buf, receive_size);
36 if (receive_size == -1)
37 return -1;
38
39 /* Run a callback function on the response.
40 * cb_ctl_array is an array of function pointers based on the status of
41 * the response. 0 should be the sequence number, but the function also
42 * behaves correctly if the actual sequence number wasn't 0 and we feed it
43 * 0 still.
44 */
45 status = mnl_cb_run2(receive_buf, receive_size, 0, portid, NULL,
46 NULL, cb_ctl_array, MNL_ARRAY_SIZE(cb_ctl_array));
47
48 if (status != MNL_CB_OK)
49 return -1;
50 }
51
52 return MNL_CB_OK;
53
54 }
55
56 int set_interface_address_message(void *message_buffer, int seq,
57 uint8_t prefix,
58 const char if_name[IFNAMSIZ],
59 const struct in6_addr *in6_addr)
60 {
61 /* put the header on the message buffer */
62 struct nlmsghdr *nlh = mnl_nlmsg_put_header(message_buffer);
63 struct ifaddrmsg *ifa = mnl_nlmsg_put_extra_header(nlh,
64 sizeof(struct ifaddrmsg));
65
66 if (!nlh)
67 return -1;
68
69 if (!ifa)
70 return -1;
71
72 /* new netlink request */
73 memset(nlh, 0, sizeof(struct nlmsg));
74 nlh->nlmsg_type = RTM_NEWADDR;
75 nlh->nlmsg_flags = NLM_F_REQUEST |
76 NLM_F_ACK |
77 NLM_F_REPLACE;
78 nlh->nlmsg_seq = seq;
79
80 memset(ifa, 0, sizeof(struct ifaddrmsg));
81 ifa->ifa_family = AF_INET6;
82 ifa->ifa_prefixlen = prefix;
83 ifa->ifa_flags = 0;
84 ifa->ifa_scope = RT_SCOPE_UNIVERSE;
85 ifa->ifa_index = if_nametoindex(if_name);
86
87 /* Put the ipv6 address */
88 mnl_attr_put(nlh, IFA_ADDRESS, sizeof(struct in6_addr), (void *)in6_addr);
89
90 return 0;
91 }
ip
该程序要镜像的命令序列是
sudo ip netns add A
sudo ip link add veth1 type veth peername veth2
sudo ip link set dev veth2 netns A
sudo ip link set dev veth1 up
sudo ip -n A link set dev veth2 up
sudo ip -n A link set dev lo up
sudo ip address add fc00::1/64 dev veth1
sudo ip -n A address add fc00::2/64 dev veth2
在 bash 脚本中运行上述命令序列向接口添加 ipv6 地址从来没有失败过。