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