8eeb8277e5
dhcpcd is a small DHCP client, supporting most, if not all, features of dhclient. It is much smaller (1/6 of the size on amd64), but still supports many of the more advanced modern RFCs like IPv4LL (RFC 3927), Classless Static Routes (RFC 3442) and Node-specific Client Identifiers (RFC 4361). It was written by Roy Marpled, partly in reply to the discussion of the DHCP client Sommer of Code project.
1203 lines
27 KiB
C
1203 lines
27 KiB
C
/*
|
|
* dhcpcd - DHCP client daemon
|
|
* Copyright 2006-2008 Roy Marples <roy@marples.name>
|
|
* All rights reserved
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "config.h"
|
|
#include "common.h"
|
|
#include "dhcp.h"
|
|
|
|
#define REQUEST (1 << 0)
|
|
#define UINT8 (1 << 1)
|
|
#define UINT16 (1 << 2)
|
|
#define SINT16 (1 << 3)
|
|
#define UINT32 (1 << 4)
|
|
#define SINT32 (1 << 5)
|
|
#define IPV4 (1 << 6)
|
|
#define STRING (1 << 7)
|
|
#define PAIR (1 << 8)
|
|
#define ARRAY (1 << 9)
|
|
#define RFC3361 (1 << 10)
|
|
#define RFC3397 (1 << 11)
|
|
#define RFC3442 (1 << 12)
|
|
|
|
#define IPV4R IPV4 | REQUEST
|
|
|
|
/* Our aggregate option buffer.
|
|
* We ONLY use this when options are split, which for most purposes is
|
|
* practically never. See RFC3396 for details. */
|
|
static uint8_t *dhcp_opt_buffer = NULL;
|
|
|
|
struct dhcp_opt {
|
|
uint8_t option;
|
|
int type;
|
|
const char *var;
|
|
};
|
|
|
|
static const struct dhcp_opt const dhcp_opts[] = {
|
|
{ 1, IPV4 | REQUEST, "subnet_mask" },
|
|
{ 2, UINT32, "time_offset" },
|
|
{ 3, IPV4 | ARRAY | REQUEST, "routers" },
|
|
{ 4, IPV4 | ARRAY, "time_servers" },
|
|
{ 5, IPV4 | ARRAY, "ien116_name_servers" },
|
|
{ 6, IPV4 | ARRAY, "domain_name_servers" },
|
|
{ 7, IPV4 | ARRAY, "log_servers" },
|
|
{ 8, IPV4 | ARRAY, "cookie_servers" },
|
|
{ 9, IPV4 | ARRAY, "lpr_servers" },
|
|
{ 10, IPV4 | ARRAY, "impress_servers" },
|
|
{ 11, IPV4 | ARRAY, "resource_location_servers" },
|
|
{ 12, STRING, "host_name" },
|
|
{ 13, UINT16, "boot_size" },
|
|
{ 14, STRING, "merit_dump" },
|
|
{ 15, STRING, "domain_name" },
|
|
{ 16, IPV4, "swap_server" },
|
|
{ 17, STRING, "root_path" },
|
|
{ 18, STRING, "extensions_path" },
|
|
{ 19, UINT8, "ip_forwarding" },
|
|
{ 20, UINT8, "non_local_source_routing" },
|
|
{ 21, IPV4 | ARRAY, "policy_filter" },
|
|
{ 22, SINT16, "max_dgram_reassembly" },
|
|
{ 23, UINT16, "default_ip_ttl" },
|
|
{ 24, UINT32, "path_mtu_aging_timeout" },
|
|
{ 25, UINT16 | ARRAY, "path_mtu_plateau_table" },
|
|
{ 26, UINT16, "interface_mtu" },
|
|
{ 27, UINT8, "all_subnets_local" },
|
|
{ 28, IPV4 | REQUEST, "broadcast_address" },
|
|
{ 29, UINT8, "perform_mask_discovery" },
|
|
{ 30, UINT8, "mask_supplier" },
|
|
{ 31, UINT8, "router_discovery" },
|
|
{ 32, IPV4, "router_solicitation_address" },
|
|
{ 33, IPV4 | ARRAY | REQUEST, "static_routes" },
|
|
{ 34, UINT8, "trailer_encapsulation" },
|
|
{ 35, UINT32, "arp_cache_timeout" },
|
|
{ 36, UINT16, "ieee802_3_encapsulation" },
|
|
{ 37, UINT8, "default_tcp_ttl" },
|
|
{ 38, UINT32, "tcp_keepalive_interval" },
|
|
{ 39, UINT8, "tcp_keepalive_garbage" },
|
|
{ 40, STRING, "nis_domain" },
|
|
{ 41, IPV4 | ARRAY, "nis_servers" },
|
|
{ 42, IPV4 | ARRAY, "ntp_servers" },
|
|
{ 43, STRING, "vendor_encapsulated_options" },
|
|
{ 44, IPV4 | ARRAY, "netbios_name_servers" },
|
|
{ 45, IPV4, "netbios_dd_server" },
|
|
{ 46, UINT8, "netbios_node_type" },
|
|
{ 47, STRING, "netbios_scope" },
|
|
{ 48, IPV4 | ARRAY, "font_servers" },
|
|
{ 49, IPV4 | ARRAY, "x_display_manager" },
|
|
{ 50, IPV4, "dhcp_requested_address" },
|
|
{ 51, UINT32 | REQUEST, "dhcp_lease_time" },
|
|
{ 52, UINT8, "dhcp_option_overload" },
|
|
{ 53, UINT8, "dhcp_message_type" },
|
|
{ 54, IPV4, "dhcp_server_identifier" },
|
|
{ 55, UINT8 | ARRAY, "dhcp_parameter_request_list" },
|
|
{ 56, STRING, "dhcp_message" },
|
|
{ 57, UINT16, "dhcp_max_message_size" },
|
|
{ 58, UINT32 | REQUEST, "dhcp_renewal_time" },
|
|
{ 59, UINT32 | REQUEST, "dhcp_rebinding_time" },
|
|
{ 64, STRING, "nisplus_domain" },
|
|
{ 65, IPV4 | ARRAY, "nisplus_servers" },
|
|
{ 66, STRING, "tftp_server_name" },
|
|
{ 67, STRING, "bootfile_name" },
|
|
{ 68, IPV4 | ARRAY, "mobile_ip_home_agent" },
|
|
{ 69, IPV4 | ARRAY, "smtp_server" },
|
|
{ 70, IPV4 | ARRAY, "pop_server" },
|
|
{ 71, IPV4 | ARRAY, "nntp_server" },
|
|
{ 72, IPV4 | ARRAY, "www_server" },
|
|
{ 73, IPV4 | ARRAY, "finger_server" },
|
|
{ 74, IPV4 | ARRAY, "irc_server" },
|
|
{ 75, IPV4 | ARRAY, "streettalk_server" },
|
|
{ 76, IPV4 | ARRAY, "streettalk_directory_assistance_server" },
|
|
{ 77, STRING, "user_class" },
|
|
{ 85, IPV4 | ARRAY, "nds_servers" },
|
|
{ 86, STRING, "nds_tree_name" },
|
|
{ 87, STRING, "nds_context" },
|
|
{ 88, STRING | RFC3397, "bcms_controller_names" },
|
|
{ 89, IPV4 | ARRAY, "bcms_controller_address" },
|
|
{ 91, UINT32, "client_last_transaction_time" },
|
|
{ 92, IPV4 | ARRAY, "associated_ip" },
|
|
{ 98, STRING, "uap_servers" },
|
|
{ 112, IPV4 | ARRAY, "netinfo_server_address" },
|
|
{ 113, STRING, "netinfo_server_tag" },
|
|
{ 114, STRING, "default_url" },
|
|
{ 118, IPV4, "subnet_selection" },
|
|
{ 119, STRING | RFC3397, "domain_search" },
|
|
{ 121, RFC3442 | REQUEST, "classless_static_routes" },
|
|
{ 249, RFC3442, "ms-classless_static_routes" },
|
|
{ 0, 0, NULL }
|
|
};
|
|
|
|
void
|
|
print_options(void)
|
|
{
|
|
const struct dhcp_opt *opt;
|
|
|
|
for (opt = dhcp_opts; opt->option; opt++)
|
|
if (opt->var)
|
|
printf("%03d %s\n", opt->option, opt->var);
|
|
}
|
|
|
|
int make_reqmask(uint8_t *mask, char **opts, int add)
|
|
{
|
|
char *token;
|
|
char *p = *opts;
|
|
const struct dhcp_opt *opt;
|
|
|
|
while ((token = strsep(&p, ", "))) {
|
|
if (*token == '\0')
|
|
continue;
|
|
for (opt = dhcp_opts; opt->option; opt++) {
|
|
if (!opt->var)
|
|
continue;
|
|
if (strcmp(opt->var, token) == 0) {
|
|
if (add == 1)
|
|
add_reqmask(mask,
|
|
opt->option);
|
|
else
|
|
del_reqmask(mask,
|
|
opt->option);
|
|
break;
|
|
}
|
|
}
|
|
if (!opt->option) {
|
|
*opts = token;
|
|
errno = ENOENT;
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
valid_length(uint8_t option, int dl, int *type)
|
|
{
|
|
const struct dhcp_opt *opt;
|
|
ssize_t sz;
|
|
|
|
if (dl == 0)
|
|
return -1;
|
|
|
|
for (opt = dhcp_opts; opt->option; opt++) {
|
|
if (opt->option != option)
|
|
continue;
|
|
|
|
if (type)
|
|
*type = opt->type;
|
|
|
|
if (opt->type == 0 || opt->type & STRING || opt->type & RFC3442)
|
|
return 0;
|
|
|
|
sz = 0;
|
|
if (opt->type & UINT32 || opt->type & IPV4)
|
|
sz = sizeof(uint32_t);
|
|
if (opt->type & UINT16)
|
|
sz = sizeof(uint16_t);
|
|
if (opt->type & UINT8)
|
|
sz = sizeof(uint8_t);
|
|
if (opt->type & IPV4 || opt->type & ARRAY)
|
|
return dl % sz;
|
|
return (dl == sz ? 0 : -1);
|
|
}
|
|
|
|
/* unknown option, so let it pass */
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
free_option_buffer(void)
|
|
{
|
|
free(dhcp_opt_buffer);
|
|
}
|
|
|
|
#define get_option_raw(dhcp, opt) get_option(dhcp, opt, NULL, NULL)
|
|
static const uint8_t *
|
|
get_option(const struct dhcp_message *dhcp, uint8_t opt, int *len, int *type)
|
|
{
|
|
const uint8_t *p = dhcp->options;
|
|
const uint8_t *e = p + sizeof(dhcp->options);
|
|
uint8_t l, ol = 0;
|
|
uint8_t o = 0;
|
|
uint8_t overl = 0;
|
|
uint8_t *bp = NULL;
|
|
const uint8_t *op = NULL;
|
|
int bl = 0;
|
|
|
|
while (p < e) {
|
|
o = *p++;
|
|
if (o == opt) {
|
|
if (op) {
|
|
if (!dhcp_opt_buffer) {
|
|
dhcp_opt_buffer = xmalloc(sizeof(struct dhcp_message));
|
|
atexit(free_option_buffer);
|
|
}
|
|
if (!bp)
|
|
bp = dhcp_opt_buffer;
|
|
memcpy(bp, op, ol);
|
|
bp += ol;
|
|
}
|
|
ol = *p;
|
|
op = p + 1;
|
|
bl += ol;
|
|
}
|
|
switch (o) {
|
|
case DHCP_PAD:
|
|
continue;
|
|
case DHCP_END:
|
|
if (overl & 1) {
|
|
/* bit 1 set means parse boot file */
|
|
overl &= ~1;
|
|
p = dhcp->bootfile;
|
|
e = p + sizeof(dhcp->bootfile);
|
|
} else if (overl & 2) {
|
|
/* bit 2 set means parse server name */
|
|
overl &= ~2;
|
|
p = dhcp->servername;
|
|
e = p + sizeof(dhcp->servername);
|
|
} else
|
|
goto exit;
|
|
break;
|
|
case DHCP_OPTIONSOVERLOADED:
|
|
/* Ensure we only get this option once */
|
|
if (!overl)
|
|
overl = p[1];
|
|
break;
|
|
}
|
|
l = *p++;
|
|
p += l;
|
|
}
|
|
|
|
exit:
|
|
if (valid_length(o, bl, type) == -1) {
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
if (len)
|
|
*len = bl;
|
|
if (bp) {
|
|
memcpy(bp, op, ol);
|
|
return (const uint8_t *)&dhcp_opt_buffer;
|
|
}
|
|
if (op)
|
|
return op;
|
|
errno = ENOENT;
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
get_option_addr(uint32_t *a, const struct dhcp_message *dhcp, uint8_t option)
|
|
{
|
|
const uint8_t *p = get_option_raw(dhcp, option);
|
|
|
|
if (!p)
|
|
return -1;
|
|
memcpy(a, p, sizeof(*a));
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
get_option_uint32(uint32_t *i, const struct dhcp_message *dhcp, uint8_t option)
|
|
{
|
|
uint32_t a;
|
|
|
|
if (get_option_addr(&a, dhcp, option) == -1)
|
|
return -1;
|
|
|
|
*i = ntohl(a);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
get_option_uint16(uint16_t *i, const struct dhcp_message *dhcp, uint8_t option)
|
|
{
|
|
const uint8_t *p = get_option_raw(dhcp, option);
|
|
uint16_t d;
|
|
|
|
if (!p)
|
|
return -1;
|
|
memcpy(&d, p, sizeof(d));
|
|
*i = ntohs(d);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
get_option_uint8(uint8_t *i, const struct dhcp_message *dhcp, uint8_t option)
|
|
{
|
|
const uint8_t *p = get_option_raw(dhcp, option);
|
|
|
|
if (!p)
|
|
return -1;
|
|
*i = *(p);
|
|
return 0;
|
|
}
|
|
|
|
/* Decode an RFC3397 DNS search order option into a space
|
|
* seperated string. Returns length of string (including
|
|
* terminating zero) or zero on error. out may be NULL
|
|
* to just determine output length. */
|
|
static ssize_t
|
|
decode_rfc3397(char *out, ssize_t len, int pl, const uint8_t *p)
|
|
{
|
|
const uint8_t *r, *q = p;
|
|
int count = 0, l, hops;
|
|
uint8_t ltype;
|
|
|
|
while (q - p < pl) {
|
|
r = NULL;
|
|
hops = 0;
|
|
while ((l = *q++)) {
|
|
ltype = l & 0xc0;
|
|
if (ltype == 0x80 || ltype == 0x40)
|
|
return 0;
|
|
else if (ltype == 0xc0) { /* pointer */
|
|
l = (l & 0x3f) << 8;
|
|
l |= *q++;
|
|
/* save source of first jump. */
|
|
if (!r)
|
|
r = q;
|
|
hops++;
|
|
if (hops > 255)
|
|
return 0;
|
|
q = p + l;
|
|
if (q - p >= pl)
|
|
return 0;
|
|
} else {
|
|
/* straightforward name segment, add with '.' */
|
|
count += l + 1;
|
|
if (out) {
|
|
if ((ssize_t)l + 1 > len) {
|
|
errno = ENOBUFS;
|
|
return -1;
|
|
}
|
|
memcpy(out, q, l);
|
|
out += l;
|
|
*out++ = '.';
|
|
len -= l;
|
|
len--;
|
|
}
|
|
q += l;
|
|
}
|
|
}
|
|
/* change last dot to space */
|
|
if (out)
|
|
*(out - 1) = ' ';
|
|
if (r)
|
|
q = r;
|
|
}
|
|
|
|
/* change last space to zero terminator */
|
|
if (out)
|
|
*(out - 1) = 0;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
decode_rfc3442(char *out, ssize_t len, int pl, const uint8_t *p)
|
|
{
|
|
const uint8_t *e;
|
|
ssize_t bytes = 0;
|
|
ssize_t b;
|
|
uint8_t cidr;
|
|
uint8_t ocets;
|
|
struct in_addr addr;
|
|
char *o = out;
|
|
|
|
/* Minimum is 5 -first is CIDR and a router length of 4 */
|
|
if (pl < 5) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
e = p + pl;
|
|
while (p < e) {
|
|
cidr = *p++;
|
|
if (cidr > 32) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
ocets = (cidr + 7) / 8;
|
|
if (!out) {
|
|
p += 4 + ocets;
|
|
bytes += ((4 * 4) * 2) + 4;
|
|
continue;
|
|
}
|
|
if ((((4 * 4) * 2) + 4) > len) {
|
|
errno = ENOBUFS;
|
|
return -1;
|
|
}
|
|
if (o != out) {
|
|
*o++ = ' ';
|
|
len--;
|
|
}
|
|
/* If we have ocets then we have a destination and netmask */
|
|
if (ocets > 0) {
|
|
addr.s_addr = 0;
|
|
memcpy(&addr.s_addr, p, (size_t)ocets);
|
|
b = snprintf(o, len, "%s/%d", inet_ntoa(addr), cidr);
|
|
p += ocets;
|
|
} else
|
|
b = snprintf(o, len, "0.0.0.0/0");
|
|
o += b;
|
|
len -= b;
|
|
|
|
/* Finally, snag the router */
|
|
memcpy(&addr.s_addr, p, 4);
|
|
p += 4;
|
|
b = snprintf(o, len, " %s", inet_ntoa(addr));
|
|
o += b;
|
|
len -= b;
|
|
}
|
|
|
|
if (out)
|
|
return o - out;
|
|
return bytes;
|
|
}
|
|
|
|
static struct rt *
|
|
decode_rfc3442_rt(int dl, const uint8_t *data)
|
|
{
|
|
const uint8_t *p = data;
|
|
const uint8_t *e;
|
|
uint8_t cidr;
|
|
uint8_t ocets;
|
|
struct rt *routes = NULL;
|
|
struct rt *rt = NULL;
|
|
|
|
/* Minimum is 5 -first is CIDR and a router length of 4 */
|
|
if (dl < 5)
|
|
return NULL;
|
|
|
|
e = p + dl;
|
|
while (p < e) {
|
|
cidr = *p++;
|
|
if (cidr > 32) {
|
|
free_routes(routes);
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
if (rt) {
|
|
rt->next = xzalloc(sizeof(*rt));
|
|
rt = rt->next;
|
|
} else {
|
|
routes = rt = xzalloc(sizeof(*routes));
|
|
}
|
|
rt->next = NULL;
|
|
|
|
ocets = (cidr + 7) / 8;
|
|
/* If we have ocets then we have a destination and netmask */
|
|
if (ocets > 0) {
|
|
memcpy(&rt->dest.s_addr, p, (size_t)ocets);
|
|
memset(&rt->net.s_addr, 255, (size_t)ocets - 1);
|
|
memset((uint8_t *)&rt->net.s_addr +
|
|
(ocets - 1),
|
|
(256 - (1 << (32 - cidr) % 8)), 1);
|
|
p += ocets;
|
|
} else {
|
|
rt->dest.s_addr = 0;
|
|
rt->net.s_addr = 0;
|
|
}
|
|
|
|
/* Finally, snag the router */
|
|
memcpy(&rt->gate.s_addr, p, 4);
|
|
p += 4;
|
|
}
|
|
return routes;
|
|
}
|
|
|
|
static char *
|
|
decode_rfc3361(int dl, const uint8_t *data)
|
|
{
|
|
uint8_t enc;
|
|
unsigned int l;
|
|
char *sip = NULL;
|
|
struct in_addr addr;
|
|
char *p;
|
|
|
|
if (dl < 2) {
|
|
errno = EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
enc = *data++;
|
|
dl--;
|
|
switch (enc) {
|
|
case 0:
|
|
if ((l = decode_rfc3397(NULL, 0, dl, data)) > 0) {
|
|
sip = xmalloc(l);
|
|
decode_rfc3397(sip, l, dl, data);
|
|
}
|
|
break;
|
|
case 1:
|
|
if (dl == 0 || dl % 4 != 0) {
|
|
errno = EINVAL;
|
|
break;
|
|
}
|
|
addr.s_addr = INADDR_BROADCAST;
|
|
l = ((dl / sizeof(addr.s_addr)) * ((4 * 4) + 1)) + 1;
|
|
sip = p = xmalloc(l);
|
|
while (l != 0) {
|
|
memcpy(&addr.s_addr, data, sizeof(addr.s_addr));
|
|
data += sizeof(addr.s_addr);
|
|
p += snprintf(p, l - (p - sip), "%s ", inet_ntoa(addr));
|
|
l -= sizeof(addr.s_addr);
|
|
}
|
|
*--p = '\0';
|
|
break;
|
|
default:
|
|
errno = EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
return sip;
|
|
}
|
|
|
|
char *
|
|
get_option_string(const struct dhcp_message *dhcp, uint8_t option)
|
|
{
|
|
int type;
|
|
int len;
|
|
const uint8_t *p;
|
|
char *s;
|
|
|
|
p = get_option(dhcp, option, &len, &type);
|
|
if (!p || *p == '\0')
|
|
return NULL;
|
|
|
|
if (type & RFC3397) {
|
|
type = decode_rfc3397(NULL, 0, len, p);
|
|
if (!type) {
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
s = xmalloc(sizeof(char) * type);
|
|
decode_rfc3397(s, type, len, p);
|
|
return s;
|
|
}
|
|
|
|
if (type & RFC3361)
|
|
return decode_rfc3361(len, p);
|
|
|
|
s = xmalloc(sizeof(char) * (len + 1));
|
|
memcpy(s, p, len);
|
|
s[len] = '\0';
|
|
return s;
|
|
}
|
|
|
|
/* This calculates the netmask that we should use for static routes.
|
|
* This IS different from the calculation used to calculate the netmask
|
|
* for an interface address. */
|
|
static uint32_t
|
|
route_netmask(uint32_t ip_in)
|
|
{
|
|
/* used to be unsigned long - check if error */
|
|
uint32_t p = ntohl(ip_in);
|
|
uint32_t t;
|
|
|
|
if (IN_CLASSA(p))
|
|
t = ~IN_CLASSA_NET;
|
|
else {
|
|
if (IN_CLASSB(p))
|
|
t = ~IN_CLASSB_NET;
|
|
else {
|
|
if (IN_CLASSC(p))
|
|
t = ~IN_CLASSC_NET;
|
|
else
|
|
t = 0;
|
|
}
|
|
}
|
|
|
|
while (t & p)
|
|
t >>= 1;
|
|
|
|
return (htonl(~t));
|
|
}
|
|
|
|
/* We need to obey routing options.
|
|
* If we have a CSR then we only use that.
|
|
* Otherwise we add static routes and then routers. */
|
|
struct rt *
|
|
get_option_routes(const struct dhcp_message *dhcp)
|
|
{
|
|
const uint8_t *p;
|
|
const uint8_t *e;
|
|
struct rt *routes = NULL;
|
|
struct rt *route = NULL;
|
|
int len;
|
|
|
|
/* If we have CSR's then we MUST use these only */
|
|
p = get_option(dhcp, DHCP_CSR, &len, NULL);
|
|
/* Check for crappy MS option */
|
|
if (!p)
|
|
p = get_option(dhcp, DHCP_MSCSR, &len, NULL);
|
|
if (p) {
|
|
routes = decode_rfc3442_rt(len, p);
|
|
if (routes)
|
|
return routes;
|
|
}
|
|
|
|
/* OK, get our static routes first. */
|
|
p = get_option(dhcp, DHCP_STATICROUTE, &len, NULL);
|
|
if (p) {
|
|
e = p + len;
|
|
while (p < e) {
|
|
if (route) {
|
|
route->next = xmalloc(sizeof(*route));
|
|
route = route->next;
|
|
} else
|
|
routes = route = xmalloc(sizeof(*routes));
|
|
route->next = NULL;
|
|
memcpy(&route->dest.s_addr, p, 4);
|
|
p += 4;
|
|
memcpy(&route->gate.s_addr, p, 4);
|
|
p += 4;
|
|
route->net.s_addr = route_netmask(route->dest.s_addr);
|
|
}
|
|
}
|
|
|
|
/* Now grab our routers */
|
|
p = get_option(dhcp, DHCP_ROUTER, &len, NULL);
|
|
if (p) {
|
|
e = p + len;
|
|
while (p < e) {
|
|
if (route) {
|
|
route->next = xzalloc(sizeof(*route));
|
|
route = route->next;
|
|
} else
|
|
routes = route = xzalloc(sizeof(*route));
|
|
memcpy(&route->gate.s_addr, p, 4);
|
|
p += 4;
|
|
}
|
|
}
|
|
|
|
return routes;
|
|
}
|
|
|
|
ssize_t
|
|
make_message(struct dhcp_message **message,
|
|
const struct interface *iface, const struct dhcp_lease *lease,
|
|
uint32_t xid, uint8_t type, const struct options *options)
|
|
{
|
|
struct dhcp_message *dhcp;
|
|
uint8_t *d, *m, *p;
|
|
const char *c;
|
|
uint8_t *n_params = NULL;
|
|
size_t l;
|
|
time_t up = uptime() - iface->start_uptime;
|
|
uint32_t ul;
|
|
uint16_t sz;
|
|
const struct dhcp_opt *opt;
|
|
|
|
dhcp = xzalloc(sizeof (*dhcp));
|
|
m = (uint8_t *)dhcp;
|
|
p = (uint8_t *)&dhcp->options;
|
|
|
|
if ((type == DHCP_INFORM ||
|
|
type == DHCP_RELEASE ||
|
|
type == DHCP_REQUEST) &&
|
|
!IN_LINKLOCAL(ntohl(iface->addr.s_addr)))
|
|
{
|
|
dhcp->ciaddr = iface->addr.s_addr;
|
|
/* Just incase we haven't actually configured the address yet */
|
|
if (type == DHCP_INFORM && iface->addr.s_addr == 0)
|
|
dhcp->ciaddr = lease->addr.s_addr;
|
|
/* Zero the address if we're currently on a different subnet */
|
|
if (type == DHCP_REQUEST &&
|
|
iface->net.s_addr != lease->net.s_addr)
|
|
dhcp->ciaddr = 0;
|
|
}
|
|
|
|
dhcp->op = DHCP_BOOTREQUEST;
|
|
dhcp->hwtype = iface->family;
|
|
switch (iface->family) {
|
|
case ARPHRD_ETHER:
|
|
case ARPHRD_IEEE802:
|
|
dhcp->hwlen = ETHER_ADDR_LEN;
|
|
memcpy(&dhcp->chaddr, &iface->hwaddr, ETHER_ADDR_LEN);
|
|
break;
|
|
case ARPHRD_IEEE1394:
|
|
case ARPHRD_INFINIBAND:
|
|
dhcp->hwlen = 0;
|
|
if (dhcp->ciaddr == 0)
|
|
dhcp->flags = htons(BROADCAST_FLAG);
|
|
break;
|
|
}
|
|
|
|
if (up < 0 || up > (time_t)UINT16_MAX)
|
|
dhcp->secs = htons((uint16_t)UINT16_MAX);
|
|
else
|
|
dhcp->secs = htons(up);
|
|
dhcp->xid = xid;
|
|
dhcp->cookie = htonl(MAGIC_COOKIE);
|
|
|
|
*p++ = DHCP_MESSAGETYPE;
|
|
*p++ = 1;
|
|
*p++ = type;
|
|
|
|
if (type == DHCP_REQUEST) {
|
|
*p++ = DHCP_MAXMESSAGESIZE;
|
|
*p++ = 2;
|
|
sz = get_mtu(iface->name);
|
|
if (sz < MTU_MIN) {
|
|
if (set_mtu(iface->name, MTU_MIN) == 0)
|
|
sz = MTU_MIN;
|
|
}
|
|
sz = htons(sz);
|
|
memcpy(p, &sz, 2);
|
|
p += 2;
|
|
}
|
|
|
|
if (iface->clientid_len > 0) {
|
|
*p++ = DHCP_CLIENTID;
|
|
*p++ = iface->clientid_len;
|
|
memcpy(p, iface->clientid, iface->clientid_len);
|
|
p+= iface->clientid_len;
|
|
}
|
|
|
|
if (type != DHCP_DECLINE && type != DHCP_RELEASE) {
|
|
if (options->userclass_len > 0) {
|
|
*p++ = DHCP_USERCLASS;
|
|
*p++ = options->userclass_len;
|
|
memcpy(p, &options->userclass, options->userclass_len);
|
|
p += options->userclass_len;
|
|
}
|
|
|
|
if (*options->classid > 0) {
|
|
*p++ = DHCP_CLASSID;
|
|
*p++ = l = strlen(options->classid);
|
|
memcpy(p, options->classid, l);
|
|
p += l;
|
|
}
|
|
}
|
|
|
|
if (type == DHCP_DISCOVER || type == DHCP_REQUEST) {
|
|
#define PUTADDR(_type, _val) \
|
|
{ \
|
|
*p++ = _type; \
|
|
*p++ = 4; \
|
|
memcpy(p, &_val.s_addr, 4); \
|
|
p += 4; \
|
|
}
|
|
if (lease->addr.s_addr &&
|
|
lease->addr.s_addr != iface->addr.s_addr &&
|
|
!IN_LINKLOCAL(ntohl(lease->addr.s_addr)))
|
|
{
|
|
PUTADDR(DHCP_IPADDRESS, lease->addr);
|
|
if (lease->server.s_addr)
|
|
PUTADDR(DHCP_SERVERID, lease->server);
|
|
}
|
|
#undef PUTADDR
|
|
|
|
if (options->leasetime != 0) {
|
|
*p++ = DHCP_LEASETIME;
|
|
*p++ = 4;
|
|
ul = htonl(options->leasetime);
|
|
memcpy(p, &ul, 4);
|
|
p += 4;
|
|
}
|
|
}
|
|
|
|
if (type == DHCP_DISCOVER ||
|
|
type == DHCP_INFORM ||
|
|
type == DHCP_REQUEST)
|
|
{
|
|
if (options->hostname[0]) {
|
|
if (options->fqdn == FQDN_DISABLE) {
|
|
*p++ = DHCP_HOSTNAME;
|
|
*p++ = l = strlen(options->hostname);
|
|
memcpy(p, options->hostname, l);
|
|
p += l;
|
|
} else {
|
|
/* Draft IETF DHC-FQDN option (81) */
|
|
*p++ = DHCP_FQDN;
|
|
*p++ = strlen(options->hostname) + 5;
|
|
/*
|
|
* Flags: 0000NEOS
|
|
* S: 1 => Client requests Server to update
|
|
* a RR in DNS as well as PTR
|
|
* O: 1 => Server indicates to client that
|
|
* DNS has been updated
|
|
* E: 1 => Name data is DNS format
|
|
* N: 1 => Client requests Server to not
|
|
* update DNS
|
|
*/
|
|
*p++ = (options->fqdn & 0x9) | 0x4;
|
|
*p++ = 0; /* from server for PTR RR */
|
|
*p++ = 0; /* from server for A RR if S=1 */
|
|
c = options->hostname;
|
|
d = p++;
|
|
while (*c) {
|
|
if (*c == '.') {
|
|
*d = p - d - 1;
|
|
d = p++;
|
|
} else
|
|
*p++ = (uint8_t) *c;
|
|
c++;
|
|
}
|
|
*p ++ = 0;
|
|
}
|
|
}
|
|
|
|
*p++ = DHCP_PARAMETERREQUESTLIST;
|
|
n_params = p;
|
|
*p++ = 0;
|
|
for (opt = dhcp_opts; opt->option; opt++) {
|
|
if (!(opt->type & REQUEST ||
|
|
has_reqmask(options->reqmask, opt->option)))
|
|
continue;
|
|
switch (opt->option) {
|
|
case DHCP_RENEWALTIME: /* FALLTHROUGH */
|
|
case DHCP_REBINDTIME:
|
|
if (type == DHCP_INFORM)
|
|
continue;
|
|
break;
|
|
}
|
|
*p++ = opt->option;
|
|
}
|
|
*n_params = p - n_params - 1;
|
|
}
|
|
*p++ = DHCP_END;
|
|
|
|
#ifdef BOOTP_MESSAGE_LENTH_MIN
|
|
/* Some crappy DHCP servers think they have to obey the BOOTP minimum
|
|
* message length.
|
|
* They are wrong, but we should still cater for them. */
|
|
while (p - m < BOOTP_MESSAGE_LENTH_MIN)
|
|
*p++ = DHCP_PAD;
|
|
#endif
|
|
|
|
*message = dhcp;
|
|
return p - m;
|
|
}
|
|
|
|
ssize_t
|
|
write_lease(const struct interface *iface, const struct dhcp_message *dhcp)
|
|
{
|
|
int fd;
|
|
ssize_t bytes = sizeof(*dhcp);
|
|
const uint8_t *p = dhcp->options;
|
|
const uint8_t *e = p + sizeof(dhcp->options);
|
|
uint8_t l;
|
|
uint8_t o = 0;
|
|
|
|
fd = open(iface->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0400);
|
|
if (fd == -1)
|
|
return -1;
|
|
|
|
/* Only write as much as we need */
|
|
while (p < e) {
|
|
o = *p;
|
|
if (o == DHCP_END) {
|
|
bytes = p - (const uint8_t *)dhcp;
|
|
break;
|
|
}
|
|
p++;
|
|
if (o != DHCP_PAD) {
|
|
l = *p++;
|
|
p += l;
|
|
}
|
|
}
|
|
bytes = write(fd, dhcp, bytes);
|
|
close(fd);
|
|
return bytes;
|
|
}
|
|
|
|
struct dhcp_message *
|
|
read_lease(const struct interface *iface)
|
|
{
|
|
int fd;
|
|
struct dhcp_message *dhcp;
|
|
ssize_t bytes;
|
|
|
|
fd = open(iface->leasefile, O_RDONLY);
|
|
if (fd == -1)
|
|
return NULL;
|
|
dhcp = xmalloc(sizeof(*dhcp));
|
|
memset(dhcp, 0, sizeof(*dhcp));
|
|
bytes = read(fd, dhcp, sizeof(*dhcp));
|
|
close(fd);
|
|
if (bytes < 0) {
|
|
free(dhcp);
|
|
dhcp = NULL;
|
|
}
|
|
return dhcp;
|
|
}
|
|
|
|
static ssize_t
|
|
print_string(char *s, ssize_t len, int dl, const uint8_t *data)
|
|
{
|
|
uint8_t c;
|
|
const uint8_t *e;
|
|
ssize_t bytes = 0;
|
|
ssize_t r;
|
|
|
|
e = data + dl;
|
|
while (data < e) {
|
|
c = *data++;
|
|
if (!isascii(c) || !isprint(c)) {
|
|
if (s) {
|
|
if (len < 5) {
|
|
errno = ENOBUFS;
|
|
return -1;
|
|
}
|
|
r = snprintf(s, len, "\\%03o", c);
|
|
len -= r;
|
|
bytes += r;
|
|
s += r;
|
|
} else
|
|
bytes += 4;
|
|
continue;
|
|
}
|
|
switch (c) {
|
|
case '"': /* FALLTHROUGH */
|
|
case '\'': /* FALLTHROUGH */
|
|
case '$': /* FALLTHROUGH */
|
|
case '`': /* FALLTHROUGH */
|
|
case '\\': /* FALLTHROUGH */
|
|
if (s) {
|
|
if (len < 3) {
|
|
errno = ENOBUFS;
|
|
return -1;
|
|
}
|
|
*s++ = '\\';
|
|
len--;
|
|
}
|
|
bytes++;
|
|
break;
|
|
}
|
|
if (s) {
|
|
*s++ = c;
|
|
len--;
|
|
}
|
|
bytes++;
|
|
}
|
|
|
|
/* NULL */
|
|
if (s)
|
|
*s = '\0';
|
|
bytes++;
|
|
return bytes;
|
|
}
|
|
|
|
static ssize_t
|
|
print_option(char *s, ssize_t len, int type, int dl, const uint8_t *data)
|
|
{
|
|
const uint8_t *e, *t;
|
|
uint16_t u16;
|
|
int16_t s16;
|
|
uint32_t u32;
|
|
int32_t s32;
|
|
struct in_addr addr;
|
|
ssize_t bytes = 0;
|
|
ssize_t l;
|
|
char *tmp;
|
|
|
|
if (type & RFC3397) {
|
|
l = decode_rfc3397(NULL, 0, dl, data);
|
|
if (l < 1)
|
|
return l;
|
|
tmp = xmalloc(l);
|
|
decode_rfc3397(tmp, l, dl, data);
|
|
l = print_string(s, len, l - 1, (uint8_t *)tmp);
|
|
free(tmp);
|
|
return l;
|
|
}
|
|
|
|
if (type & RFC3442)
|
|
return decode_rfc3442(s, len, dl, data);
|
|
|
|
if (type & STRING) {
|
|
/* Some DHCP servers return NULL strings */
|
|
if (*data == '\0')
|
|
return 0;
|
|
return print_string(s, len, dl, data);
|
|
}
|
|
|
|
if (!s) {
|
|
if (type & UINT8)
|
|
l = 3;
|
|
else if (type & UINT16)
|
|
l = 5;
|
|
else if (type & SINT16)
|
|
l = 6;
|
|
else if (type & UINT32)
|
|
l = 10;
|
|
else if (type & SINT32)
|
|
l = 11;
|
|
else if (type & IPV4)
|
|
l = 16;
|
|
else {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
return (l + 1) * dl;
|
|
}
|
|
|
|
t = data;
|
|
e = data + dl;
|
|
while (data < e) {
|
|
if (data != t) {
|
|
*s++ = ' ';
|
|
bytes++;
|
|
len--;
|
|
}
|
|
if (type & UINT8) {
|
|
l = snprintf(s, len, "%d", *data);
|
|
data++;
|
|
} else if (type & UINT16) {
|
|
memcpy(&u16, data, sizeof(u16));
|
|
u16 = ntohs(u16);
|
|
l = snprintf(s, len, "%d", u16);
|
|
data += sizeof(u16);
|
|
} else if (type & SINT16) {
|
|
memcpy(&s16, data, sizeof(s16));
|
|
s16 = ntohs(s16);
|
|
l = snprintf(s, len, "%d", s16);
|
|
data += sizeof(s16);
|
|
} else if (type & UINT32) {
|
|
memcpy(&u32, data, sizeof(u32));
|
|
u32 = ntohl(u32);
|
|
l = snprintf(s, len, "%d", u32);
|
|
data += sizeof(u32);
|
|
} else if (type & SINT32) {
|
|
memcpy(&s32, data, sizeof(s32));
|
|
s32 = ntohl(s32);
|
|
l = snprintf(s, len, "%d", s32);
|
|
data += sizeof(s32);
|
|
} else if (type & IPV4) {
|
|
memcpy(&addr.s_addr, data, sizeof(addr.s_addr));
|
|
l = snprintf(s, len, "%s", inet_ntoa(addr));
|
|
data += sizeof(addr.s_addr);
|
|
} else
|
|
l = 0;
|
|
len -= l;
|
|
bytes += l;
|
|
s += l;
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
static void
|
|
setvar(char ***e, const char *prefix, const char *var, const char *value)
|
|
{
|
|
size_t len = strlen(prefix) + strlen(var) + strlen(value) + 4;
|
|
|
|
**e = xmalloc(len);
|
|
snprintf(**e, len, "%s_%s=%s", prefix, var, value);
|
|
(*e)++;
|
|
}
|
|
|
|
ssize_t
|
|
configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp,
|
|
const struct options *options)
|
|
{
|
|
unsigned int i;
|
|
const uint8_t *p;
|
|
int pl;
|
|
struct in_addr addr;
|
|
struct in_addr net;
|
|
struct in_addr brd;
|
|
char *val, *v;
|
|
const struct dhcp_opt *opt;
|
|
ssize_t len, e = 0;
|
|
char **ep;
|
|
char cidr[4];
|
|
uint8_t overl = 0;
|
|
|
|
get_option_uint8(&overl, dhcp, DHCP_OPTIONSOVERLOADED);
|
|
|
|
if (!env) {
|
|
for (opt = dhcp_opts; opt->option; opt++) {
|
|
if (!opt->var)
|
|
continue;
|
|
if (has_reqmask(options->nomask, opt->option))
|
|
continue;
|
|
if (get_option_raw(dhcp, opt->option))
|
|
e++;
|
|
}
|
|
if (dhcp->yiaddr)
|
|
e += 5;
|
|
if (*dhcp->bootfile && !(overl & 1))
|
|
e++;
|
|
if (*dhcp->servername && !(overl & 2))
|
|
e++;
|
|
return e;
|
|
}
|
|
|
|
ep = env;
|
|
if (dhcp->yiaddr) {
|
|
/* Set some useful variables that we derive from the DHCP
|
|
* message but are not necessarily in the options */
|
|
addr.s_addr = dhcp->yiaddr;
|
|
setvar(&ep, prefix, "ip_address", inet_ntoa(addr));
|
|
if (get_option_addr(&net.s_addr, dhcp, DHCP_SUBNETMASK) == -1) {
|
|
net.s_addr = get_netmask(addr.s_addr);
|
|
setvar(&ep, prefix, "subnet_mask", inet_ntoa(net));
|
|
}
|
|
i = inet_ntocidr(net);
|
|
snprintf(cidr, sizeof(cidr), "%d", inet_ntocidr(net));
|
|
setvar(&ep, prefix, "subnet_cidr", cidr);
|
|
if (get_option_addr(&brd.s_addr, dhcp, DHCP_BROADCAST) == -1) {
|
|
brd.s_addr = addr.s_addr | ~net.s_addr;
|
|
setvar(&ep, prefix, "broadcast_address", inet_ntoa(net));
|
|
}
|
|
addr.s_addr = dhcp->yiaddr & net.s_addr;
|
|
setvar(&ep, prefix, "network_number", inet_ntoa(addr));
|
|
}
|
|
|
|
if (*dhcp->bootfile && !(overl & 1))
|
|
setvar(&ep, prefix, "filename", (const char *)dhcp->bootfile);
|
|
if (*dhcp->servername && !(overl & 2))
|
|
setvar(&ep, prefix, "server_name", (const char *)dhcp->servername);
|
|
|
|
for (opt = dhcp_opts; opt->option; opt++) {
|
|
if (!opt->var)
|
|
continue;
|
|
if (has_reqmask(options->nomask, opt->option))
|
|
continue;
|
|
val = NULL;
|
|
p = get_option(dhcp, opt->option, &pl, NULL);
|
|
if (!p)
|
|
continue;
|
|
len = print_option(NULL, 0, opt->type, pl, p);
|
|
if (len < 0)
|
|
return -1;
|
|
e = strlen(prefix) + strlen(opt->var) + len + 4;
|
|
v = val = *ep++ = xmalloc(e);
|
|
v += snprintf(val, e, "%s_%s=", prefix, opt->var);
|
|
if (len != 0)
|
|
print_option(v, len, opt->type, pl, p);
|
|
}
|
|
|
|
return ep - env;
|
|
}
|