저는 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 스크립트에서 위의 명령 시퀀스를 실행하는 것은 결코 실패하지 않았습니다.