// SPDX-License-Identifier: GPL-2.0-or-later /* PASST - Plug A Simple Socket Transport * for qemu/UNIX domain socket mode * * PASTA - Pack A Subtle Tap Abstraction * for network namespace/tap device mode * * dhcp.c - Minimalistic DHCP server for PASST * * Copyright (c) 2020-2021 Red Hat GmbH * Author: Stefano Brivio */ #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "ip.h" #include "checksum.h" #include "packet.h" #include "passt.h" #include "tap.h" #include "log.h" #include "dhcp.h" /* RFC 3396: maximum option data that can be split across options field, * file field, and sname field (minus code+length overhead per portion). */ #define OPT_CONCAT_MAX 496 /** * enum opt_state - DHCP option state * @OPT_UNSET: Option not configured * @OPT_DEFAULT: Option set from host config * @OPT_USER: Option set via --dhcp-opt command line */ enum opt_state { OPT_UNSET, OPT_DEFAULT, OPT_USER, }; /** * struct opt - DHCP option * @sent: Convenience flag, set while filling replies * @slen: Length of option defined for server * @s: Option payload from server * @clen: Length of option received from client, -1 if not received * @c: Option payload from client * @state: Option state (unset, default, or user) */ struct opt { int sent; int slen; uint8_t s[OPT_CONCAT_MAX]; int clen; uint8_t c[255]; enum opt_state state; }; static struct opt opts[256]; #define DHCPDISCOVER 1 #define DHCPOFFER 2 #define DHCPREQUEST 3 #define DHCPDECLINE 4 #define DHCPACK 5 #define DHCPNAK 6 #define DHCPRELEASE 7 #define DHCPINFORM 8 #define DHCPFORCERENEW 9 #define OPT_MIN 60 /* RFC 951 */ /* Total option size (excluding end option) is 576 (RFC 2131), minus * offset of options (268), minus end option (1). */ #define OPT_MAX 307 /** * dhcp_init() - Initialise DHCP options */ void dhcp_init(void) { int i; for (i = 0; i < ARRAY_SIZE(opts); i++) if (opts[i].state != OPT_USER) opts[i].state = OPT_UNSET; if (opts[1].state != OPT_USER) /* Mask */ opts[1] = (struct opt) { 0, 4, { 0 }, 0, { 0 }, OPT_DEFAULT, }; if (opts[3].state != OPT_USER) /* Router */ opts[3] = (struct opt) { 0, 4, { 0 }, 0, { 0 }, OPT_DEFAULT, }; if (opts[51].state != OPT_USER) /* Lease time */ opts[51] = (struct opt) { 0, 4, { 0xff, 0xff, 0xff, 0xff }, 0, { 0 }, OPT_DEFAULT, }; if (opts[53].state != OPT_USER) /* Type */ opts[53] = (struct opt) { 0, 1, { 0 }, 0, { 0 }, OPT_DEFAULT, }; if (opts[54].state != OPT_USER) /* Server ID */ opts[54] = (struct opt) { 0, 4, { 0 }, 0, { 0 }, OPT_DEFAULT, }; } /** * struct msg - BOOTP/DHCP message * @op: BOOTP message type * @htype: Hardware address type * @hlen: Hardware address length * @hops: DHCP relay hops * @xid: Transaction ID randomly chosen by client * @secs: Seconds elapsed since beginning of acquisition or renewal * @flags: DHCP message flags * @ciaddr: Client IP address in BOUND, RENEW, REBINDING * @yiaddr: IP address being offered or assigned * @siaddr: Next server to use in bootstrap * @giaddr: Relay agent IP address * @chaddr: Client hardware address * @sname: Server host name * @file: Boot file name * @magic: Magic cookie prefix before options * @o: Options */ struct msg { uint8_t op; #define BOOTREQUEST 1 #define BOOTREPLY 2 uint8_t htype; uint8_t hlen; uint8_t hops; uint32_t xid; uint16_t secs; uint16_t flags; #define FLAG_BROADCAST htons_constant(0x8000) uint32_t ciaddr; struct in_addr yiaddr; uint32_t siaddr; uint32_t giaddr; uint8_t chaddr[16]; uint8_t sname[64]; uint8_t file[128]; uint32_t magic; uint8_t o[OPT_MAX + 1 /* End option */ ]; } __attribute__((__packed__)); /** * enum dhcp_opt_type - DHCP option value types per RFC 2132 * @DHCP_OPT_NONE: Unsupported or unknown option * @DHCP_OPT_STR: Variable-length string * @DHCP_OPT_IPV4: Single IPv4 address * @DHCP_OPT_IPV4_LIST: Multiple IPv4 addresses, comma-separated * @DHCP_OPT_UINT8: Unsigned 8-bit integer * @DHCP_OPT_UINT16: Unsigned 16-bit integer * @DHCP_OPT_UINT32: Unsigned 32-bit integer * @DHCP_OPT_INT32: Signed 32-bit integer * @DHCP_OPT_STR_CONCAT:Concatenation-requiring string (RFC 3396) */ enum dhcp_opt_type { DHCP_OPT_NONE, DHCP_OPT_STR, DHCP_OPT_IPV4, DHCP_OPT_IPV4_LIST, DHCP_OPT_UINT8, DHCP_OPT_UINT16, DHCP_OPT_UINT32, DHCP_OPT_INT32, DHCP_OPT_STR_CONCAT, }; /** * dhcp_opt_types - Maps option code to RFC 2132 value type, indexed by code */ static const enum dhcp_opt_type dhcp_opt_types[] = { [1] = DHCP_OPT_IPV4, /* Subnet Mask */ [2] = DHCP_OPT_INT32, /* Time Offset */ [3] = DHCP_OPT_IPV4_LIST, /* Router */ [4] = DHCP_OPT_IPV4_LIST, /* Time Server */ [5] = DHCP_OPT_IPV4_LIST, /* Name Server */ [6] = DHCP_OPT_IPV4_LIST, /* Domain Name Server */ [7] = DHCP_OPT_IPV4_LIST, /* Log Server */ [8] = DHCP_OPT_IPV4_LIST, /* Cookie Server */ [9] = DHCP_OPT_IPV4_LIST, /* LPR Server */ [10] = DHCP_OPT_IPV4_LIST, /* Impress Server */ [11] = DHCP_OPT_IPV4_LIST, /* Resource Location Server */ [12] = DHCP_OPT_STR, /* Host Name */ [13] = DHCP_OPT_UINT16, /* Boot File Size */ [15] = DHCP_OPT_STR, /* Domain Name */ [16] = DHCP_OPT_IPV4, /* Swap Server */ [17] = DHCP_OPT_STR, /* Root Path */ [19] = DHCP_OPT_UINT8, /* IP Forwarding */ [23] = DHCP_OPT_UINT8, /* Default IP TTL */ [26] = DHCP_OPT_UINT16, /* Interface MTU */ [28] = DHCP_OPT_IPV4, /* Broadcast Address */ [37] = DHCP_OPT_UINT8, /* TCP Default TTL */ [38] = DHCP_OPT_UINT32, /* TCP Keepalive Interval */ [40] = DHCP_OPT_STR, /* NIS Domain Name */ [41] = DHCP_OPT_IPV4_LIST, /* NIS Servers */ [42] = DHCP_OPT_IPV4_LIST, /* NTP Servers */ [44] = DHCP_OPT_IPV4_LIST, /* NetBIOS Name Server */ [50] = DHCP_OPT_IPV4, /* Requested IP Address */ [51] = DHCP_OPT_UINT32, /* IP Address Lease Time */ [53] = DHCP_OPT_UINT8, /* DHCP Message Type */ [54] = DHCP_OPT_IPV4, /* Server Identifier */ [57] = DHCP_OPT_UINT16, /* Max DHCP Message Size */ [58] = DHCP_OPT_UINT32, /* Renewal (T1) Time */ [59] = DHCP_OPT_UINT32, /* Rebinding (T2) Time */ [60] = DHCP_OPT_STR, /* Vendor Class Identifier */ [66] = DHCP_OPT_STR, /* TFTP Server Name */ [67] = DHCP_OPT_STR, /* Bootfile Name */ [252] = DHCP_OPT_STR, /* WPAD URL */ }; /** * dhcp_opt_parse() - Parse a DHCP option value * @code: DHCP option code * @str: Value string from command line * @buf: Output buffer for binary value * @buf_len: Size of output buffer * * Return: number of bytes written to @buf, or -1 on error */ static int dhcp_opt_parse(uint8_t code, const char *str, uint8_t *buf, size_t buf_len) { enum dhcp_opt_type type; unsigned long val; unsigned int i; uint8_t width; size_t slen; size_t len; char *end; if (code >= ARRAY_SIZE(dhcp_opt_types)) return -1; type = dhcp_opt_types[code]; if (!*str) return -1; switch (type) { case DHCP_OPT_NONE: return -1; case DHCP_OPT_IPV4: case DHCP_OPT_IPV4_LIST: len = 0; while (*str) { char ipbuf[INET_ADDRSTRLEN]; size_t chunk; chunk = strcspn(str, ","); if (!chunk || chunk >= sizeof(ipbuf)) return -1; memcpy(ipbuf, str, chunk); ipbuf[chunk] = '\0'; if (len + sizeof(struct in_addr) > buf_len) return -1; if (inet_pton(AF_INET, ipbuf, buf + len) != 1) return -1; len += sizeof(struct in_addr); if (type == DHCP_OPT_IPV4) { if (str[chunk] == ',') return -1; break; } str += chunk + (str[chunk] == ','); } if (!len) return -1; return len; case DHCP_OPT_UINT8: case DHCP_OPT_UINT16: case DHCP_OPT_UINT32: case DHCP_OPT_INT32: if (type == DHCP_OPT_UINT8) width = 1; else if (type == DHCP_OPT_UINT16) width = 2; else width = 4; if (buf_len < width) return -1; errno = 0; if (type == DHCP_OPT_INT32) { long sval; sval = strtol(str, &end, 0); if (*end || errno || sval < INT32_MIN || sval > INT32_MAX) return -1; val = (uint32_t)sval; } else { val = strtoul(str, &end, 0); if (*end || errno || val >= (1ULL << (width * 8))) return -1; } for (i = width; i > 0; i--) { buf[i - 1] = val & 0xff; val >>= 8; } return width; case DHCP_OPT_STR: if (strlen(str) > 255) return -1; /* fallthrough */ case DHCP_OPT_STR_CONCAT: slen = strlen(str); if (slen >= buf_len) return -1; memcpy(buf, str, slen); return slen; } return -1; } /** * dhcp_set_opt() - Parse and store a user-specified DHCP option * @code: DHCP option code * @val_str: Value string from command line */ void dhcp_set_opt(uint8_t code, const char *val_str) { int ret; ret = dhcp_opt_parse(code, val_str, opts[code].s, sizeof(opts[code].s)); if (ret < 0) die("Invalid value for DHCP option %u: %s", code, val_str); opts[code].slen = ret; opts[code].state = OPT_USER; } /** * dhcp_opt_to_str() - Render a binary DHCP option value to a printable string * @code: DHCP option code * @buf: Output string buffer * @buf_len: Size of output buffer * * Return: pointer to @buf if option is user-set, NULL otherwise */ const char *dhcp_opt_to_str(uint8_t code, char *buf, size_t buf_len) { enum dhcp_opt_type type; int off = 0; unsigned int i; if (opts[code].state != OPT_USER || opts[code].slen < 0) return NULL; if (code >= ARRAY_SIZE(dhcp_opt_types)) return NULL; type = dhcp_opt_types[code]; switch (type) { case DHCP_OPT_IPV4: case DHCP_OPT_IPV4_LIST: for (i = 0; i + 4 <= (unsigned int)opts[code].slen; i += 4) { if (off) off += snprintf(buf + off, buf_len - off, ","); inet_ntop(AF_INET, opts[code].s + i, buf + off, buf_len - off); off = strlen(buf); } return buf; case DHCP_OPT_UINT8: if (snprintf(buf, buf_len, "%u", opts[code].s[0]) >= (int)buf_len) return NULL; return buf; case DHCP_OPT_UINT16: if (snprintf(buf, buf_len, "%u", ((unsigned)opts[code].s[0] << 8) | opts[code].s[1]) >= (int)buf_len) return NULL; return buf; case DHCP_OPT_UINT32: if (snprintf(buf, buf_len, "%u", ((unsigned)opts[code].s[0] << 24) | ((unsigned)opts[code].s[1] << 16) | ((unsigned)opts[code].s[2] << 8) | opts[code].s[3]) >= (int)buf_len) return NULL; return buf; case DHCP_OPT_INT32: if (snprintf(buf, buf_len, "%d", (int)(((unsigned)opts[code].s[0] << 24) | ((unsigned)opts[code].s[1] << 16) | ((unsigned)opts[code].s[2] << 8) | opts[code].s[3])) >= (int)buf_len) return NULL; return buf; case DHCP_OPT_STR: if ((size_t)opts[code].slen < buf_len) { memcpy(buf, opts[code].s, opts[code].slen); buf[opts[code].slen] = '\0'; } else { memcpy(buf, opts[code].s, buf_len - 1); buf[buf_len - 1] = '\0'; } return buf; default: return NULL; } } /** * fill_one() - Fill a single option into a buffer * @buf: Buffer to write option * @size: Usable size of @buf (excluding end marker) * @o: Option number * @offset: Current offset within @buf, updated on insertion * * Return: false if @buf has space to write the option, true otherwise */ static bool fill_one(uint8_t *buf, size_t size, int o, int *offset) { size_t slen = opts[o].slen; /* If we don't have space to write the option, then just skip */ if (*offset + 2 /* code and length of option */ + slen > size) return true; buf[*offset] = o; buf[*offset + 1] = slen; /* Move to option */ *offset += 2; memcpy(buf + *offset, opts[o].s, slen); opts[o].sent = 1; *offset += slen; return false; } /** * enum dhcp_overload - DHCP option overload values (RFC 2132, Section 9.3) * @DHCP_OVERLOAD_NONE: No overload * @DHCP_OVERLOAD_FILE: file field carries options * @DHCP_OVERLOAD_SNAME: sname field carries options */ enum dhcp_overload { DHCP_OVERLOAD_NONE, DHCP_OVERLOAD_FILE, DHCP_OVERLOAD_SNAME, }; /** * is_concat_opt() - Check if option requires RFC 3396 concatenation support * @o: Option number * * Return: true if option is a concatenation-requiring type */ static bool is_concat_opt(int o) { if ((size_t)o >= ARRAY_SIZE(dhcp_opt_types)) return false; return dhcp_opt_types[o] == DHCP_OPT_STR_CONCAT; } /** * fill_split() - Write a split portion of an option into a buffer * @buf: Buffer to write into * @size: Usable size of @buf * @o: Option number (code) * @offset: Current offset within @buf, updated on write * @data: Pointer to remaining option data to write * @remaining: Bytes of option data still to write * * Return: number of data bytes written (excluding code+length header) */ static size_t fill_split(uint8_t *buf, size_t size, int o, int *offset, const uint8_t *data, size_t remaining) { size_t avail, chunk; if (*offset + 2 >= (int)size) return 0; avail = size - *offset - 2; chunk = remaining < avail ? remaining : avail; if (!chunk) return 0; buf[*offset] = o; buf[*offset + 1] = chunk; *offset += 2; memcpy(buf + *offset, data, chunk); *offset += chunk; return chunk; } /** * fill() - Fill options in message, with overload into file/sname if needed * @m: Message to fill * @overload: Set to option 52 value (0 if none, 1/2/3 per RFC 2132) * @has_bootfile: Reserve file field for boot file name * * Return: current size of options field */ static int fill(struct msg *m, enum dhcp_overload *overload, bool has_bootfile) { int sname_off = 0, file_off = 0; *overload = DHCP_OVERLOAD_NONE; /* Reserve 3 bytes for option 52 (overload) if needed */ size_t size = OPT_MAX - 3; int i, o, offset = 0; for (o = 0; o < 255; o++) opts[o].sent = 0; /* Some clients (wattcp32, mTCP, maybe some others) expect * option 53 at the beginning of the list. * Put it there explicitly, unless requested via option 55. */ if (opts[55].clen > 0 && !memchr(opts[55].c, 53, opts[55].clen)) if (fill_one(m->o, OPT_MAX, 53, &offset)) debug("DHCP: skipping option 53"); for (i = 0; i < opts[55].clen; i++) { o = opts[55].c[i]; if (opts[o].state != OPT_UNSET) fill_one(m->o, size, o, &offset); } for (o = 0; o < 255; o++) { if (opts[o].state != OPT_UNSET && !opts[o].sent) fill_one(m->o, size, o, &offset); } /* Overflow unsent options into sname, then file */ for (o = 0; (size_t)o < ARRAY_SIZE(opts); o++) { if (opts[o].state == OPT_UNSET || opts[o].sent) continue; fill_one(m->sname, sizeof(m->sname) - 1, o, &sname_off); } for (o = 0; (size_t)o < ARRAY_SIZE(opts); o++) { if (opts[o].state == OPT_UNSET || opts[o].sent) continue; if (!has_bootfile && fill_one(m->file, sizeof(m->file) - 1, o, &file_off)) if (!is_concat_opt(o)) debug("DHCP: skipping option %i" " (overload full)", o); } /* RFC 3396: split concatenation-requiring options that didn't fit * as a single option. Split order: options, file, sname. */ for (o = 0; (size_t)o < ARRAY_SIZE(opts); o++) { size_t file_cap, sname_cap, total, written; if (opts[o].state == OPT_UNSET || opts[o].sent || !is_concat_opt(o)) continue; sname_cap = sizeof(m->sname) - 1 > (size_t)sname_off ? sizeof(m->sname) - 1 - sname_off : 0; if (has_bootfile || sizeof(m->file) - 1 <= (size_t)file_off) file_cap = 0; else file_cap = sizeof(m->file) - 1 - file_off; total = (size > (size_t)offset ? size - offset : 0) + file_cap + sname_cap; if (total < (size_t)opts[o].slen) { debug("DHCP: skipping option %i (no space to split)", o); continue; } written = 0; written += fill_split(m->o, size, o, &offset, opts[o].s, opts[o].slen); if (written < (size_t)opts[o].slen && !has_bootfile) written += fill_split(m->file, sizeof(m->file) - 1, o, &file_off, opts[o].s + written, opts[o].slen - written); if (written < (size_t)opts[o].slen) fill_split(m->sname, sizeof(m->sname) - 1, o, &sname_off, opts[o].s + written, opts[o].slen - written); opts[o].sent = 1; } if (sname_off) { m->sname[sname_off] = 255; *overload |= DHCP_OVERLOAD_SNAME; } if (file_off) { m->file[file_off] = 255; *overload |= DHCP_OVERLOAD_FILE; } if (*overload) { m->o[offset++] = 52; m->o[offset++] = 1; m->o[offset++] = *overload; } m->o[offset++] = 255; if (offset < OPT_MIN) { memset(&m->o[offset], 0, OPT_MIN - offset); offset = OPT_MIN; } return offset; } /** * opt_dns_search_dup_ptr() - Look for possible domain name compression pointer * @buf: Current option buffer with existing labels * @cmp: Portion of domain name being added * @len: Length of current option buffer * * Return: offset to corresponding compression pointer if any, -1 if not found */ static int opt_dns_search_dup_ptr(unsigned char *buf, const char *cmp, size_t len) { unsigned int i; for (i = 0; i < len; i++) { if (buf[i] == 0 && len - i - 1 >= strlen(cmp) && !memcmp(buf + i + 1, cmp, strlen(cmp))) return i; if ((buf[i] & 0xc0) == 0xc0 && len - i - 2 >= strlen(cmp) && !memcmp(buf + i + 2, cmp, strlen(cmp))) return i + 1; } return -1; } /** * opt_set_dns_search() - Fill data and set length for Domain Search option * @c: Execution context * @max_len: Maximum total length of option buffer */ static void opt_set_dns_search(const struct ctx *c, size_t max_len) { char buf[NS_MAXDNAME]; int i; opts[119].slen = 0; opts[119].state = OPT_DEFAULT; for (i = 0; i < 255; i++) max_len -= opts[i].slen; for (i = 0; *c->dns_search[i].n; i++) { unsigned int n; int count = -1; const char *p; buf[0] = 0; for (p = c->dns_search[i].n, n = 1; *p; p++) { if (*p == '.') { /* RFC 1035 4.1.4 Message compression */ count = opt_dns_search_dup_ptr(opts[119].s, p + 1, opts[119].slen); if (count >= 0) { buf[n++] = '\xc0'; buf[n++] = count; break; } buf[n++] = '.'; } else { buf[n++] = *p; } } /* The compression pointer is also an end of label */ if (count < 0) buf[n++] = 0; if (n >= max_len) break; memcpy(opts[119].s + opts[119].slen, buf, n); opts[119].slen += n; max_len -= n; } for (i = 0; i < opts[119].slen; i++) { if (!opts[119].s[i] || opts[119].s[i] == '.') { opts[119].s[i] = strcspn((char *)opts[119].s + i + 1, ".\xc0"); } } if (!opts[119].slen) opts[119].state = OPT_UNSET; } /** * dhcp() - Check if this is a DHCP message, reply as needed * @c: Execution context * @data: Single packet with Ethernet buffer * * Return: 0 if it's not a DHCP message, 1 if handled, -1 on failure */ int dhcp(const struct ctx *c, struct iov_tail *data) { char macstr[ETH_ADDRSTRLEN]; enum dhcp_overload overload; size_t mlen, dlen, opt_len; struct in_addr mask, dst; struct ethhdr eh_storage; struct iphdr iph_storage; struct udphdr uh_storage; const struct ethhdr *eh; const struct iphdr *iph; const struct udphdr *uh; uint8_t bootfile[128]; struct msg m_storage; struct msg const *m; bool has_bootfile; struct msg reply; int bootfile_len; unsigned int i; eh = IOV_REMOVE_HEADER(data, eh_storage); iph = IOV_PEEK_HEADER(data, iph_storage); if (!eh || !iph) return -1; if (!iov_drop_header(data, iph->ihl * 4UL)) return -1; uh = IOV_REMOVE_HEADER(data, uh_storage); if (!uh) return -1; if (uh->dest != htons(67)) return 0; if (c->no_dhcp) return 1; mlen = iov_tail_size(data); m = (struct msg const *)iov_remove_header_(data, &m_storage, offsetof(struct msg, o), __alignof__(struct msg)); if (!m || mlen != ntohs(uh->len) - sizeof(*uh) || mlen < offsetof(struct msg, o) || m->op != BOOTREQUEST) return -1; reply.op = BOOTREPLY; reply.htype = m->htype; reply.hlen = m->hlen; reply.hops = 0; reply.xid = m->xid; reply.secs = 0; reply.flags = m->flags; reply.ciaddr = m->ciaddr; reply.yiaddr = c->ip4.addr; reply.siaddr = 0; reply.giaddr = m->giaddr; memcpy(&reply.chaddr, m->chaddr, sizeof(reply.chaddr)); memset(&reply.sname, 0, sizeof(reply.sname)); memset(&reply.file, 0, sizeof(reply.file)); reply.magic = m->magic; for (i = 0; i < ARRAY_SIZE(opts); i++) opts[i].clen = -1; while ((opt_len = iov_tail_size(data))) { uint8_t olen_storage, type_storage; const uint8_t *olen; uint8_t *type; if (!(type = IOV_REMOVE_HEADER(data, type_storage))) return -1; if (*type == 255) break; if (*type == 0) /* Pad Option (RFC 2132, 3.1): one byte */ continue; if (!(olen = IOV_REMOVE_HEADER(data, olen_storage))) return -1; if (opt_len - 2 < *olen) return -1; iov_to_buf(&data->iov[0], data->cnt, data->off, &opts[*type].c, *olen); opts[*type].clen = *olen; iov_drop_header(data, *olen); } opts[80].state = OPT_UNSET; if (opts[53].clen > 0 && opts[53].c[0] == DHCPDISCOVER) { if (opts[80].clen == -1) { info("DHCP: offer to discover"); opts[53].s[0] = DHCPOFFER; } else { info("DHCP: ack to discover (Rapid Commit)"); opts[53].s[0] = DHCPACK; opts[80].slen = 0; opts[80].state = OPT_DEFAULT; } } else if (opts[53].clen <= 0 || opts[53].c[0] == DHCPREQUEST) { info("%s: ack to request", /* DHCP needs a valid message type */ (opts[53].clen <= 0) ? "BOOTP" : "DHCP"); opts[53].s[0] = DHCPACK; } else { return -1; } info(" from %s", eth_ntop(m->chaddr, macstr, sizeof(macstr))); mask.s_addr = htonl(0xffffffff << (32 - c->ip4.prefix_len)); if (opts[1].state != OPT_USER) memcpy(opts[1].s, &mask, sizeof(mask)); if (opts[3].state != OPT_USER) memcpy(opts[3].s, &c->ip4.guest_gw, sizeof(c->ip4.guest_gw)); if (opts[54].state != OPT_USER) memcpy(opts[54].s, &c->ip4.our_tap_addr, sizeof(c->ip4.our_tap_addr)); /* If the gateway is not on the assigned subnet, send an option 121 * (Classless Static Routing) adding a dummy route to it. */ if (opts[121].state != OPT_USER && (c->ip4.addr.s_addr & mask.s_addr) != (c->ip4.guest_gw.s_addr & mask.s_addr)) { /* a.b.c.d/32:0.0.0.0, 0:a.b.c.d */ opts[121].slen = 14; opts[121].state = OPT_DEFAULT; opts[121].s[0] = 32; memcpy(opts[121].s + 1, &c->ip4.guest_gw, sizeof(c->ip4.guest_gw)); memcpy(opts[121].s + 10, &c->ip4.guest_gw, sizeof(c->ip4.guest_gw)); } if (opts[26].state != OPT_USER && c->mtu) { opts[26].slen = 2; opts[26].state = OPT_DEFAULT; opts[26].s[0] = c->mtu / 256; opts[26].s[1] = c->mtu % 256; } if (opts[6].state != OPT_USER) { for (i = 0, opts[6].slen = 0; !c->no_dhcp_dns && i < ARRAY_SIZE(c->ip4.dns); i++) { if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns[i])) break; ((struct in_addr *)opts[6].s)[i] = c->ip4.dns[i]; opts[6].slen += sizeof(uint32_t); } if (!opts[6].slen) opts[6].slen = -1; } if (opts[6].slen) opts[6].state = OPT_DEFAULT; else opts[6].state = OPT_UNSET; opt_len = strlen(c->hostname); if (opts[12].state != OPT_USER && opt_len > 0) { opts[12].slen = opt_len; opts[12].state = OPT_DEFAULT; memcpy(opts[12].s, &c->hostname, opt_len); } opt_len = strlen(c->fqdn); if (opt_len > 0) { opt_len += 3 /* flags */ + 2; /* Length byte for first label, and terminator */ if (sizeof(opts[81].s) >= opt_len) { opts[81].s[0] = 0x4; /* flags (E) */ opts[81].s[1] = 0xff; /* RCODE1 */ opts[81].s[2] = 0xff; /* RCODE2 */ encode_domain_name((char *)opts[81].s + 3, c->fqdn); opts[81].slen = opt_len; opts[81].state = OPT_DEFAULT; } else { debug("DHCP: client FQDN option doesn't fit, skipping"); } } if (!c->no_dhcp_dns_search) /* 3 bytes reserved for option 52 (code, length, value) */ opt_set_dns_search(c, OPT_MAX - 3); /* RFC 2132, Section 9.5: put boot file name in the 'file' header * field. Suppress option 67 from the options area and reserve * the file field from overload. */ has_bootfile = opts[67].slen > 0 && (size_t)opts[67].slen < sizeof(reply.file); if (has_bootfile) { memcpy(bootfile, opts[67].s, opts[67].slen); bootfile_len = opts[67].slen; } dlen = offsetof(struct msg, o) + fill(&reply, &overload, has_bootfile); if (has_bootfile) memcpy(reply.file, bootfile, bootfile_len); if (m->flags & FLAG_BROADCAST) dst = in4addr_broadcast; else dst = c->ip4.addr; tap_udp4_send(c, c->ip4.our_tap_addr, 67, dst, 68, &reply, dlen); return 1; }