import of dnsmasq-2.52.tar.gz

This commit is contained in:
Simon Kelley
2010-01-22 20:16:09 +00:00
parent 1f15b81d61
commit 316e2730ac
43 changed files with 2694 additions and 2033 deletions

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2009 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2010 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -54,6 +54,8 @@
#define OPTION_ARCH 93
#define OPTION_PXE_UUID 97
#define OPTION_SUBNET_SELECT 118
#define OPTION_VENDOR_IDENT 124
#define OPTION_VENDOR_IDENT_OPT 125
#define OPTION_END 255
#define SUBOPT_CIRCUIT_ID 1
@@ -77,10 +79,17 @@
#define DHCPRELEASE 7
#define DHCPINFORM 8
#define BRDBAND_FORUM_IANA 3561 /* Broadband forum IANA enterprise */
#define have_config(config, mask) ((config) && ((config)->flags & (mask)))
#define option_len(opt) ((int)(((unsigned char *)(opt))[1]))
#define option_ptr(opt, i) ((void *)&(((unsigned char *)(opt))[2u+(unsigned int)(i)]))
#ifdef HAVE_SCRIPT
static void add_extradata_data(struct dhcp_lease *lease, unsigned char *data, size_t len, int delim);
static void add_extradata_opt(struct dhcp_lease *lease, unsigned char *opt);
#endif
static int match_bytes(struct dhcp_opt *o, unsigned char *p, int len);
static int sanitise(unsigned char *opt, char *buf);
static struct in_addr server_id(struct dhcp_context *context, struct in_addr override, struct in_addr fallback);
static unsigned int calc_time(struct dhcp_context *context, struct dhcp_config *config, unsigned char *opt);
@@ -114,12 +123,12 @@ static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt);
static void do_encap_opts(struct dhcp_opt *opts, int encap, int flag, struct dhcp_packet *mess, unsigned char *end, int null_term);
static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid);
static int prune_vendor_opts(struct dhcp_netid *netid);
static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid);
static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local);
struct dhcp_boot *find_boot(struct dhcp_netid *netid);
size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
size_t sz, time_t now, int unicast_dest, int *is_inform)
size_t sz, time_t now, int unicast_dest, int *is_inform, int pxe)
{
unsigned char *opt, *clid = NULL;
struct dhcp_lease *ltmp, *lease = NULL;
@@ -144,9 +153,10 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
unsigned char *agent_id = NULL, *uuid = NULL;
unsigned char *emac = NULL;
int emac_len = 0;
struct dhcp_netid known_id, iface_id;
struct dhcp_netid known_id, iface_id, cpewan_id;
struct dhcp_opt *o;
unsigned char pxe_uuid[17];
unsigned char *oui = NULL, *serial = NULL, *class = NULL;
subnet_addr.s_addr = override.s_addr = 0;
@@ -194,6 +204,35 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
if ((option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ) || mess_type == DHCPDISCOVER))
mess->ciaddr.s_addr = 0;
/* search for device identity from CPEWAN devices, we pass this through to the script */
if ((opt = option_find(mess, sz, OPTION_VENDOR_IDENT_OPT, 5)))
{
unsigned int elen, offset, len = option_len(opt);
for (offset = 0; offset < (len - 5); offset += elen + 5)
{
elen = option_uint(opt, offset + 4 , 1);
if (option_uint(opt, offset, 4) == BRDBAND_FORUM_IANA)
{
unsigned char *x = option_ptr(opt, offset + 5);
unsigned char *y = option_ptr(opt, offset + elen + 5);
oui = option_find1(x, y, 1, 1);
serial = option_find1(x, y, 2, 1);
class = option_find1(x, y, 3, 1);
/* If TR069-id is present set the tag "cpewan-id" to facilitate echoing
the gateway id back. Note that the device class is optional */
if (oui && serial)
{
cpewan_id.net = "cpewan-id";
cpewan_id.next = netid;
netid = &cpewan_id;
}
break;
}
}
}
if ((opt = option_find(mess, sz, OPTION_AGENT_ID, 1)))
{
/* Any agent-id needs to be copied back out, verbatim, as the last option
@@ -370,7 +409,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
netid = &known_id;
}
if (mess_type == 0)
if (mess_type == 0 && !pxe)
{
/* BOOTP request */
struct dhcp_netid id, bootp_id;
@@ -601,38 +640,44 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
/* dhcp-match. If we have hex-and-wildcards, look for a left-anchored match.
Otherwise assume the option is an array, and look for a matching element.
If no data given, existance of the option is enough. */
If no data given, existance of the option is enough. This code handles
rfc3925 V-I classes too. */
for (o = daemon->dhcp_match; o; o = o->next)
{
int i, matched = 0;
if (!(opt = option_find(mess, sz, o->opt, 1)) ||
o->len > option_len(opt))
continue;
if (o->len == 0)
matched = 1;
else if (o->flags & DHOPT_HEX)
{
if (memcmp_masked(o->val, option_ptr(opt, 0), o->len, o->u.wildcard_mask))
matched = 1;
}
else
for (i = 0; i <= (option_len(opt) - o->len); )
{
if (memcmp(o->val, option_ptr(opt, i), o->len) == 0)
{
matched = 1;
break;
}
unsigned int len, elen, match = 0;
size_t offset, o2;
if (o->flags & DHOPT_STRING)
i++;
else
i += o->len;
}
if (matched)
if (o->flags & DHOPT_RFC3925)
{
if (!(opt = option_find(mess, sz, OPTION_VENDOR_IDENT, 5)))
continue;
for (offset = 0; offset < (option_len(opt) - 5u); offset += len + 5)
{
len = option_uint(opt, offset + 4 , 1);
/* Need to take care that bad data can't run us off the end of the packet */
if ((offset + len + 5 <= (option_len(opt))) &&
(option_uint(opt, offset, 4) == (unsigned int)o->u.encap))
for (o2 = offset + 5; o2 < offset + len + 5; o2 += elen + 1)
{
elen = option_uint(opt, o2, 1);
if ((o2 + elen + 1 <= option_len(opt)) &&
(match = match_bytes(o, option_ptr(opt, o2 + 1), elen)))
break;
}
if (match)
break;
}
}
else
{
if (!(opt = option_find(mess, sz, o->opt, 1)))
continue;
match = match_bytes(o, option_ptr(opt, 0), option_len(opt));
}
if (match)
{
o->netid->next = netid;
netid = o->netid;
@@ -778,8 +823,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
{
pxearch = option_uint(opt, 0, 2);
/* proxy DHCP here. The DHCPREQUEST stuff is for gPXE */
if ((mess_type == DHCPDISCOVER || mess_type == DHCPREQUEST) &&
/* proxy DHCP here. */
if ((mess_type == DHCPDISCOVER || (pxe && mess_type == DHCPREQUEST)) &&
(context->flags & CONTEXT_PROXY))
{
struct dhcp_boot *boot = find_boot(netid);
@@ -809,16 +854,16 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(context->local.s_addr));
pxe_misc(mess, end, uuid);
prune_vendor_opts(netid);
do_encap_opts(pxe_opts(pxearch, netid), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
do_encap_opts(pxe_opts(pxearch, netid, context->local), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy" : "proxy-ignored", mess->xid);
log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", mess->xid);
return ignore ? 0 : dhcp_packet_size(mess, netid, agent_id, real_end);
}
}
}
/* if we're just a proxy server, go no further */
if (context->flags & CONTEXT_PROXY)
if ((context->flags & CONTEXT_PROXY) || pxe)
return 0;
if ((opt = option_find(mess, sz, OPTION_REQUESTED_OPTIONS, 0)))
@@ -1135,13 +1180,37 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
}
else
{
if (do_classes)
if (context->netid.net)
{
context->netid.next = netid;
netid = &context->netid;
}
#ifdef HAVE_SCRIPT
if (do_classes && daemon->lease_change_command)
{
struct dhcp_netid *n;
if (mess->giaddr.s_addr)
lease->giaddr = mess->giaddr;
lease->changed = 1;
/* copy user-class and vendor class into new lease, for the script */
free(lease->extradata);
lease->extradata_size = lease->extradata_len = 0;
add_extradata_opt(lease, option_find(mess, sz, OPTION_VENDOR_ID, 1));
add_extradata_opt(lease, option_find(mess, sz, OPTION_HOSTNAME, 1));
add_extradata_opt(lease, oui);
add_extradata_opt(lease, serial);
add_extradata_opt(lease, class);
/* space-concat tag set */
if (!netid)
add_extradata_opt(lease, NULL);
else
for (n = netid; n; n = n->next)
add_extradata_data(lease, (unsigned char *)n->net, strlen(n->net), n->next ? ' ' : 0);
if ((opt = option_find(mess, sz, OPTION_USER_CLASS, 1)))
{
int len = option_len(opt);
@@ -1149,39 +1218,11 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
/* If the user-class option started as counted strings, the first byte will be zero. */
if (len != 0 && ucp[0] == 0)
ucp++, len--;
free(lease->userclass);
if ((lease->userclass = whine_malloc(len+1)))
{
memcpy(lease->userclass, ucp, len);
lease->userclass[len] = 0;
lease->userclass_len = len+1;
}
}
if ((opt = option_find(mess, sz, OPTION_VENDOR_ID, 1)))
{
int len = option_len(opt);
unsigned char *ucp = option_ptr(opt, 0);
free(lease->vendorclass);
if ((lease->vendorclass = whine_malloc(len+1)))
{
memcpy(lease->vendorclass, ucp, len);
lease->vendorclass[len] = 0;
lease->vendorclass_len = len+1;
}
}
if ((opt = option_find(mess, sz, OPTION_HOSTNAME, 1)))
{
int len = option_len(opt);
unsigned char *ucp = option_ptr(opt, 0);
free(lease->supplied_hostname);
if ((lease->supplied_hostname = whine_malloc(len+1)))
{
memcpy(lease->supplied_hostname, ucp, len);
lease->supplied_hostname[len] = 0;
lease->supplied_hostname_len = len+1;
}
add_extradata_data(lease, ucp, len, 0);
}
}
#endif
if (!hostname_auth && (client_hostname = host_from_dns(mess->yiaddr)))
{
@@ -1189,12 +1230,6 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
hostname_auth = 1;
}
if (context->netid.net)
{
context->netid.next = netid;
netid = &context->netid;
}
time = calc_time(context, config, option_find(mess, sz, OPTION_LEASE_TIME, 4));
lease_set_hwaddr(lease, mess->chaddr, clid, mess->hlen, mess->htype, clid_len);
@@ -1299,6 +1334,37 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
return 0;
}
static int match_bytes(struct dhcp_opt *o, unsigned char *p, int len)
{
int i;
if (o->len > len)
return 0;
if (o->len == 0)
return 1;
if (o->flags & DHOPT_HEX)
{
if (memcmp_masked(o->val, p, o->len, o->u.wildcard_mask))
return 1;
}
else
for (i = 0; i <= (len - o->len); )
{
if (memcmp(o->val, p + i, o->len) == 0)
return 1;
if (o->flags & DHOPT_STRING)
i++;
else
i += o->len;
}
return 0;
}
/* find a good value to use as MAC address for logging and address-allocation hashing.
This is normally just the chaddr field from the DHCP packet,
but eg Firewire will have hlen == 0 and use the client-id instead.
@@ -1383,6 +1449,55 @@ static int sanitise(unsigned char *opt, char *buf)
return 1;
}
#ifdef HAVE_SCRIPT
static void add_extradata_data(struct dhcp_lease *lease, unsigned char *data, size_t len, int delim)
{
if ((lease->extradata_size - lease->extradata_len) < (len + 1))
{
size_t newsz = lease->extradata_len + len + 100;
unsigned char *new = whine_malloc(newsz);
if (!new)
return;
if (lease->extradata)
{
memcpy(new, lease->extradata, lease->extradata_len);
free(lease->extradata);
}
lease->extradata = new;
lease->extradata_size = newsz;
}
if (len != 0)
memcpy(lease->extradata + lease->extradata_len, data, len);
lease->extradata[lease->extradata_len + len] = delim;
lease->extradata_len += len + 1;
}
static void add_extradata_opt(struct dhcp_lease *lease, unsigned char *opt)
{
if (!opt)
add_extradata_data(lease, NULL, 0, 0);
else
{
size_t i, len = option_len(opt);
unsigned char *ucp = option_ptr(opt, 0);
/* check for embeded NULLs */
for (i = 0; i < len; i++)
if (ucp[i] == 0)
{
len = i;
break;
}
add_extradata_data(lease, ucp, len, 0);
}
}
#endif
static void log_packet(char *type, void *addr, unsigned char *ext_mac,
int mac_len, char *interface, char *string, u32 xid)
{
@@ -1761,7 +1876,7 @@ static struct dhcp_opt *option_find2(struct dhcp_netid *netid, struct dhcp_opt *
{
struct dhcp_opt *tmp;
for (tmp = opts; tmp; tmp = tmp->next)
if (tmp->opt == opt && !(tmp->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR)))
if (tmp->opt == opt && !(tmp->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925)))
if (match_netid(tmp->netid, netid, netid ? 0 : 1))
return tmp;
@@ -1861,7 +1976,7 @@ static int prune_vendor_opts(struct dhcp_netid *netid)
return force;
}
static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid)
static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local)
{
#define NUM_OPTS 4
@@ -1869,18 +1984,16 @@ static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid)
struct pxe_service *service;
static struct dhcp_opt *o, *ret;
int i, j = NUM_OPTS - 1;
struct in_addr boot_server;
/* We pass back references to these, hence they are declared static */
static unsigned char discovery_control;
static unsigned char fake_prompt[] = { 0, 'P', 'X', 'E' };
static struct dhcp_opt *fake_opts = NULL;
/* We are found by broadcast, so disable multicast. It gets switched on again
if we point to other servers and don't give a unicast address. Note that
we don't provide our own address for services we are the boot server for because unicast
discovery is to port 4011 and we don't listen there. If you are using proxy DHCP
and DHCP relays, the relay will need to forward to the proxy too. */
discovery_control = 2;
/* Disable multicast, since we don't support it, and broadcast
unless we need it */
discovery_control = 3;
ret = daemon->dhcp_opts;
@@ -1920,26 +2033,25 @@ static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid)
return daemon->dhcp_opts;
}
if (!service->basename)
boot_server = service->basename ? local : service->server;
if (boot_server.s_addr != 0)
{
if (service->server.s_addr != 0)
{
if (q - (unsigned char *)daemon->dhcp_buff2 + 3 + INADDRSZ >= 253)
goto toobig;
/* Boot service with known address - give it */
*(q++) = service->type >> 8;
*(q++) = service->type;
*(q++) = 1;
/* dest misaligned */
memcpy(q, &service->server.s_addr, INADDRSZ);
q += INADDRSZ;
}
else if (service->type != 0)
/* We're not supplying a server, so let the client multicast.
type zero is "local boot" so no need for M/C on that. */
discovery_control = 0;
}
if (q - (unsigned char *)daemon->dhcp_buff2 + 3 + INADDRSZ >= 253)
goto toobig;
/* Boot service with known address - give it */
*(q++) = service->type >> 8;
*(q++) = service->type;
*(q++) = 1;
/* dest misaligned */
memcpy(q, &boot_server.s_addr, INADDRSZ);
q += INADDRSZ;
}
else if (service->type != 0)
/* We don't know the server for a service type, so we'll
allow the client to broadcast for it */
discovery_control = 2;
}
/* if no prompt, wait forever if there's a choice */
@@ -1975,14 +2087,11 @@ static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid)
ret->opt = SUBOPT_PXE_MENU_PROMPT;
}
if (discovery_control != 0)
{
ret = &fake_opts[j--];
ret->len = 1;
ret->opt = SUBOPT_PXE_DISCOVERY;
ret->val= &discovery_control;
}
ret = &fake_opts[j--];
ret->len = 1;
ret->opt = SUBOPT_PXE_DISCOVERY;
ret->val= &discovery_control;
return ret;
}
@@ -2270,45 +2379,78 @@ static void do_options(struct dhcp_context *context,
/* Now send options to be encapsulated in arbitrary options,
eg dhcp-option=encap:172,17,.......
Also hand vendor-identifying vendor-encapsulated options,
dhcp-option = rfc3925-encap:13,17,.......
The may be more that one "outer" to do, so group
all the options which match each outer in turn. */
for (opt = config_opts; opt; opt = opt->next)
opt->flags &= ~DHOPT_ENCAP_DONE;
for (opt = config_opts; opt; opt = opt->next)
if ((opt->flags & (DHOPT_ENCAPSULATE | DHOPT_ENCAP_DONE)) == DHOPT_ENCAPSULATE)
{
struct dhcp_opt *o;
int found = 0;
for (o = config_opts; o; o = o->next)
{
o->flags &= ~DHOPT_ENCAP_MATCH;
if ((o->flags & DHOPT_ENCAPSULATE) && opt->u.encap == o->u.encap)
{
o->flags |= DHOPT_ENCAP_DONE;
if (match_netid(o->netid, netid, 1) &&
(o->flags & DHOPT_FORCE || in_list(req_options, o->u.encap)))
{
o->flags |= DHOPT_ENCAP_MATCH;
found = 1;
}
}
}
if (found)
do_encap_opts(config_opts, opt->u.encap, DHOPT_ENCAP_MATCH, mess, end, null_term);
}
{
int flags;
if ((flags = (opt->flags & (DHOPT_ENCAPSULATE | DHOPT_RFC3925))))
{
int found = 0;
struct dhcp_opt *o;
if (opt->flags & DHOPT_ENCAP_DONE)
continue;
for (len = 0, o = config_opts; o; o = o->next)
{
int outer = flags & DHOPT_ENCAPSULATE ? o->u.encap : OPTION_VENDOR_IDENT_OPT;
o->flags &= ~DHOPT_ENCAP_MATCH;
if (!(o->flags & flags) || opt->u.encap != o->u.encap)
continue;
o->flags |= DHOPT_ENCAP_DONE;
if (match_netid(o->netid, netid, 1) &&
((o->flags & DHOPT_FORCE) || in_list(req_options, outer)))
{
o->flags |= DHOPT_ENCAP_MATCH;
found = 1;
len += do_opt(o, NULL, NULL, 0) + 2;
}
}
if (found)
{
if (flags & DHOPT_ENCAPSULATE)
do_encap_opts(config_opts, opt->u.encap, DHOPT_ENCAP_MATCH, mess, end, null_term);
else if (len > 250)
my_syslog(MS_DHCP | LOG_WARNING, _("cannot send RFC3925 option: too many options for enterprise number %d"), opt->u.encap);
else if ((p = free_space(mess, end, OPTION_VENDOR_IDENT_OPT, len + 5)))
{
int swap_ent = htonl(opt->u.encap);
memcpy(p, &swap_ent, 4);
p += 4;
*(p++) = len;
for (o = config_opts; o; o = o->next)
if (o->flags & DHOPT_ENCAP_MATCH)
{
len = do_opt(o, p + 2, NULL, 0);
*(p++) = o->opt;
*(p++) = len;
p += len;
}
}
}
}
}
/* Must precede pxe_opts, since it overwrites req_options */
force_encap = prune_vendor_opts(netid);
if (in_list(req_options, OPTION_VENDOR_CLASS_OPT))
force_encap = 1;
if (pxe_arch != -1)
if (context && pxe_arch != -1)
{
pxe_misc(mess, end, uuid);
config_opts = pxe_opts(pxe_arch, netid);
config_opts = pxe_opts(pxe_arch, netid, context->local);
}
if (force_encap)