From 5d0d77e1980f906e39a4e49dd3c3c5bcf0f36862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20Marussy?= Date: Mon, 30 Dec 2019 20:56:03 +0100 Subject: [PATCH] Wait for link-local address for DHCPv6 dhclient -6 fails if the interface to be configures has no link-local address. This is especially problematic when only DHCPv6 is used (e.g., --ip=none --ip6=dhcp), because the wait for a DHCPv4 lease is usually ample time for the LL address to become available on the IPv6 link. The LL address must not be tenative. Therefore, this patch implements waiting for a non-tentative link-local address in fnet for DHCPv6 configured interfaces. The command fnet waitll waits for an LL address on the interface . Currently, the maximum waiting time is 30 seconds, and the kernel is polled through rtnetlink every 500 milliseconds. These values seem sufficient for virtual bridged networks, e.g., libvirt NAT networks. --- src/firejail/dhcp.c | 16 ++++++ src/fnet/fnet.h | 1 + src/fnet/interface.c | 122 +++++++++++++++++++++++++++++++++++++++++++ src/fnet/main.c | 4 ++ 4 files changed, 143 insertions(+) diff --git a/src/firejail/dhcp.c b/src/firejail/dhcp.c index c9bbb4d8f71..7ce9a2b187f 100644 --- a/src/firejail/dhcp.c +++ b/src/firejail/dhcp.c @@ -117,6 +117,21 @@ static void dhcp_start_dhclient(const Dhclient *client) { *(client->pid) = dhcp_read_pidfile(client); } +static void dhcp_waitll(const char *ifname) { + sbox_run(SBOX_ROOT | SBOX_CAPS_NETWORK | SBOX_SECCOMP, 3, PATH_FNET, "waitll", ifname); +} + +static void dhcp_waitll_all() { + if (cfg.bridge0.arg_ip6_dhcp) + dhcp_waitll(cfg.bridge0.devsandbox); + if (cfg.bridge1.arg_ip6_dhcp) + dhcp_waitll(cfg.bridge1.devsandbox); + if (cfg.bridge2.arg_ip6_dhcp) + dhcp_waitll(cfg.bridge2.devsandbox); + if (cfg.bridge3.arg_ip6_dhcp) + dhcp_waitll(cfg.bridge3.devsandbox); +} + void dhcp_start(void) { if (!any_dhcp()) return; @@ -131,6 +146,7 @@ void dhcp_start(void) { printf("Running dhclient -4 in the background as pid %ld\n", (long) dhclient4_pid); } if (any_ip6_dhcp()) { + dhcp_waitll_all(); dhcp_start_dhclient(&dhclient6); if (arg_debug) printf("Running dhclient -6 in the background as pid %ld\n", (long) dhclient6_pid); diff --git a/src/fnet/fnet.h b/src/fnet/fnet.h index 4900967f786..4d0d62b39d1 100644 --- a/src/fnet/fnet.h +++ b/src/fnet/fnet.h @@ -47,6 +47,7 @@ int net_get_mac(const char *ifname, unsigned char mac[6]); void net_if_ip(const char *ifname, uint32_t ip, uint32_t mask, int mtu); int net_if_mac(const char *ifname, const unsigned char mac[6]); void net_if_ip6(const char *ifname, const char *addr6); +void net_if_waitll(const char *ifname); // arp.c diff --git a/src/fnet/interface.c b/src/fnet/interface.c index 7e7cceeed8d..f84e4f0f7c2 100644 --- a/src/fnet/interface.c +++ b/src/fnet/interface.c @@ -28,6 +28,8 @@ #include #include #include +#include +#include static void check_if_name(const char *ifname) { if (strlen(ifname) > IFNAMSIZ) { @@ -370,3 +372,123 @@ void net_if_ip6(const char *ifname, const char *addr6) { close(sock); } + +static int net_netlink_address_tentative(struct nlmsghdr *current_header) { + struct ifaddrmsg *msg = NLMSG_DATA(current_header); + struct rtattr *rta = IFA_RTA(msg); + size_t msg_len = IFA_PAYLOAD(current_header); + int has_flags = 0; + while (RTA_OK(rta, msg_len)) { + if (rta->rta_type == IFA_FLAGS) { + has_flags = 0; + uint32_t *flags = RTA_DATA(rta); + if (*flags & IFA_F_TENTATIVE) + return 1; + } + rta = RTA_NEXT(rta, msg_len); + } + // According to , if an IFA_FLAGS attribute is present, + // the field ifa_flags should be ignored. + return !has_flags && (msg->ifa_flags & IFA_F_TENTATIVE); +} + +static int net_netlink_if_has_ll(int sock, int index) { + struct { + struct nlmsghdr header; + struct ifaddrmsg message; + } req; + memset(&req, 0, sizeof(req)); + req.header.nlmsg_len = NLMSG_LENGTH(sizeof(req.message)); + req.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + req.header.nlmsg_type = RTM_GETADDR; + req.message.ifa_family = AF_INET6; + if (send(sock, &req, req.header.nlmsg_len, 0) != req.header.nlmsg_len) + errExit("send"); + + int found = 0; + int all_parts_processed = 0; + while (!all_parts_processed) { + char buf[16384]; + ssize_t len = recv(sock, buf, sizeof(buf), 0); + if (len < 0) + errExit("recv"); + if (len < sizeof(struct nlmsghdr)) { + fprintf(stderr, "Received incomplete netlink message\n"); + exit(1); + } + + struct nlmsghdr *current_header = (struct nlmsghdr *) buf; + while (NLMSG_OK(current_header, len)) { + switch (current_header->nlmsg_type) { + case RTM_NEWADDR: { + struct ifaddrmsg *msg = NLMSG_DATA(current_header); + if (!found && msg->ifa_index == index && msg->ifa_scope == RT_SCOPE_LINK && + !net_netlink_address_tentative(current_header)) + found = 1; + } + break; + case NLMSG_NOOP: + break; + case NLMSG_DONE: + all_parts_processed = 1; + break; + case NLMSG_ERROR: { + struct nlmsgerr *err = NLMSG_DATA(current_header); + fprintf(stderr, "Netlink error: %d\n", err->error); + exit(1); + } + break; + default: + fprintf(stderr, "Unknown netlink message type: %u\n", current_header->nlmsg_type); + exit(1); + break; + } + + current_header = NLMSG_NEXT(current_header, len); + } + } + + return found; +} + +// wait for a link-local IPv6 address for DHCPv6 +// ex: firejail --net=br0 --ip6=dhcp +void net_if_waitll(const char *ifname) { + // find interface index + int inet6_sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); + if (inet6_sock < 0) { + fprintf(stderr, "Error fnet: IPv6 is not supported on this system\n"); + exit(1); + } + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); + ifr.ifr_addr.sa_family = AF_INET; + if (ioctl(inet6_sock, SIOGIFINDEX, &ifr) < 0) { + perror("ioctl SIOGIFINDEX"); + exit(1); + } + close(inet6_sock); + int index = ifr.ifr_ifindex; + + // poll for link-local address + int netlink_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (netlink_sock < 0) + errExit("socket"); + int tries = 0; + int found = 0; + while (tries < 60 && !found) { + if (tries >= 1) + usleep(500000); + + found = net_netlink_if_has_ll(netlink_sock, index); + + tries++; + } + close(netlink_sock); + + if (!found) { + fprintf(stderr, "Waiting for link-local IPv6 address of %s timed out\n", ifname); + exit(1); + } +} diff --git a/src/fnet/main.c b/src/fnet/main.c index 890f842f667..3ef500b5e53 100644 --- a/src/fnet/main.c +++ b/src/fnet/main.c @@ -47,6 +47,7 @@ static void usage(void) { printf("\tfnet config mac addr\n"); printf("\tfnet config ipv6 dev ip\n"); printf("\tfnet ifup dev\n"); + printf("\tfnet waitll dev\n"); } int main(int argc, char **argv) { @@ -141,6 +142,9 @@ printf("\n"); else if (argc == 5 && strcmp(argv[1], "config") == 0 && strcmp(argv[2], "ipv6") == 0) { net_if_ip6(argv[3], argv[4]); } + else if (argc == 3 && strcmp(argv[1], "waitll") == 0) { + net_if_waitll(argv[2]); + } else { fprintf(stderr, "Error fnet: invalid arguments\n"); return 1;