From cc3c3560f63b5c5f167306108b7dc96fc3b72437 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Wed, 5 Jun 2013 16:23:24 +0000 Subject: [PATCH] Add DHPCv6 FQDN support (RFC4704) Improve hostname support in general, so we are aware of hostname changes. When setting the hostname, prefer the FQDN name or append a domain. --- common.c | 14 ++++++++++ common.h | 5 ++++ dhcp-common.c | 44 +++++++++++++++++++++++++++++++ dhcp-common.h | 1 + dhcp.c | 57 ++++++++++++++-------------------------- dhcp6.c | 37 +++++++++++++++++++++++--- dhcp6.h | 1 + dhcpcd-hooks/30-hostname | 20 +++++++++++--- dhcpcd.8.in | 6 ++--- dhcpcd.conf.5.in | 6 +++-- if-options.c | 32 +++++++++------------- if-options.h | 2 ++ 12 files changed, 157 insertions(+), 68 deletions(-) diff --git a/common.c b/common.c index ab3a0580..4f277958 100644 --- a/common.c +++ b/common.c @@ -60,6 +60,7 @@ # define _PATH_DEVNULL "/dev/null" #endif +static char hostname_buffer[HOSTNAME_MAX_LEN]; int clock_monotonic; static char *lbuf; static size_t lbuf_len; @@ -134,6 +135,19 @@ set_nonblock(int fd) return 0; } +const char * +get_hostname(void) +{ + + gethostname(hostname_buffer, sizeof(hostname_buffer)); + if (strcmp(hostname_buffer, "(none)") == 0 || + strcmp(hostname_buffer, "localhost") == 0 || + strncmp(hostname_buffer, "localhost.", strlen("localhost.")) == 0 || + hostname_buffer[0] == '.') + return NULL; + return hostname_buffer; +} + /* Handy function to get the time. * We only care about time advancements, not the actual time itself * Which is why we use CLOCK_MONOTONIC, but it is not available on all diff --git a/common.h b/common.h index 0e5bd041..b7851cb4 100644 --- a/common.h +++ b/common.h @@ -34,6 +34,10 @@ #include "config.h" #include "defs.h" +#ifndef HOSTNAME_MAX_LEN +#define HOSTNAME_MAX_LEN 250 /* 255 - 3 (FQDN) - 2 (DNS enc) */ +#endif + #define UNCONST(a) ((void *)(unsigned long)(const void *)(a)) #define timeval_to_double(tv) ((tv)->tv_sec * 1.0 + (tv)->tv_usec * 1.0e-6) @@ -96,6 +100,7 @@ int set_cloexec(int); int set_nonblock(int); char *get_line(FILE * __restrict); +const char *get_hostname(void); extern int clock_monotonic; int get_monotonic(struct timeval *); ssize_t setvar(char ***, const char *, const char *, const char *); diff --git a/dhcp-common.c b/dhcp-common.c index 6c9ab780..d8c5ae62 100644 --- a/dhcp-common.c +++ b/dhcp-common.c @@ -89,6 +89,50 @@ int make_option_mask(const struct dhcp_opt *dopts, return 0; } +size_t +encode_rfc1035(const char *src, uint8_t *dst) +{ + uint8_t *p; + uint8_t *lp; + size_t len; + uint8_t has_dot; + + if (src == NULL || *src == '\0') + return 0; + if (dst) { + p = dst; + lp = p++; + } + len = 1; + has_dot = 0; + for (; *src; src++) { + if (*src == '\0') + break; + if (*src == '.') { + /* Skip the trailing . */ + if (src[1] == '\0') + break; + has_dot = 1; + if (dst) { + *lp = p - lp - 1; + if (*lp == '\0') + return len; + lp = p++; + } + } else if (dst) + *p++ = (uint8_t)*src; + len++; + } + if (dst) { + *lp = p - lp - 1; + if (has_dot) + *p++ = '\0'; + } + if (has_dot) + len++; + return len; +} + /* Decode an RFC3397 DNS search order option into a space * separated string. Returns length of string (including * terminating zero) or zero on error. out may be NULL diff --git a/dhcp-common.h b/dhcp-common.h index 50e69b96..22f98bc1 100644 --- a/dhcp-common.h +++ b/dhcp-common.h @@ -67,6 +67,7 @@ struct dhcp_opt { #define del_option_mask(var, val) (var[val >> 3] &= ~(1 << (val & 7))) #define has_option_mask(var, val) (var[val >> 3] & (1 << (val & 7))) int make_option_mask(const struct dhcp_opt *,uint8_t *, const char *, int); +size_t encode_rfc1035(const char *src, uint8_t *dst); ssize_t decode_rfc3397(char *, ssize_t, int, const uint8_t *); ssize_t print_string(char *, ssize_t, int, const uint8_t *); ssize_t print_option(char *, ssize_t, int, int, const uint8_t *, const char *); diff --git a/dhcp.c b/dhcp.c index 5514f3aa..8180a40e 100644 --- a/dhcp.c +++ b/dhcp.c @@ -801,33 +801,6 @@ get_option_routes(struct interface *ifp, const struct dhcp_message *dhcp) return routes; } -static size_t -encode_rfc1035(const char *src, uint8_t *dst) -{ - uint8_t *p = dst; - uint8_t *lp = p++; - - if (*src == '\0') - return 0; - for (; *src; src++) { - if (*src == '\0') - break; - if (*src == '.') { - /* Skip the trailing . */ - if (src[1] == '\0') - break; - *lp = p - lp - 1; - if (*lp == '\0') - return p - dst; - lp = p++; - } else - *p++ = (uint8_t)*src; - } - *lp = p - lp - 1; - *p++ = '\0'; - return p - dst; -} - #define PUTADDR(_type, _val) \ { \ *p++ = _type; \ @@ -877,6 +850,7 @@ make_message(struct dhcp_message **message, const struct dhcp_state *state = D_CSTATE(iface); const struct dhcp_lease *lease = &state->lease; time_t up = uptime() - state->start_uptime; + const char *hostname; dhcp = calloc(1, sizeof (*dhcp)); if (dhcp == NULL) @@ -1006,18 +980,22 @@ make_message(struct dhcp_message **message, * upto the first dot (the short hostname) as otherwise * confuses some DHCP servers when updating DNS. * The FQDN option should be used if a FQDN is required. */ - if (ifo->options & DHCPCD_HOSTNAME && ifo->hostname[0]) { + if (ifo->hostname[0] == '\0') + hostname = get_hostname(); + else + hostname = ifo->hostname; + if (ifo->options & DHCPCD_HOSTNAME && hostname) { *p++ = DHO_HOSTNAME; - hp = strchr(ifo->hostname, '.'); + hp = strchr(hostname, '.'); if (hp) - len = hp - ifo->hostname; + len = hp - hostname; else - len = strlen(ifo->hostname); + len = strlen(hostname); *p++ = len; - memcpy(p, ifo->hostname, len); + memcpy(p, hostname, len); p += len; } - if (ifo->fqdn != FQDN_DISABLE && ifo->hostname[0]) { + if (ifo->fqdn != FQDN_DISABLE) { /* IETF DHC-FQDN option (81), RFC4702 */ *p++ = DHO_FQDN; lp = p; @@ -1032,12 +1010,17 @@ make_message(struct dhcp_message **message, * N: 1 => Client requests Server to not * update DNS */ - *p++ = (ifo->fqdn & 0x09) | 0x04; + if (hostname) + *p++ = (ifo->fqdn & 0x09) | 0x04; + else + *p++ = (FQDN_NONE & 0x09) | 0x04; *p++ = 0; /* from server for PTR RR */ *p++ = 0; /* from server for A RR if S=1 */ - ul = encode_rfc1035(ifo->hostname, p); - *lp += ul; - p += ul; + if (hostname) { + ul = encode_rfc1035(hostname, p); + *lp += ul; + p += ul; + } } /* vendor is already encoded correctly, so just add it */ diff --git a/dhcp6.c b/dhcp6.c index 026cc6c3..b0fe4393 100644 --- a/dhcp6.c +++ b/dhcp6.c @@ -126,6 +126,7 @@ const struct dhcp_opt dhcp6_opts[] = { { D6_OPTION_BCMS_SERVER_A, IPV6A, "bcms_server_a" }, { D6_OPTION_POSIX_TIMEZONE, STRING, "posix_timezone" }, { D6_OPTION_TZDB_TIMEZONE, STRING, "tzdb_timezone" }, + { D6_OPTION_FQDN, RFC3397, "fqdn" }, { 0, 0, NULL } }; @@ -361,6 +362,7 @@ dhcp6_makemessage(struct interface *ifp) uint8_t IA, *p; uint32_t u32; const struct ipv6_addr *ap; + const char *hostname; state = D6_STATE(ifp); if (state->send) { @@ -379,8 +381,16 @@ dhcp6_makemessage(struct interface *ifp) len += sizeof(*u16); } if (len == 0) - len = sizeof(*u16) * 3; + len = sizeof(*u16) * 4; len += sizeof(*o); + + if (ifo->fqdn != FQDN_DISABLE) { + if (ifo->hostname[0] == '\0') + hostname = get_hostname(); + else + hostname = ifo->hostname; + len += sizeof(*o) + 1 + encode_rfc1035(hostname, NULL); + } } len += sizeof(*state->send); @@ -568,6 +578,26 @@ dhcp6_makemessage(struct interface *ifp) } if (state->send->type != DHCP6_RELEASE) { + if (ifo->fqdn != FQDN_DISABLE) { + o = D6_NEXT_OPTION(o); + o->code = htons(D6_OPTION_FQDN); + p = D6_OPTION_DATA(o); + switch (ifo->fqdn) { + case FQDN_BOTH: + *p = 0x01; + break; + case FQDN_PTR: + *p = 0x00; + break; + default: + *p = 0x04; + break; + } + o->len = encode_rfc1035(hostname, p + 1); + if (o->len == 0) + *p = 0x04; + o->len = htons(++o->len); + } o = D6_NEXT_OPTION(o); o->code = htons(D6_OPTION_ORO); o->len = 0; @@ -581,10 +611,11 @@ dhcp6_makemessage(struct interface *ifp) } } if (o->len == 0) { + *u16++ = htons(D6_OPTION_UNICAST); *u16++ = htons(D6_OPTION_DNS_SERVERS); *u16++ = htons(D6_OPTION_DOMAIN_LIST); - *u16++ = htons(D6_OPTION_UNICAST); - o->len = sizeof(*u16) * 3; + *u16++ = htons(D6_OPTION_FQDN); + o->len = sizeof(*u16) * 4; } o->len = htons(o->len); } diff --git a/dhcp6.h b/dhcp6.h index 7189145d..de6e6499 100644 --- a/dhcp6.h +++ b/dhcp6.h @@ -79,6 +79,7 @@ #define D6_OPTION_INFO_REFRESH_TIME 32 #define D6_OPTION_BCMS_SERVER_D 33 #define D6_OPTION_BCMS_SERVER_A 34 +#define D6_OPTION_FQDN 39 #define D6_OPTION_POSIX_TIMEZONE 41 #define D6_OPTION_TZDB_TIMEZONE 42 diff --git a/dhcpcd-hooks/30-hostname b/dhcpcd-hooks/30-hostname index 35d7e867..77ae5fce 100644 --- a/dhcpcd-hooks/30-hostname +++ b/dhcpcd-hooks/30-hostname @@ -65,15 +65,27 @@ try_hostname() set_hostname() { - if need_hostname; then - if [ -n "$new_host_name" ]; then + + need_hostname || return + + if [ -n "$new_fqdn_name" ]; then + try_hostname "$new_fqdn_name" + elif [ -n "$new_host_name" ]; then + if [ -n "$new_domain_name" ]; then + try_hostname "$new_host_name.$new_domain_name" + else try_hostname "$new_host_name" - elif [ -n "$new_fqdn_name" ]; then - try_hostname "$new_fqdn_name" fi fi } +# For ease of use, map DHCP6 names onto our DHCP4 names +case "$reason" in +BOUND6|RENEW6|REBIND6|REBOOT6|INFORM6) + new_fqdn_name="$new_dhcp6_fqdn" + ;; +esac + if $if_up; then set_hostname fi diff --git a/dhcpcd.8.in b/dhcpcd.8.in index 3e943b06..0a181b71 100644 --- a/dhcpcd.8.in +++ b/dhcpcd.8.in @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd May 3, 2013 +.Dd June 5, 2013 .Dt DHCPCD 8 .Os .Sh NAME @@ -604,8 +604,8 @@ running on the .Xr resolvconf 8 .Sh STANDARDS RFC 951, RFC 1534, RFC 2131, RFC 2132, RFC 2855, RFC 3004, RFC 3315,RFC 3361, -RFC 3633, RFC 3396, RFC 3397, RFC 3442, RFC 3927, RFC 4361, RFC 4390, RFC 4702, -RFC 4861, RFC 4833, RFC 5227, RFC 5969, RFC 6106. +RFC 3633, RFC 3396, RFC 3397, RFC 3442, RFC 3927, RFC 4361, RFC 4390, RFC 4702, +RFC 4704, RFC 4861, RFC 4833, RFC 5227, RFC 5969, RFC 6106. .Sh AUTHORS .An Roy Marples Aq roy@marples.name .Sh BUGS diff --git a/dhcpcd.conf.5.in b/dhcpcd.conf.5.in index ff698f5e..4b0d5c0c 100644 --- a/dhcpcd.conf.5.in +++ b/dhcpcd.conf.5.in @@ -180,9 +180,11 @@ IPv6RS should be disabled globally when requesting a Prefix Delegation like so: Only configure IPv4. .It Ic ipv6only Only confgiure IPv6. -.It Ic fqdn Op none | ptr | both -none disables FQDN encoding, ptr just asks the DHCP server to update the PTR +.It Ic fqdn Op disable | ptr | both +ptr just asks the DHCP server to update the PTR record of the host in DNS whereas both also updates the A record. +disable will disable the FQDN option. +The default is both. .Nm dhcpcd itself never does any DNS updates. .Nm dhcpcd diff --git a/if-options.c b/if-options.c index c9f56ad5..509a9360 100644 --- a/if-options.c +++ b/if-options.c @@ -459,20 +459,20 @@ parse_option(struct if_options *ifo, int opt, const char *arg) add_environ(ifo, arg, 1); break; case 'h': - if (arg) { - s = parse_string(ifo->hostname, - HOSTNAME_MAX_LEN, arg); - if (s == -1) { - syslog(LOG_ERR, "hostname: %m"); - return -1; - } - if (s != 0 && ifo->hostname[0] == '.') { - syslog(LOG_ERR, - "hostname cannot begin with ."); - return -1; - } - ifo->hostname[s] = '\0'; + if (!arg) { + ifo->options |= DHCPCD_HOSTNAME; + break; } + s = parse_string(ifo->hostname, HOSTNAME_MAX_LEN, arg); + if (s == -1) { + syslog(LOG_ERR, "hostname: %m"); + return -1; + } + if (s != 0 && ifo->hostname[0] == '.') { + syslog(LOG_ERR, "hostname cannot begin with ."); + return -1; + } + ifo->hostname[s] = '\0'; if (ifo->hostname[0] == '\0') ifo->options &= ~DHCPCD_HOSTNAME; else @@ -1131,12 +1131,6 @@ read_config(const char *file, ifo->reboot = DEFAULT_REBOOT; ifo->metric = -1; strlcpy(ifo->script, SCRIPT, sizeof(ifo->script)); - gethostname(ifo->hostname, HOSTNAME_MAX_LEN); - /* Ensure that the hostname is NULL terminated */ - ifo->hostname[HOSTNAME_MAX_LEN] = '\0'; - if (strcmp(ifo->hostname, "(none)") == 0 || - strcmp(ifo->hostname, "localhost") == 0) - ifo->hostname[0] = '\0'; ifo->vendorclassid[0] = strlen(vendor); memcpy(ifo->vendorclassid + 1, vendor, ifo->vendorclassid[0]); diff --git a/if-options.h b/if-options.h index 1efb6e47..9cc91e71 100644 --- a/if-options.h +++ b/if-options.h @@ -45,7 +45,9 @@ #define DEFAULT_TIMEOUT 30 #define DEFAULT_REBOOT 5 +#ifndef HOSTNAME_MAX_LEN #define HOSTNAME_MAX_LEN 250 /* 255 - 3 (FQDN) - 2 (DNS enc) */ +#endif #define VENDORCLASSID_MAX_LEN 255 #define CLIENTID_MAX_LEN 48 #define USERCLASS_MAX_LEN 255