From 2756fcc3f88c27d0e12e72dbdd2906fbf45f2362 Mon Sep 17 00:00:00 2001 From: Pavel Zhukov Date: Thu, 21 Feb 2019 10:32:35 +0100 Subject: [PATCH 12/21] RFC 3442 - Classless Static Route Option for DHCPv4 (#516325) Cc: pzhukov@redhat.com (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 | 5 +++-- 9 files changed, 219 insertions(+), 6 deletions(-) diff --git a/client/clparse.c b/client/clparse.c index 44387ed..862e4f9 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]; @@ -87,7 +87,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); @@ -141,6 +145,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 d9e1197..2343b19 100644 --- a/common/dhcp-options.5 +++ b/common/dhcp-options.5 @@ -110,6 +110,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 @@ -1086,6 +1106,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 c4da73c..981fb92 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 ""; + } + 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 fc0e088..3034cf0 100644 --- a/common/options.c +++ b/common/options.c @@ -729,7 +729,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; @@ -1804,6 +1808,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 = ','; @@ -1812,6 +1817,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) @@ -2004,6 +2010,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)) { @@ -2159,6 +2192,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 ""; + } + + 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 3ac4ebf..f17bc0b 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; @@ -5101,6 +5139,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; @@ -5378,6 +5425,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 d2294c0..f1be07d 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 @@ -216,6 +217,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 0a74137..95bf539 100644 --- a/includes/dhcp.h +++ b/includes/dhcp.h @@ -158,6 +158,7 @@ struct dhcp_packet { #define DHO_ASSOCIATED_IP 92 #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 3632a6b..2ac39ae 100644 --- a/includes/dhcpd.h +++ b/includes/dhcpd.h @@ -2951,6 +2951,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 *); @@ -3169,6 +3170,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 7e7215a..b4d93ba 100644 --- a/includes/dhctoken.h +++ b/includes/dhctoken.h @@ -376,8 +376,9 @@ enum dhcp_token { LEASE_ID_FORMAT = 676, TOKEN_HEX = 677, TOKEN_OCTAL = 678, - KEY_ALGORITHM = 679 - BOOTP_BROADCAST_ALWAYS = 680 + KEY_ALGORITHM = 679, + BOOTP_BROADCAST_ALWAYS = 680, + DESTINATION_DESCRIPTOR = 681 }; #define is_identifier(x) ((x) >= FIRST_TOKEN && \ -- 2.14.5