--- /dev/null
+From 7e6299e72feaaf7e8bd499614999ba8a07dd1a8a Mon Sep 17 00:00:00 2001
+From: Pavel Zhukov <pzhukov@redhat.com>
+Date: Thu, 21 Feb 2019 10:32:35 +0100
+Subject: [PATCH 12/28] RFC 3442 - Classless Static Route Option for DHCPv4
+ (#516325)
+
+(Submitted to dhcp-bugs@isc.org - [ISC-Bugs #24572])
+---
+ client/clparse.c | 13 ++++++++--
+ common/dhcp-options.5 | 43 +++++++++++++++++++++++++++++++++
+ common/inet.c | 54 +++++++++++++++++++++++++++++++++++++++++
+ common/options.c | 49 ++++++++++++++++++++++++++++++++++++-
+ common/parse.c | 56 ++++++++++++++++++++++++++++++++++++++++++-
+ common/tables.c | 2 ++
+ includes/dhcp.h | 1 +
+ includes/dhcpd.h | 2 ++
+ includes/dhctoken.h | 3 ++-
+ 9 files changed, 218 insertions(+), 5 deletions(-)
+
+diff --git a/client/clparse.c b/client/clparse.c
+index 902b523..57f6456 100644
+--- a/client/clparse.c
++++ b/client/clparse.c
+@@ -31,7 +31,7 @@
+
+ struct client_config top_level_config;
+
+-#define NUM_DEFAULT_REQUESTED_OPTS 14
++#define NUM_DEFAULT_REQUESTED_OPTS 15
+ /* There can be 2 extra requested options for DHCPv4-over-DHCPv6. */
+ struct option *default_requested_options[NUM_DEFAULT_REQUESTED_OPTS + 2 + 1];
+
+@@ -90,7 +90,11 @@ isc_result_t read_client_conf ()
+ dhcp_universe.code_hash, &code, 0, MDL);
+
+ /* 4 */
+- code = DHO_ROUTERS;
++ /* The Classless Static Routes option code MUST appear in the parameter
++ * request list prior to both the Router option code and the Static
++ * Routes option code, if present. (RFC3442)
++ */
++ code = DHO_CLASSLESS_STATIC_ROUTES;
+ option_code_hash_lookup(&default_requested_options[3],
+ dhcp_universe.code_hash, &code, 0, MDL);
+
+@@ -144,6 +148,11 @@ isc_result_t read_client_conf ()
+ option_code_hash_lookup(&default_requested_options[13],
+ dhcp_universe.code_hash, &code, 0, MDL);
+
++ /* 15 */
++ code = DHO_ROUTERS;
++ option_code_hash_lookup(&default_requested_options[14],
++ dhcp_universe.code_hash, &code, 0, MDL);
++
+ for (code = 0 ; code < NUM_DEFAULT_REQUESTED_OPTS ; code++) {
+ if (default_requested_options[code] == NULL)
+ log_fatal("Unable to find option definition for "
+diff --git a/common/dhcp-options.5 b/common/dhcp-options.5
+index a784b32..86f04ed 100644
+--- a/common/dhcp-options.5
++++ b/common/dhcp-options.5
+@@ -117,6 +117,26 @@ hexadecimal, separated by colons. For example:
+ or
+ option dhcp-client-identifier 43:4c:49:45:54:2d:46:4f:4f;
+ .fi
++.PP
++The
++.B destination-descriptor
++describe the IP subnet number and subnet mask
++of a particular destination using a compact encoding. This encoding
++consists of one octet describing the width of the subnet mask,
++followed by all the significant octets of the subnet number.
++The following table contains some examples of how various subnet
++number/mask combinations can be encoded:
++.nf
++.sp 1
++Subnet number Subnet mask Destination descriptor
++0 0 0
++10.0.0.0 255.0.0.0 8.10
++10.0.0.0 255.255.255.0 24.10.0.0
++10.17.0.0 255.255.0.0 16.10.17
++10.27.129.0 255.255.255.0 24.10.27.129
++10.229.0.128 255.255.255.128 25.10.229.0.128
++10.198.122.47 255.255.255.255 32.10.198.122.47
++.fi
+ .SH SETTING OPTION VALUES USING EXPRESSIONS
+ Sometimes it's helpful to be able to set the value of a DHCP option
+ based on some value that the client has sent. To do this, you can
+@@ -1093,6 +1113,29 @@ dhclient-script will create routes:
+ .RE
+ .PP
+ .nf
++.B option \fBclassless-static-routes\fR \fIdestination-descriptor ip-address\fR
++ [\fB,\fR \fIdestination-descriptor ip-address\fR...]\fB;\fR
++.fi
++.RS 0.25i
++.PP
++This option (see RFC3442) specifies a list of classless static routes
++that the client should install in its routing cache.
++.PP
++This option can contain one or more static routes, each of which
++consists of a destination descriptor and the IP address of the router
++that should be used to reach that destination.
++.PP
++Many clients may not implement the Classless Static Routes option.
++DHCP server administrators should therefore configure their DHCP
++servers to send both a Router option and a Classless Static Routes
++option, and should specify the default router(s) both in the Router
++option and in the Classless Static Routes option.
++.PP
++If the DHCP server returns both a Classless Static Routes option and
++a Router option, the DHCP client ignores the Router option.
++.RE
++.PP
++.nf
+ .B option \fBstreettalk-directory-assistance-server\fR \fIip-address\fR
+ [\fB,\fR \fIip-address\fR...]\fB;\fR
+ .fi
+diff --git a/common/inet.c b/common/inet.c
+index 0f7f168..7c446d4 100644
+--- a/common/inet.c
++++ b/common/inet.c
+@@ -519,6 +519,60 @@ free_iaddrcidrnetlist(struct iaddrcidrnetlist **result) {
+ return ISC_R_SUCCESS;
+ }
+
++static const char *
++inet_ntopdd(const unsigned char *src, unsigned srclen, char *dst, size_t size)
++{
++ char tmp[sizeof("32.255.255.255.255")];
++ int len;
++
++ switch (srclen) {
++ case 2:
++ len = sprintf (tmp, "%u.%u", src[0], src[1]);
++ break;
++ case 3:
++ len = sprintf (tmp, "%u.%u.%u", src[0], src[1], src[2]);
++ break;
++ case 4:
++ len = sprintf (tmp, "%u.%u.%u.%u", src[0], src[1], src[2], src[3]);
++ break;
++ case 5:
++ len = sprintf (tmp, "%u.%u.%u.%u.%u", src[0], src[1], src[2], src[3], src[4]);
++ break;
++ default:
++ return NULL;
++ }
++ if (len < 0)
++ return NULL;
++
++ if (len > size) {
++ errno = ENOSPC;
++ return NULL;
++ }
++
++ return strcpy (dst, tmp);
++}
++
++/* pdestdesc() turns an iaddr structure into a printable dest. descriptor */
++const char *
++pdestdesc(const struct iaddr addr) {
++ static char pbuf[sizeof("255.255.255.255.255")];
++
++ if (addr.len == 0) {
++ return "<null destination descriptor>";
++ }
++ if (addr.len == 1) {
++ return "0";
++ }
++ if ((addr.len >= 2) && (addr.len <= 5)) {
++ return inet_ntopdd(addr.iabuf, addr.len, pbuf, sizeof(pbuf));
++ }
++
++ log_fatal("pdestdesc():%s:%d: Invalid destination descriptor length %d.",
++ MDL, addr.len);
++ /* quell compiler warnings */
++ return NULL;
++}
++
+ /* piaddr() turns an iaddr structure into a printable address. */
+ /* XXX: should use a const pointer rather than passing the structure */
+ const char *
+diff --git a/common/options.c b/common/options.c
+index 92c8fee..66433c4 100644
+--- a/common/options.c
++++ b/common/options.c
+@@ -734,7 +734,11 @@ cons_options(struct packet *inpacket, struct dhcp_packet *outpacket,
+ * packet.
+ */
+ priority_list[priority_len++] = DHO_SUBNET_MASK;
+- priority_list[priority_len++] = DHO_ROUTERS;
++ if (lookup_option(&dhcp_universe, cfg_options,
++ DHO_CLASSLESS_STATIC_ROUTES))
++ priority_list[priority_len++] = DHO_CLASSLESS_STATIC_ROUTES;
++ else
++ priority_list[priority_len++] = DHO_ROUTERS;
+ priority_list[priority_len++] = DHO_DOMAIN_NAME_SERVERS;
+ priority_list[priority_len++] = DHO_HOST_NAME;
+ priority_list[priority_len++] = DHO_FQDN;
+@@ -1812,6 +1816,7 @@ const char *pretty_print_option (option, data, len, emit_commas, emit_quotes)
+ unsigned long tval;
+ isc_boolean_t a_array = ISC_FALSE;
+ int len_used;
++ unsigned int octets = 0;
+
+ if (emit_commas)
+ comma = ',';
+@@ -1820,6 +1825,7 @@ const char *pretty_print_option (option, data, len, emit_commas, emit_quotes)
+
+ memset (enumbuf, 0, sizeof enumbuf);
+
++ if (option->format[0] != 'R') { /* see explanation lower */
+ /* Figure out the size of the data. */
+ for (l = i = 0; option -> format [i]; i++, l++) {
+ if (l >= sizeof(fmtbuf) - 1)
+@@ -2029,6 +2035,33 @@ const char *pretty_print_option (option, data, len, emit_commas, emit_quotes)
+ if (numhunk < 0)
+ numhunk = 1;
+
++ } else { /* option->format[i] == 'R') */
++ /* R (destination descriptor) has variable length.
++ * We can find it only in classless static route option,
++ * so we are for sure parsing classless static route option now.
++ * We go through whole the option to check whether there are no
++ * missing/extra bytes.
++ * I didn't find out how to improve the existing code and that's the
++ * reason for this separate 'else' where I do my own checkings.
++ * I know it's little bit unsystematic, but it works.
++ */
++ numhunk = 0;
++ numelem = 2; /* RI */
++ fmtbuf[0]='R'; fmtbuf[1]='I'; fmtbuf[2]=0;
++ for (i =0; i < len; i = i + octets + 5) {
++ if (data[i] > 32) { /* subnet mask width */
++ log_error ("wrong subnet mask width in destination descriptor");
++ break;
++ }
++ numhunk++;
++ octets = ((data[i]+7) / 8);
++ }
++ if (i != len) {
++ log_error ("classless static routes option has wrong size or "
++ "there's some garbage in format");
++ }
++ }
++
+ /* Cycle through the array (or hunk) printing the data. */
+ for (i = 0; i < numhunk; i++) {
+ if ((a_array == ISC_TRUE) && (i != 0) && (numelem > 0)) {
+@@ -2197,6 +2230,20 @@ const char *pretty_print_option (option, data, len, emit_commas, emit_quotes)
+ strcpy(op, piaddr(iaddr));
+ dp += 4;
+ break;
++
++ case 'R':
++ if (dp[0] <= 32)
++ iaddr.len = (((dp[0]+7)/8)+1);
++ else {
++ log_error ("wrong subnet mask width in destination descriptor");
++ return "<error>";
++ }
++
++ memcpy(iaddr.iabuf, dp, iaddr.len);
++ strcpy(op, pdestdesc(iaddr));
++ dp += iaddr.len;
++ break;
++
+ case '6':
+ iaddr.len = 16;
+ memcpy(iaddr.iabuf, dp, 16);
+diff --git a/common/parse.c b/common/parse.c
+index b123a6c..7cf4f2a 100644
+--- a/common/parse.c
++++ b/common/parse.c
+@@ -344,6 +344,39 @@ int parse_ip_addr (cfile, addr)
+ return 0;
+ }
+
++/*
++ * destination-descriptor :== NUMBER DOT NUMBER |
++ * NUMBER DOT NUMBER DOT NUMBER |
++ * NUMBER DOT NUMBER DOT NUMBER DOT NUMBER |
++ * NUMBER DOT NUMBER DOT NUMBER DOT NUMBER DOT NUMBER
++ */
++
++int parse_destination_descriptor (cfile, addr)
++ struct parse *cfile;
++ struct iaddr *addr;
++{
++ unsigned int mask_width, dest_dest_len;
++ addr -> len = 0;
++ if (parse_numeric_aggregate (cfile, addr -> iabuf,
++ &addr -> len, DOT, 10, 8)) {
++ mask_width = (unsigned int)addr->iabuf[0];
++ dest_dest_len = (((mask_width+7)/8)+1);
++ if (mask_width > 32) {
++ parse_warn (cfile,
++ "subnet mask width (%u) greater than 32.", mask_width);
++ }
++ else if (dest_dest_len != addr->len) {
++ parse_warn (cfile,
++ "destination descriptor with subnet mask width %u "
++ "should have %u octets, but has %u octets.",
++ mask_width, dest_dest_len, addr->len);
++ }
++
++ return 1;
++ }
++ return 0;
++}
++
+ /*
+ * Return true if every character in the string is hexadecimal.
+ */
+@@ -724,8 +757,10 @@ unsigned char *parse_numeric_aggregate (cfile, buf,
+ if (count) {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token != separator) {
+- if (!*max)
++ if (!*max) {
++ *max = count;
+ break;
++ }
+ if (token != RBRACE && token != LBRACE)
+ token = next_token (&val,
+ (unsigned *)0,
+@@ -1672,6 +1707,9 @@ int parse_option_code_definition (cfile, option)
+ case IP_ADDRESS:
+ type = 'I';
+ break;
++ case DESTINATION_DESCRIPTOR:
++ type = 'R';
++ break;
+ case IP6_ADDRESS:
+ type = '6';
+ break;
+@@ -5124,6 +5162,15 @@ int parse_option_token (rv, cfile, fmt, expr, uniform, lookups)
+ }
+ break;
+
++ case 'R': /* destination descriptor */
++ if (!parse_destination_descriptor (cfile, &addr)) {
++ return 0;
++ }
++ if (!make_const_data (&t, addr.iabuf, addr.len, 0, 1, MDL)) {
++ return 0;
++ }
++ break;
++
+ case '6': /* IPv6 address. */
+ if (!parse_ip6_addr(cfile, &addr)) {
+ return 0;
+@@ -5401,6 +5448,13 @@ int parse_option_decl (oc, cfile)
+ goto exit;
+ len = ip_addr.len;
+ dp = ip_addr.iabuf;
++ goto alloc;
++
++ case 'R': /* destination descriptor */
++ if (!parse_destination_descriptor (cfile, &ip_addr))
++ goto exit;
++ len = ip_addr.len;
++ dp = ip_addr.iabuf;
+
+ alloc:
+ if (hunkix + len > sizeof hunkbuf) {
+diff --git a/common/tables.c b/common/tables.c
+index ce12fcd..96521a6 100644
+--- a/common/tables.c
++++ b/common/tables.c
+@@ -45,6 +45,7 @@ HASH_FUNCTIONS (option_code, const unsigned *, struct option,
+ Format codes:
+
+ I - IPv4 address
++ R - destination descriptor (RFC3442)
+ 6 - IPv6 address
+ l - 32-bit signed integer
+ L - 32-bit unsigned integer
+@@ -223,6 +224,7 @@ static struct option dhcp_options[] = {
+ #endif
+ { "subnet-selection", "I", &dhcp_universe, 118, 1 },
+ { "domain-search", "D", &dhcp_universe, 119, 1 },
++ { "classless-static-routes", "RIA", &dhcp_universe, 121, 1 },
+ { "vivco", "Evendor-class.", &dhcp_universe, 124, 1 },
+ { "vivso", "Evendor.", &dhcp_universe, 125, 1 },
+ #if 0
+diff --git a/includes/dhcp.h b/includes/dhcp.h
+index cafe172..5a73129 100644
+--- a/includes/dhcp.h
++++ b/includes/dhcp.h
+@@ -159,6 +159,7 @@ struct dhcp_packet {
+ #define DHO_V6_ONLY_PREFERRED 108 /* RFC8925 */
+ #define DHO_SUBNET_SELECTION 118 /* RFC3011! */
+ #define DHO_DOMAIN_SEARCH 119 /* RFC3397 */
++#define DHO_CLASSLESS_STATIC_ROUTES 121 /* RFC3442 */
+ #define DHO_VIVCO_SUBOPTIONS 124
+ #define DHO_VIVSO_SUBOPTIONS 125
+
+diff --git a/includes/dhcpd.h b/includes/dhcpd.h
+index 4a57002..25e1c72 100644
+--- a/includes/dhcpd.h
++++ b/includes/dhcpd.h
+@@ -2967,6 +2967,7 @@ isc_result_t range2cidr(struct iaddrcidrnetlist **result,
+ const struct iaddr *lo, const struct iaddr *hi);
+ isc_result_t free_iaddrcidrnetlist(struct iaddrcidrnetlist **result);
+ const char *piaddr (struct iaddr);
++const char *pdestdesc (struct iaddr);
+ char *piaddrmask(struct iaddr *, struct iaddr *);
+ char *piaddrcidr(const struct iaddr *, unsigned int);
+ u_int16_t validate_port(char *);
+@@ -3189,6 +3190,7 @@ void parse_client_lease_declaration (struct parse *,
+ int parse_option_decl (struct option_cache **, struct parse *);
+ void parse_string_list (struct parse *, struct string_list **, int);
+ int parse_ip_addr (struct parse *, struct iaddr *);
++int parse_destination_descriptor (struct parse *, struct iaddr *);
+ int parse_ip_addr_with_subnet(struct parse *, struct iaddrmatch *);
+ void parse_reject_statement (struct parse *, struct client_config *);
+
+diff --git a/includes/dhctoken.h b/includes/dhctoken.h
+index 6daa422..3f5334e 100644
+--- a/includes/dhctoken.h
++++ b/includes/dhctoken.h
+@@ -378,7 +378,8 @@ enum dhcp_token {
+ TOKEN_OCTAL = 678,
+ KEY_ALGORITHM = 679,
+ BOOTP_BROADCAST_ALWAYS = 680,
+- DISCONNECT = 681
++ DESTINATION_DESCRIPTOR = 681,
++ DISCONNECT = 682
+ };
+
+ #define is_identifier(x) ((x) >= FIRST_TOKEN && \
+--
+2.35.1
+