Implement classless static routes support as specified in RFC3442.
Bug-Debian: https://bugs.debian.org/884716
Bug-Ubuntu: https://launchpad.net/bugs/1526956
Signed-off-by: Benjamin Drung <benjamin.drung at profitbricks.com>
---
usr/kinit/ipconfig/bootp_proto.c | 110 +++++++++++++++++++++++++++++++
usr/kinit/ipconfig/dhcp_proto.c | 1 +
usr/kinit/ipconfig/main.c | 50 ++++++++++++--
usr/kinit/ipconfig/netdev.c | 54 +++++++++++----
usr/kinit/ipconfig/netdev.h | 22 ++++++-
5 files changed, 215 insertions(+), 22 deletions(-)
diff --git a/usr/kinit/ipconfig/bootp_proto.c b/usr/kinit/ipconfig/bootp_proto.c
index 150ebfa7..ee218d5f 100644
--- a/usr/kinit/ipconfig/bootp_proto.c
+++ b/usr/kinit/ipconfig/bootp_proto.c
@@ -267,6 +267,87 @@ static char *bootp_ext119_decode(const void *ext, int16_t
ext_size, void *tmp)
return decoded_str;
}
+/*
+ * DESCRIPTION
+ * bootp_ext121_decode() decodes Classless Route Option data.
+ *
+ * ARGUMENTS
+ * const uint8_t *ext
+ * *ext is a pointer to a DHCP Classless Route Option data.
+ * For example, if *ext is {16, 192, 168, 192, 168, 42, 1},
+ * this function returns a pointer to
+ * {
+ * subnet = 192.168.0.0;
+ * netmask_width = 16;
+ * gateway = 192.168.42.1;
+ * next = NULL;
+ * }
+ *
+ * int16_t ext_size
+ * ext_size is the memory size of *ext. For example,
+ * if *ext is {16, 192, 168, 192, 168, 42, 1}, ext_size must be 7.
+ *
+ * RETURN VALUE
+ * if OK, a pointer to a decoded struct route malloc-ed
+ * else , NULL
+ *
+ * SEE ALSO RFC3442
+ */
+struct route *bootp_ext121_decode(const uint8_t *ext, int16_t ext_size)
+{
+ int16_t index = 0;
+ uint8_t netmask_width;
+ uint8_t significant_octets;
+ struct route *routes = NULL;
+ struct route *prev_route = NULL;
+
+ while (index < ext_size) {
+ netmask_width = ext[index];
+ index++;
+ if (netmask_width > 32) {
+ printf("IP-Config: Given Classless Route Option subnet mask width
'%u' "
+ "exceeds IPv4 limit of 32. Ignoring remaining
option.\n",
+ netmask_width);
+ return routes;
+ }
+ significant_octets = netmask_width / 8 + (netmask_width % 8 > 0);
+ if (ext_size - index < significant_octets + 4) {
+ printf("IP-Config: Given Classless Route Option remaining lengths (%u
octets) "
+ "is shorter than the expected %u octets. Ignoring remaining
options.\n",
+ ext_size - index, significant_octets + 4);
+ return routes;
+ }
+
+ struct route *route = malloc(sizeof(struct route));
+ if (route == NULL)
+ return routes;
+
+ /* convert only significant octets from byte array into integer in network
byte order */
+ route->subnet = 0;
+ memcpy(&route->subnet, &ext[index], significant_octets);
+ index += significant_octets;
+ /* RFC3442 demands: After deriving a subnet number and subnet mask from
+ each destination descriptor, the DHCP client MUST zero any bits in
+ the subnet number where the corresponding bit in the mask is zero. */
+ route->subnet &= netdev_genmask(netmask_width);
+
+ /* convert octet array into network byte order */
+ memcpy(&route->gateway, &ext[index], 4);
+ index += 4;
+
+ route->netmask_width = netmask_width;
+ route->next = NULL;
+
+ if (prev_route == NULL) {
+ routes = route;
+ } else {
+ prev_route->next = route;
+ }
+ prev_route = route;
+ }
+ return routes;
+}
+
/*
* Parse a bootp reply packet
*/
@@ -275,6 +356,8 @@ int bootp_parse(struct netdev *dev, struct bootp_hdr *hdr,
{
uint8_t ext119_buf[BOOTP_EXTS_SIZE];
int16_t ext119_len = 0;
+ uint8_t ext121_buf[BOOTP_EXTS_SIZE];
+ int16_t ext121_len = 0;
dev->bootp.gateway = hdr->giaddr;
dev->ip_addr = hdr->yiaddr;
@@ -367,6 +450,16 @@ int bootp_parse(struct netdev *dev, struct bootp_hdr *hdr,
} else
ext119_len = -1;
+ break;
+ case 121: /* Classless Static Route Option (RFC3442) */
+ if (ext121_len >= 0 &&
+ ext121_len + len <= sizeof(ext121_buf)) {
+ memcpy(ext121_buf + ext121_len,
+ ext, len);
+ ext121_len += len;
+ } else
+ ext121_len = -1;
+
break;
}
@@ -385,6 +478,23 @@ int bootp_parse(struct netdev *dev, struct bootp_hdr *hdr,
}
}
+ if (ext121_len > 0) {
+ struct route *ret;
+
+ ret = bootp_ext121_decode(ext121_buf, ext121_len);
+ if (ret != NULL) {
+ struct route *cur = dev->routes;
+ struct route *next;
+ while (cur != NULL) {
+ next = cur->next;
+ free(cur);
+ cur = next;
+ }
+ free(cur);
+ dev->routes = ret;
+ }
+ }
+
/*
* Got packet.
*/
diff --git a/usr/kinit/ipconfig/dhcp_proto.c b/usr/kinit/ipconfig/dhcp_proto.c
index ebf79cc0..ab7bdadc 100644
--- a/usr/kinit/ipconfig/dhcp_proto.c
+++ b/usr/kinit/ipconfig/dhcp_proto.c
@@ -26,6 +26,7 @@ static uint8_t dhcp_params[] = {
28, /* broadcast addr */
40, /* NIS domain name (why?) */
119, /* Domain Search Option */
+ 121, /* Classless Static Route Option (RFC3442) */
};
static uint8_t dhcp_discover_hdr[] = {
diff --git a/usr/kinit/ipconfig/main.c b/usr/kinit/ipconfig/main.c
index 7be2a1fc..ab713aae 100644
--- a/usr/kinit/ipconfig/main.c
+++ b/usr/kinit/ipconfig/main.c
@@ -70,6 +70,8 @@ static inline const char *my_inet_ntoa(uint32_t addr)
static void print_device_config(struct netdev *dev)
{
+ int dns0_spaces;
+ int dns1_spaces;
printf("IP-Config: %s complete", dev->name);
if (dev->proto == PROTO_BOOTP || dev->proto == PROTO_DHCP)
printf(" (%s from %s)", protoinfos[dev->proto].name,
@@ -79,9 +81,27 @@ static void print_device_config(struct netdev *dev)
printf(":\n address: %-16s ", my_inet_ntoa(dev->ip_addr));
printf("broadcast: %-16s ", my_inet_ntoa(dev->ip_broadcast));
printf("netmask: %-16s\n", my_inet_ntoa(dev->ip_netmask));
- printf(" gateway: %-16s ", my_inet_ntoa(dev->ip_gateway));
- printf("dns0 : %-16s ", my_inet_ntoa(dev->ip_nameserver[0]));
- printf("dns1 : %-16s\n", my_inet_ntoa(dev->ip_nameserver[1]));
+ if (dev->routes != NULL) {
+ struct route *cur;
+ char *delim = "";
+ printf(" routes :");
+ for (cur = dev->routes; cur != NULL; cur = cur->next) {
+ printf("%s %s/%u", delim, my_inet_ntoa(cur->subnet),
cur->netmask_width);
+ if (cur->gateway != 0) {
+ printf(" via %s", my_inet_ntoa(cur->gateway));
+ }
+ delim = ",";
+ }
+ printf("\n");
+ dns0_spaces = 3;
+ dns1_spaces = 5;
+ } else {
+ printf(" gateway: %-16s", my_inet_ntoa(dev->ip_gateway));
+ dns0_spaces = 5;
+ dns1_spaces = 3;
+ }
+ printf(" dns0%*c: %-16s", dns0_spaces, ' ',
my_inet_ntoa(dev->ip_nameserver[0]));
+ printf(" dns1%*c: %-16s\n", dns1_spaces, ' ',
my_inet_ntoa(dev->ip_nameserver[1]));
if (dev->hostname[0])
printf(" host : %-64s\n", dev->hostname);
if (dev->dnsdomainname[0])
@@ -105,8 +125,8 @@ static void configure_device(struct netdev *dev)
if (netdev_setaddress(dev))
printf("IP-Config: failed to set addresses on %s\n",
dev->name);
- if (netdev_setdefaultroute(dev))
- printf("IP-Config: failed to set default route on %s\n",
+ if (netdev_setroutes(dev))
+ printf("IP-Config: failed to set routes on %s\n",
dev->name);
if (dev->hostname[0] &&
sethostname(dev->hostname, strlen(dev->hostname)))
@@ -160,8 +180,24 @@ static void dump_device_config(struct netdev *dev)
my_inet_ntoa(dev->ip_broadcast));
write_option(f, "IPV4NETMASK",
my_inet_ntoa(dev->ip_netmask));
- write_option(f, "IPV4GATEWAY",
- my_inet_ntoa(dev->ip_gateway));
+ if (dev->routes != NULL) {
+ /* Use 6 digits to encode the index */
+ char key[23];
+ char value[19];
+ int i = 0;
+ struct route *cur;
+ for (cur = dev->routes; cur != NULL; cur = cur->next) {
+ snprintf(key, sizeof(key), "IPV4ROUTE%iSUBNET", i);
+ snprintf(value, sizeof(value), "%s/%u",
my_inet_ntoa(cur->subnet), cur->netmask_width);
+ write_option(f, key, value);
+ snprintf(key, sizeof(key), "IPV4ROUTE%iGATEWAY", i);
+ write_option(f, key, my_inet_ntoa(cur->gateway));
+ i++;
+ }
+ } else {
+ write_option(f, "IPV4GATEWAY",
+ my_inet_ntoa(dev->ip_gateway));
+ }
write_option(f, "IPV4DNS0",
my_inet_ntoa(dev->ip_nameserver[0]));
write_option(f, "IPV4DNS1",
diff --git a/usr/kinit/ipconfig/netdev.c b/usr/kinit/ipconfig/netdev.c
index e203d0c6..641087c1 100644
--- a/usr/kinit/ipconfig/netdev.c
+++ b/usr/kinit/ipconfig/netdev.c
@@ -6,6 +6,7 @@
#include <sys/ioctl.h>
#include <errno.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
@@ -88,23 +89,48 @@ static void set_s_addr(struct sockaddr *saddr, uint32_t
ipaddr)
memcpy(saddr, &sin, sizeof sin);
}
-int netdev_setdefaultroute(struct netdev *dev)
+int netdev_setroutes(struct netdev *dev)
{
struct rtentry r;
- if (dev->ip_gateway == INADDR_ANY)
- return 0;
-
- memset(&r, 0, sizeof(r));
-
- set_s_addr(&r.rt_dst, INADDR_ANY);
- set_s_addr(&r.rt_gateway, dev->ip_gateway);
- set_s_addr(&r.rt_genmask, INADDR_ANY);
- r.rt_flags = RTF_UP | RTF_GATEWAY;
-
- if (ioctl(cfd, SIOCADDRT, &r) == -1 && errno != EEXIST) {
- perror("SIOCADDRT");
- return -1;
+ /* RFC3442 demands:
+ If the DHCP server returns both a Classless Static Routes option and
+ a Router option, the DHCP client MUST ignore the Router option. */
+ if (dev->routes != NULL) {
+ struct route *cur;
+ for (cur = dev->routes; cur != NULL; cur = cur->next) {
+ memset(&r, 0, sizeof(r));
+
+ if (dev->name != NULL) {
+ r.rt_dev = strdup(dev->name);
+ }
+ set_s_addr(&r.rt_dst, cur->subnet);
+ set_s_addr(&r.rt_gateway, cur->gateway);
+ set_s_addr(&r.rt_genmask, netdev_genmask(cur->netmask_width));
+ r.rt_flags = RTF_UP;
+ if (cur->gateway != 0) {
+ r.rt_flags |= RTF_GATEWAY;
+ }
+
+ if (ioctl(cfd, SIOCADDRT, &r) == -1 && errno != EEXIST) {
+ perror("SIOCADDRT");
+ free(r.rt_dev);
+ return -1;
+ }
+ free(r.rt_dev);
+ }
+ } else if (dev->ip_gateway != INADDR_ANY) {
+ memset(&r, 0, sizeof(r));
+
+ set_s_addr(&r.rt_dst, INADDR_ANY);
+ set_s_addr(&r.rt_gateway, dev->ip_gateway);
+ set_s_addr(&r.rt_genmask, INADDR_ANY);
+ r.rt_flags = RTF_UP | RTF_GATEWAY;
+
+ if (ioctl(cfd, SIOCADDRT, &r) == -1 && errno != EEXIST) {
+ perror("SIOCADDRT");
+ return -1;
+ }
}
return 0;
}
diff --git a/usr/kinit/ipconfig/netdev.h b/usr/kinit/ipconfig/netdev.h
index cd853b6c..5b5117ac 100644
--- a/usr/kinit/ipconfig/netdev.h
+++ b/usr/kinit/ipconfig/netdev.h
@@ -1,12 +1,20 @@
#ifndef IPCONFIG_NETDEV_H
#define IPCONFIG_NETDEV_H
+#include <arpa/inet.h>
#include <sys/utsname.h>
#include <net/if.h>
#define BPLEN 256
#define FNLEN 128 /* from DHCP RFC 2131 */
+struct route {
+ uint32_t subnet; /* subnet */
+ uint32_t netmask_width; /* subnet mask width */
+ uint32_t gateway; /* gateway */
+ struct route *next;
+};
+
struct netdev {
const char *name; /* Device name */
unsigned int ifindex; /* interface index */
@@ -44,6 +52,7 @@ struct netdev {
char bootpath[BPLEN]; /* boot path */
char filename[FNLEN]; /* filename */
char *domainsearch; /* decoded, NULL or malloc-ed */
+ struct route *routes; /* decoded, NULL or malloc-ed list */
long uptime; /* when complete configuration */
struct netdev *next; /* next configured i/f */
};
@@ -69,7 +78,7 @@ extern struct netdev *ifaces;
int netdev_getflags(struct netdev *dev, short *flags);
int netdev_setaddress(struct netdev *dev);
-int netdev_setdefaultroute(struct netdev *dev);
+int netdev_setroutes(struct netdev *dev);
int netdev_up(struct netdev *dev);
int netdev_down(struct netdev *dev);
int netdev_init_if(struct netdev *dev);
@@ -83,4 +92,15 @@ static inline int netdev_running(struct netdev *dev)
return ret ? 0 : !!(flags & IFF_RUNNING);
}
+static inline uint32_t netdev_genmask(uint32_t netmask_width)
+{
+ /* Map netmask width to network mask in network byte order.
+ Example: 24 -> "255.255.255.0" -> htonl(0xFFFFFF00) */
+ if (netmask_width == 0) {
+ return 0;
+ } else {
+ return htonl(~((1u << (32 - netmask_width)) - 1));
+ }
+}
+
#endif /* IPCONFIG_NETDEV_H */
--
2.17.1