You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
440 lines
14 KiB
440 lines
14 KiB
From 01b1dcfef129a4eccfaf0f63a216774019f82dca 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/26] 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 "<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 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 "<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 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
|
|
|