[klibc] [PATCH] Implement classless static routes

Benjamin Drung benjamin.drung at profitbricks.com
Tue Dec 19 05:27:18 PST 2017


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 | 112 +++++++++++++++++++++++++++++++++++++++
 usr/kinit/ipconfig/dhcp_proto.c  |   1 +
 usr/kinit/ipconfig/main.c        |  43 ++++++++++++---
 usr/kinit/ipconfig/netdev.c      |  45 +++++++++++-----
 usr/kinit/ipconfig/netdev.h      |  17 ++++++
 5 files changed, 197 insertions(+), 21 deletions(-)

diff --git a/usr/kinit/ipconfig/bootp_proto.c b/usr/kinit/ipconfig/bootp_proto.c
index 150ebfa7..ae050127 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 NULL;
+
+		/* convert only significant octets from array into 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 */
+		route->gateway = *(uint32_t*)&ext[index];
+		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,25 @@ 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) {
+			if (dev->routes != NULL) {
+				struct route *cur = dev->routes;
+				struct route *next;
+				while(cur->next != 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..0e12db4d 100644
--- a/usr/kinit/ipconfig/main.c
+++ b/usr/kinit/ipconfig/main.c
@@ -79,9 +79,22 @@ 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);
+			printf(" via %s", my_inet_ntoa(cur->gateway));
+			delim = ",";
+		}
+		printf("\n dns0   : %-16s ", my_inet_ntoa(dev->ip_nameserver[0]));
+		printf("dns1     : %-16s\n", my_inet_ntoa(dev->ip_nameserver[1]));
+	} else {
+		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->hostname[0])
 		printf(" host   : %-64s\n", dev->hostname);
 	if (dev->dnsdomainname[0])
@@ -105,8 +118,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 +173,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..446b4d87 100644
--- a/usr/kinit/ipconfig/netdev.c
+++ b/usr/kinit/ipconfig/netdev.c
@@ -88,23 +88,40 @@ 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));
+
+			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 | RTF_GATEWAY;
+
+			if (ioctl(cfd, SIOCADDRT, &r) == -1 && errno != EEXIST) {
+				perror("SIOCADDRT");
+				return -1;
+			}
+		}
+	} 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..e909ef22 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  */
 };
@@ -70,6 +79,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 +93,11 @@ 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) */
+	return htonl(~((1u << (32 - netmask_width)) - 1));
+}
+
 #endif /* IPCONFIG_NETDEV_H */
-- 
2.14.1



More information about the klibc mailing list