Skip to content

Commit

Permalink
Wait for link-local address for DHCPv6
Browse files Browse the repository at this point in the history
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 <if> waits for an LL address on the interface <if>.

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.
  • Loading branch information
kris7t committed Dec 30, 2019
1 parent ce3c198 commit 5d0d77e
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 0 deletions.
16 changes: 16 additions & 0 deletions src/firejail/dhcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/fnet/fnet.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
122 changes: 122 additions & 0 deletions src/fnet/interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
#include <net/if_arp.h>
#include <net/route.h>
#include <linux/if_bridge.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>

static void check_if_name(const char *ifname) {
if (strlen(ifname) > IFNAMSIZ) {
Expand Down Expand Up @@ -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 <linux/if_addr.h>, 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);
}
}
4 changes: 4 additions & 0 deletions src/fnet/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 5d0d77e

Please sign in to comment.