Implement classless static routes
authorBenjamin Drung <benjamin.drung@profitbricks.com>
Wed, 13 Dec 2017 22:04:29 +0000 (23:04 +0100)
committerBen Hutchings <ben@decadent.org.uk>
Sun, 6 Jan 2019 19:33:01 +0000 (19:33 +0000)
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@profitbricks.com>
Gbp-Pq: Name Implement-classless-static-routes.patch

usr/kinit/ipconfig/bootp_proto.c
usr/kinit/ipconfig/dhcp_proto.c
usr/kinit/ipconfig/main.c
usr/kinit/ipconfig/netdev.c
usr/kinit/ipconfig/netdev.h

index 150ebfa7952b816e9b35744a0f5aa42d5bc19497..f6f9dd43bc904e7f16de595051af7ceef4673eda 100644 (file)
@@ -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,22 @@ 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;
+                       }
+                       dev->routes = ret;
+               }
+       }
+
        /*
         * Got packet.
         */
index d5b759bbc16ba855c8291ae2051e7da2cb7a1854..4e560b84c1c9a24794caa65c75b2cd298b5c2334 100644 (file)
@@ -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[] = {
index e00f049173fbe1877c48425c1eafe41b19c18350..1a41c3851c0555a53cccd233cfbce2cbf3916537 100644 (file)
@@ -71,6 +71,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,
@@ -80,9 +82,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])
@@ -106,8 +126,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)))
@@ -161,8 +181,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",
@@ -545,7 +581,7 @@ bail:
 
 static int add_all_devices(struct netdev *template);
 
-static int parse_device(struct netdev *dev, const char *ip)
+static int parse_device(struct netdev *dev, char *ip)
 {
        char *cp;
        int opt;
@@ -658,7 +694,7 @@ static void bringup_one_dev(struct netdev *template, struct netdev *dev)
        bringup_device(dev);
 }
 
-static struct netdev *add_device(const char *info)
+static struct netdev *add_device(char *info)
 {
        struct netdev *dev;
        int i;
index e203d0c69f95c301df685978230488cfa9adee66..de87f960285d96c957a4e54849ffaa1a8ff04149 100644 (file)
@@ -88,23 +88,44 @@ 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));
+
+                       r.rt_dev = 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");
+                               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;
 }
index 4b75a65ad067022bd06ef6261ed61800e4ce8548..dbc80cd09d116b8bc08200c846f7e522e8c49d5e 100644 (file)
@@ -1,14 +1,22 @@
 #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          */
+       char *name;             /* Device name          */
        unsigned int ifindex;   /* interface index      */
        unsigned int hwtype;    /* ARPHRD_xxx           */
        unsigned int hwlen;     /* HW address length    */
@@ -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 */
        int pkt_fd;             /* packet socket for this interface */
        struct netdev *next;    /* next configured i/f  */
@@ -70,7 +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);
@@ -84,4 +93,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 */