Skip to content

Commit

Permalink
net: add support for adding IP addresses
Browse files Browse the repository at this point in the history
This adds the --ip flag, which configures addresses to inner interfaces.
  • Loading branch information
Snaipe committed Apr 17, 2021
1 parent 601bc8f commit 1067f33
Show file tree
Hide file tree
Showing 9 changed files with 301 additions and 70 deletions.
5 changes: 5 additions & 0 deletions enter.c
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,11 @@ int enter(struct entry_settings *opts)
net_if_up(rtnl, "lo");
}

/* Add addresses */
for (size_t i = 0; i < opts->naddrs; ++i) {
net_addr_add(rtnl, &opts->addrs[i]);
}

/* Bring up the rest of the nics */
for (size_t i = 0; i < opts->nnics; ++i) {
net_if_up(rtnl, opts->nics[i].name);
Expand Down
3 changes: 3 additions & 0 deletions enter.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ struct limit {
enum {
MAX_MOUNT = 4096,
MAX_NICS = 4096,
MAX_ADDRS = 4096,
};

/* SHARE_WITH_PARENT is a special value for entry_settings.shares[ns]. */
Expand Down Expand Up @@ -65,6 +66,8 @@ struct entry_settings {

struct nic_options nics[MAX_NICS];
size_t nnics;
struct addr_options addrs[MAX_ADDRS];
size_t naddrs;

mode_t umask;

Expand Down
51 changes: 51 additions & 0 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ enum {
OPTION_GIDMAP,
OPTION_NIC,
OPTION_PIDFILE,
OPTION_IP,
OPTION_NO_FAKE_DEVTMPFS,
OPTION_NO_DERANDOMIZE,
OPTION_NO_PROC_REMOUNT,
Expand Down Expand Up @@ -289,6 +290,7 @@ int main(int argc, char *argv[], char *envp[])
{ "gid-map", required_argument, NULL, OPTION_GIDMAP },
{ "nic", required_argument, NULL, OPTION_NIC },
{ "pidfile", required_argument, NULL, OPTION_PIDFILE },
{ "ip", required_argument, NULL, OPTION_IP },

/* Opt-out feature flags */
{ "no-copy-hard-limits", no_argument, NULL, OPTION_NO_COPY_HARD_LIMITS },
Expand Down Expand Up @@ -540,6 +542,55 @@ int main(int argc, char *argv[], char *envp[])
break;
}

case OPTION_IP:
{
if (opts.naddrs >= MAX_ADDRS) {
errx(1, "can only create a maximum of %d addresses", MAX_ADDRS);
}
struct addr_options *addr = &opts.addrs[opts.naddrs];

/* 16 is enough to support everything */
struct kvlist kvlist[16];
size_t nopts = sizeof (kvlist) / sizeof (*kvlist);
kvlist_parse(optarg, kvlist, nopts, NULL);

/* Only the first two argument need not be key-value pairs */
size_t start = 0;
if (kvlist[start].key != NULL && kvlist[start].value == NULL) {
kvlist[start].value = kvlist[start].key;
kvlist[start++].key = "ip";
}
if (kvlist[start].key != NULL && kvlist[start].value == NULL) {
kvlist[start].value = kvlist[start].key;
kvlist[start++].key = "dev";
}

int has_link = 0;
int has_addr = 0;
for (size_t i = 0; i < nopts; ++i) {
if (kvlist[i].key == NULL) {
continue;
}

has_link = has_link || strcmp(kvlist[i].key, "dev") == 0;
has_addr = has_addr || strcmp(kvlist[i].key, "ip") == 0;

if (kvlist[i].key != NULL) {
addr_parse(addr, kvlist[i].key, kvlist[i].value);
}
}

if (!has_link) {
errx(1, "ip: must at least specify an interface to add the address to");
}
if (!has_addr) {
errx(1, "ip: must at least specify an IP address");
}

opts.naddrs++;
break;
}

case OPTION_UMASK:
if (sscanf(optarg, "%o", &opts.umask) != 1) {
err(2, "%s is not a valid umask", optarg);
Expand Down
12 changes: 10 additions & 2 deletions man/bst.1.scd
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,14 @@ _VAR=value_ before the executable to run.
Supported interface types and valid options are described in more detail
in the *NETWORKING* section.

\--ip [ip=]<ip-address>[/<subnet>],[dev=]<device-name>
Add the specified IPv4 or IPv6 address to the specified device in the inner
process' network namespace.

Specifying a subnet to an IPv4 address sets the corresponding broadcast address.

You cannot use this option with _--share=net_.

\--limit <resource>=<value>++
\--limit <resource>=[hard]:[soft]
Set the specified hard and soft resource limits of the specified resource
Expand Down Expand Up @@ -404,6 +412,8 @@ The following environment variables are accessible to the setup program:

# NETWORKING

## Creating network interfaces

This section describes the supported interface types and options that can
be used with _--nic_ in a network namespace.

Expand All @@ -412,8 +422,6 @@ Supported interface types are:
- *macvlan*
- *ipvlan*

## Options

*macvlan*
*link=<host-interface>*: attach the MACVLAN interface to the specified
host interface.
Expand Down
131 changes: 126 additions & 5 deletions net.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* in the LICENSE file.
*/

#include <arpa/inet.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
Expand All @@ -18,6 +19,7 @@
#include <string.h>
#include <sys/socket.h>

#include "compat.h"
#include "net.h"

// NLA_HDRLEN is defined with -Wsign-conversion errors, so just define our own here,
Expand Down Expand Up @@ -77,18 +79,21 @@ static int nl_sendmsg(int sockfd, const struct iovec *iov, size_t iovlen)
struct nlpkt {
struct {
struct nlmsghdr nlhdr;
struct ifinfomsg ifinfo;
union {
struct ifinfomsg ifinfo;
struct ifaddrmsg ifaddr;
};
} *hdr;
char *data;
size_t capacity;
};

static void nlpkt_init(struct nlpkt *pkt)
static void nlpkt_init(struct nlpkt *pkt, size_t hdr_extra_size)
{
pkt->data = calloc(1, 4096);
pkt->capacity = 4096;
pkt->hdr = (void *) pkt->data;
pkt->hdr->nlhdr.nlmsg_len = NLMSG_HDRLEN + NLMSG_ALIGN(sizeof (struct ifinfomsg));
pkt->hdr->nlhdr.nlmsg_len = NLMSG_HDRLEN + NLMSG_ALIGN(hdr_extra_size);
}

static void nlpkt_close(struct nlpkt *pkt)
Expand Down Expand Up @@ -214,7 +219,7 @@ static struct nic_handler nic_handlers[] = {
void net_if_add(int sockfd, const struct nic_options *nicopts)
{
struct nlpkt pkt;
nlpkt_init(&pkt);
nlpkt_init(&pkt, sizeof (struct ifinfomsg));

pkt.hdr->nlhdr.nlmsg_type = RTM_NEWLINK;
pkt.hdr->nlhdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE;
Expand Down Expand Up @@ -244,7 +249,7 @@ void net_if_add(int sockfd, const struct nic_options *nicopts)
void net_if_rename(int sockfd, int link, const char *to)
{
struct nlpkt pkt;
nlpkt_init(&pkt);
nlpkt_init(&pkt, sizeof (struct ifinfomsg));

pkt.hdr->nlhdr.nlmsg_type = RTM_NEWLINK;
pkt.hdr->nlhdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
Expand Down Expand Up @@ -291,6 +296,51 @@ void net_if_up(int sockfd, const char *name)
}
}

void net_addr_add(int sockfd, const struct addr_options *addr)
{
struct nlpkt pkt;
nlpkt_init(&pkt, sizeof (struct ifaddrmsg));

pkt.hdr->nlhdr.nlmsg_type = RTM_NEWADDR;
pkt.hdr->nlhdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE;

uint32_t link_idx = if_nametoindex(addr->intf);
if (link_idx == 0) {
err(1, "if_nametoindex %s", addr->intf);
}

pkt.hdr->ifaddr.ifa_family = addr->ip.type;
pkt.hdr->ifaddr.ifa_prefixlen = addr->ip.prefix_length;
pkt.hdr->ifaddr.ifa_scope = RT_SCOPE_UNIVERSE;
pkt.hdr->ifaddr.ifa_index = link_idx;

switch (addr->ip.type) {
case AF_INET6:
nlpkt_add_attr(&pkt, IFA_LOCAL, addr->ip.v6);
nlpkt_add_attr(&pkt, IFA_ADDRESS, addr->ip.v6);
break;
case AF_INET:
{
nlpkt_add_attr(&pkt, IFA_LOCAL, addr->ip.v4);
uint32_t broadcast = htonl(ntohl(*(uint32_t*)&addr->ip.v4) | (uint32_t) ((1 << (32 - addr->ip.prefix_length)) - 1));
nlpkt_add_attr(&pkt, IFA_BROADCAST, broadcast);
nlpkt_add_attr(&pkt, IFA_ADDRESS, addr->ip.v4);
}
break;
}

struct iovec iov = { .iov_base = pkt.data, .iov_len = pkt.hdr->nlhdr.nlmsg_len };

if (nl_sendmsg(sockfd, &iov, 1) == -1) {
char ip[INET6_ADDRSTRLEN+1];
inet_ntop(addr->ip.type, &addr->ip.v6, ip, sizeof (ip));
ip[INET6_ADDRSTRLEN] = '\0';
err(1, "addr_add %s/%d %.*s", ip, addr->ip.prefix_length, IF_NAMESIZE, addr->intf);
}

nlpkt_close(&pkt);
}

struct valmap {
const char *name;
void *val;
Expand Down Expand Up @@ -388,3 +438,74 @@ void nic_parse(struct nic_options *nic, const char *key, const char *val)
}
errx(1, "unknown option '%s' for interface type '%s'", key, nic->type);
}

static void addr_parse_ip(struct addr_options *addr, const char *data)
{
char copy[64];
if (strlcpy(copy, data, sizeof(copy)) != strlen(data)) {
errx(1, "invalid IP address '%s': string too large", data);
}

/* Split the ip into its address and prefix length component */
char *prefix_length = strchr(copy, '/');
if (prefix_length != NULL) {
*(prefix_length++) = 0;
}

void *buf;
if (strchr(copy, ':') != NULL) {
addr->ip.type = AF_INET6;
addr->ip.prefix_length = 128;
buf = &addr->ip.v6;
} else {
addr->ip.type = AF_INET;
addr->ip.prefix_length = 32;
buf = &addr->ip.v4;
}

if (inet_pton(addr->ip.type, copy, buf) == -1) {
err(1, "invalid IP address '%s'", copy);
}

if (prefix_length == NULL) {
return;
}

errno = 0;
long val = strtol(prefix_length, NULL, 10);
if (val < 0 || (addr->ip.type == AF_INET && val > 32) || (addr->ip.type == AF_INET6 && val > 128)) {
errno = ERANGE;
}
if (errno != 0) {
err(1, "invalid prefix length '%s'", prefix_length);
}
addr->ip.prefix_length = (uint8_t) val;
}

static void addr_parse_link(struct addr_options *addr, const char *v)
{
strlcpy(addr->intf, v, IF_NAMESIZE);
}

void addr_parse(struct addr_options *addr, const char *key, const char *val)
{
struct optmap {
const char *opt;
void (*fn)(struct addr_options *, const char *);
};

static struct optmap opts[] = {
{ "ip", addr_parse_ip },
{ "dev", addr_parse_link },
{ NULL, NULL },
};

for (struct optmap *e = &opts[0]; e->opt != NULL; ++e) {
if (strcmp(key, e->opt) != 0) {
continue;
}
e->fn(addr, val);
return;
}
errx(1, "unknown option '%s' for address", key);
}
17 changes: 17 additions & 0 deletions net.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

# include <net/ethernet.h>
# include <net/if.h>
# include <netinet/in.h>
# include <stdint.h>

struct macvlan {
Expand All @@ -32,12 +33,28 @@ struct nic_options {
};
};

struct ip {
uint8_t type; // Either AF_INET or AF_INET6
union {
struct in_addr v4;
struct in6_addr v6;
};
uint8_t prefix_length;
};

struct addr_options {
struct ip ip;
char intf[IF_NAMESIZE];
};

int init_rtnetlink_socket();

void net_addr_add(int sockfd, const struct addr_options *addropts);
void net_if_add(int sockfd, const struct nic_options *nicopts);
void net_if_rename(int sockfd, int link, const char *to);
void net_if_up(int sockfd, const char *name);

void addr_parse(struct addr_options *addr, const char *key, const char *val);
void nic_parse(struct nic_options *nic, const char *key, const char *val);

#endif /* !NET_H */
18 changes: 18 additions & 0 deletions test/net.t
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,21 @@ IPVLANs
$ bst --nic parent,dummy,address=fe:ed:de:ad:be:ef bst --nic ipvlan,type=ipvlan,link=parent -- ip link show ipvlan
2: ipvlan@if2: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/ether fe:ed:de:ad:be:ef brd ff:ff:ff:ff:ff:ff link-netnsid 0

Adding addresses

$ bst --nic dummy,type=dummy,address=fe:ed:de:ad:be:ef --ip 172.20.0.1,dev=dummy -- ip addr show dummy
2: dummy: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
link/ether fe:ed:de:ad:be:ef brd ff:ff:ff:ff:ff:ff
inet 172.20.0.1/32 brd 172.20.0.1 scope global dummy
valid_lft forever preferred_lft forever
inet6 fe80::fced:deff:fead:beef/64 scope link tentative
valid_lft forever preferred_lft forever

$ bst --nic dummy,type=dummy,address=fe:ed:de:ad:be:ef --ip 172.20.0.1/16,dev=dummy -- ip addr show dummy
2: dummy: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
link/ether fe:ed:de:ad:be:ef brd ff:ff:ff:ff:ff:ff
inet 172.20.0.1/16 brd 172.20.255.255 scope global dummy
valid_lft forever preferred_lft forever
inet6 fe80::fced:deff:fead:beef/64 scope link tentative
valid_lft forever preferred_lft forever
Loading

0 comments on commit 1067f33

Please sign in to comment.