私はNixOSで作業しています。単一のホストでコンテナのネットワーキングを設定するプログラムを作成するために、プログラムでvethペアを操作し、IPv6アドレスを割り当てるいくつかの機能をCで書いています。
これを行うsetns()
には、インターフェイスの起動などの作業を実行する前に、libnftnlとlibmnlを使用してrtnetlinkパケットの作成と呼び出しスレッドのネットワーク名前空間を正しいネットワーク名前空間に設定する詳細を処理します。
プログラムの一般的な流れは次のとおりです。
- 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アドレスも他のネットワーク名前空間のすべてのインターフェイスに割り当てられます。ただし、必ずしもホストネットワークの名前空間にあるわけではありません。。
プログラムを実行してすべてのインターフェイスを削除してから約1分以内にプログラムを再実行すると、この現象が発生します。
私が考えたいくつかの解決策は、ホストネームスペースのインターフェイスにアドレスを割り当てる前にプログラムをスリープモード(たとえば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
インターフェイスにipv6アドレスを追加するためにbashスクリプトで上記のコマンドシーケンスを実行することは決して失敗しませんでした。