diff --git a/src/dhcp6.c b/src/dhcp6.c index 76aaf87..832c3a6 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -265,8 +265,8 @@ struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct return NULL; } -int address6_allocate(struct dhcp_context *context, unsigned char *clid, int clid_len, - int serial, struct dhcp_netid *netids, struct in6_addr *ans) +struct dhcp_context *address6_allocate(struct dhcp_context *context, unsigned char *clid, int clid_len, + int iaid, struct dhcp_netid *netids, struct in6_addr *ans) { /* Find a free address: exclude anything in use and anything allocated to a particular hwaddr/clientid/hostname in our configuration. @@ -283,12 +283,12 @@ int address6_allocate(struct dhcp_context *context, unsigned char *clid, int cl /* hash hwaddr: use the SDBM hashing algorithm. This works for MAC addresses, let's see how it manages with client-ids! */ - for (j = 0, i = 0; i < clid_len; i++) + for (j = iaid, i = 0; i < clid_len; i++) j += clid[i] + (j << 6) + (j << 16) - j; for (pass = 0; pass <= 1; pass++) for (c = context; c; c = c->current) - if (c->flags & (CONTEXT_DEPRECATE | CONTEXT_STATIC | CONTEXT_RA_STATELESS)) + if (c->flags & (CONTEXT_DEPRECATE | CONTEXT_STATIC | CONTEXT_RA_STATELESS | CONTEXT_USED)) continue; else if (!match_netid(c->filter, netids, pass)) continue; @@ -296,9 +296,9 @@ int address6_allocate(struct dhcp_context *context, unsigned char *clid, int cl { if (option_bool(OPT_CONSEC_ADDR)) /* seed is largest extant lease addr in this context */ - start = lease_find_max_addr6(c) + serial; + start = lease_find_max_addr6(c); else - start = addr6part(&c->start6) + ((j + c->addr_epoch + serial) % (1 + addr6part(&c->end6) - addr6part(&c->start6))); + start = addr6part(&c->start6) + ((j + c->addr_epoch) % (1 + addr6part(&c->end6) - addr6part(&c->start6))); /* iterate until we find a free address. */ addr = start; @@ -315,7 +315,7 @@ int address6_allocate(struct dhcp_context *context, unsigned char *clid, int cl { *ans = c->start6; setaddr6part (ans, addr); - return 1; + return c; } addr++; @@ -325,7 +325,7 @@ int address6_allocate(struct dhcp_context *context, unsigned char *clid, int cl } while (addr != start); } - + return 0; } @@ -369,34 +369,6 @@ struct dhcp_context *address6_valid(struct dhcp_context *context, return NULL; } -struct dhcp_context *narrow_context6(struct dhcp_context *context, - struct in6_addr *taddr, - struct dhcp_netid *netids) -{ - /* We start of with a set of possible contexts, all on the current physical interface. - These are chained on ->current. - Here we have an address, and return the actual context correponding to that - address. Note that none may fit, if the address came a dhcp-host and is outside - any dhcp-range. In that case we return a static range if possible, or failing that, - any context on the correct subnet. (If there's more than one, this is a dodgy - configuration: maybe there should be a warning.) */ - - struct dhcp_context *tmp; - - if (!(tmp = address6_available(context, taddr, netids)) && - !(tmp = address6_valid(context, taddr, netids))) - for (tmp = context; tmp; tmp = tmp->current) - if (match_netid(tmp->filter, netids, 1) && - is_same_net6(taddr, &tmp->start6, tmp->prefix)) - break; - - /* Only one context allowed now */ - if (tmp) - tmp->current = NULL; - - return tmp; -} - static int is_config_in_context6(struct dhcp_context *context, struct dhcp_config *config) { /* expand wildcard on contructed contexts */ diff --git a/src/dnsmasq.h b/src/dnsmasq.h index a24cf41..3a872c4 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -681,6 +681,14 @@ struct cond_domain { struct cond_domain *next; }; +#ifdef OPTION6_PREFIX_CLASS +struct prefix_class { + int class; + struct dhcp_netid netid; + struct prefix_class *next; +}; +#endif + struct dhcp_context { unsigned int lease_time, addr_epoch; struct in_addr netmask, broadcast; @@ -714,6 +722,7 @@ struct dhcp_context { #define CONTEXT_GC 4096 #define CONTEXT_RA 8192 #define CONTEXT_WILDCARD 16384 +#define CONTEXT_USED 32768 struct ping_result { struct in_addr addr; @@ -1055,7 +1064,8 @@ struct dhcp_lease *lease4_allocate(struct in_addr addr); struct dhcp_lease *lease6_allocate(struct in6_addr *addrp, int lease_type); struct dhcp_lease *lease6_find(unsigned char *clid, int clid_len, int lease_type, int iaid, struct in6_addr *addr); -void lease6_filter(int lease_type, int iaid, struct dhcp_context *context); +void lease6_reset(void); +struct dhcp_lease *lease6_find_by_client(struct dhcp_lease *first, int lease_type, unsigned char *clid, int clid_len, int iaid); struct dhcp_lease *lease6_find_by_addr(struct in6_addr *net, int prefix, u64 addr); u64 lease_find_max_addr6(struct dhcp_context *context); void lease_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface); @@ -1160,8 +1170,8 @@ int get_incoming_mark(union mysockaddr *peer_addr, struct all_addr *local_addr, #ifdef HAVE_DHCP6 void dhcp6_init(void); void dhcp6_packet(time_t now); -int address6_allocate(struct dhcp_context *context, unsigned char *clid, int clid_len, - int serial, struct dhcp_netid *netids, struct in6_addr *ans); +struct dhcp_context *address6_allocate(struct dhcp_context *context, unsigned char *clid, int clid_len, + int iaid, struct dhcp_netid *netids, struct in6_addr *ans); int is_addr_in_context6(struct dhcp_context *context, struct in6_addr *addr); struct dhcp_context *address6_available(struct dhcp_context *context, struct in6_addr *taddr, @@ -1169,9 +1179,6 @@ struct dhcp_context *address6_available(struct dhcp_context *context, struct dhcp_context *address6_valid(struct dhcp_context *context, struct in6_addr *taddr, struct dhcp_netid *netids); -struct dhcp_context *narrow_context6(struct dhcp_context *context, - struct in6_addr *taddr, - struct dhcp_netid *netids); struct dhcp_config *find_config6(struct dhcp_config *configs, struct dhcp_context *context, unsigned char *duid, int duid_len, diff --git a/src/lease.c b/src/lease.c index 889d6f3..a29cd9e 100644 --- a/src/lease.c +++ b/src/lease.c @@ -555,8 +555,7 @@ struct dhcp_lease *lease_find_by_addr(struct in_addr addr) } #ifdef HAVE_DHCP6 -/* addr or clid may be NULL for "don't care, both NULL resets "USED" flags both - set activates USED check */ +/* find address for {CLID, IAID, address} */ struct dhcp_lease *lease6_find(unsigned char *clid, int clid_len, int lease_type, int iaid, struct in6_addr *addr) { @@ -567,40 +566,52 @@ struct dhcp_lease *lease6_find(unsigned char *clid, int clid_len, if (!(lease->flags & lease_type) || lease->hwaddr_type != iaid) continue; - if (clid && addr && (lease->flags & LEASE_USED)) + if (memcmp(lease->hwaddr, addr, IN6ADDRSZ) != 0) continue; - if (addr && memcmp(lease->hwaddr, addr, IN6ADDRSZ) != 0) - continue; - - if (clid && - (clid_len != lease->clid_len || + if ((clid_len != lease->clid_len || memcmp(clid, lease->clid, clid_len) != 0)) continue; - lease->flags |= LEASE_USED; return lease; } return NULL; } -void lease6_filter(int lease_type, int iaid, struct dhcp_context *context) +/* reset "USED flags */ +void lease6_reset(void) { struct dhcp_lease *lease; for (lease = leases; lease; lease = lease->next) + lease->flags &= ~LEASE_USED; +} + +/* enumerate all leases belonging to {CLID, IAID} */ +struct dhcp_lease *lease6_find_by_client(struct dhcp_lease *first, int lease_type, unsigned char *clid, int clid_len, int iaid) +{ + struct dhcp_lease *lease; + + if (!first) + first = leases; + + for (lease = first; lease; lease = lease->next) { - /* reset "USED flag */ - lease->flags &= ~LEASE_USED; - + if (lease->flags & LEASE_USED) + continue; + if (!(lease->flags & lease_type) || lease->hwaddr_type != iaid) continue; - - /* leases on the wrong interface get filtered out here */ - if (!is_addr_in_context6(context, (struct in6_addr *)&lease->hwaddr)) - lease->flags |= LEASE_USED; + + if ((clid_len != lease->clid_len || + memcmp(clid, lease->clid, clid_len) != 0)) + continue; + + return lease; } + + return NULL; } struct dhcp_lease *lease6_find_by_addr(struct in6_addr *net, int prefix, u64 addr) diff --git a/src/rfc3315.c b/src/rfc3315.c index 785a426..bf3d2a6 100644 --- a/src/rfc3315.c +++ b/src/rfc3315.c @@ -19,16 +19,37 @@ #ifdef HAVE_DHCP6 +struct state { + unsigned char *clid; + int clid_len, iaid, ia_type, interface, hostname_auth; + char *client_hostname, *hostname, *domain, *send_domain; + struct dhcp_context *context; + struct in6_addr *link_address; + unsigned int xid, fqdn_flags; + char *iface_name; + void *packet_options, *end; + struct dhcp_netid *tags, *context_tags; +}; + static int dhcp6_maybe_relay(struct in6_addr *link_address, struct dhcp_netid **relay_tagsp, struct dhcp_context *context, int interface, char *iface_name, struct in6_addr *fallback, void *inbuff, size_t sz, int is_unicast, time_t now); static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dhcp_netid *tags, struct dhcp_context *context, int interface, char *iface_name, struct in6_addr *fallback, void *inbuff, size_t sz, int is_unicast, time_t now); static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_opts); -static void log6_packet(char *type, unsigned char *clid, int clid_len, struct in6_addr *addr, int xid, char *iface, char *string); +static void log6_packet(struct state *state, char *type, struct in6_addr *addr, char *string); static void *opt6_find (void *opts, void *end, unsigned int search, unsigned int minsize); static void *opt6_next(void *opts, void *end); static unsigned int opt6_uint(unsigned char *opt, int offset, int size); +static void get_context_tag(struct state *state, struct dhcp_context *context); +static void *check_ia(struct state *state, void *opt, void **endp); +static int build_ia(struct state *state, int *t1cntr); +static void end_ia(int t1cntr, unsigned int min_time, int do_fuzz); +static void add_address(struct state *state, struct dhcp_context *context, unsigned int lease_time, unsigned int requested_time, + unsigned int *min_time, struct in6_addr *addr, int update_lease, time_t now); +static void update_leases(struct state *state, struct dhcp_context *context, struct in6_addr *addr, unsigned int lease_time, time_t now); +static int add_local_addrs(struct dhcp_context *context); +struct dhcp_netid *add_options(struct state *state, struct in6_addr *fallback, struct dhcp_context *context); #define opt6_len(opt) ((int)(opt6_uint(opt, -2, 2))) #define opt6_type(opt) (opt6_uint(opt, -4, 2)) @@ -172,39 +193,49 @@ static int dhcp6_maybe_relay(struct in6_addr *link_address, struct dhcp_netid ** static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dhcp_netid *tags, struct dhcp_context *context, int interface, char *iface_name, struct in6_addr *fallback, void *inbuff, size_t sz, int is_unicast, time_t now) { - void *packet_options = inbuff + 4; - void *end = inbuff + sz; - void *opt, *oro; - int i, o, o1; - unsigned char *clid = NULL; - int clid_len = 0, start_opts; - struct dhcp_netid *tagif, *context_tags = NULL; - char *client_hostname= NULL, *hostname = NULL; - char *domain = NULL, *send_domain = NULL; + void *opt; + int i, o, o1, start_opts; + struct dhcp_opt *opt_cfg; + struct dhcp_netid *tagif; struct dhcp_config *config = NULL; struct dhcp_netid known_id, iface_id, v6_id; - int done_dns = 0, hostname_auth = 0, do_encap = 0; unsigned char *outmsgtypep; - struct dhcp_opt *opt_cfg; struct dhcp_vendor *vendor; struct dhcp_context *context_tmp; - unsigned int xid, ignore = 0; - unsigned int fqdn_flags = 0x01; /* default to send if we recieve no FQDN option */ + unsigned int ignore = 0; + struct state state; + + state.packet_options = inbuff + 4; + state.end = inbuff + sz; + state.clid = NULL; + state.clid_len = 0; + state.context_tags = NULL; + state.tags = tags; + state.link_address = link_address; + state.interface = interface; + state.domain = NULL; + state.send_domain = NULL; + state.context = context; + state.hostname_auth = 0; + state.hostname = NULL; + state.client_hostname = NULL; + state.iface_name = iface_name; + state.fqdn_flags = 0x01; /* default to send if we recieve no FQDN option */ /* set tag with name == interface */ iface_id.net = iface_name; - iface_id.next = tags; - tags = &iface_id; + iface_id.next = state.tags; + state.tags = &iface_id; /* set tag "dhcpv6" */ v6_id.net = "dhcpv6"; - v6_id.next = tags; - tags = &v6_id; + v6_id.next = state.tags; + state.tags = &v6_id; /* copy over transaction-id, and save pointer to message type */ outmsgtypep = put_opt6(inbuff, 4); start_opts = save_counter(-1); - xid = outmsgtypep[3] | outmsgtypep[2] << 8 | outmsgtypep[1] << 16; + state.xid = outmsgtypep[3] | outmsgtypep[2] << 8 | outmsgtypep[1] << 16; /* We're going to be linking tags from all context we use. mark them as unused so we don't link one twice and break the list */ @@ -218,19 +249,19 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh inet_ntop(AF_INET6, &context_tmp->end6, daemon->dhcp_buff2, ADDRSTRLEN); if (context_tmp->flags & (CONTEXT_STATIC)) my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCPv6 subnet: %s/%d"), - xid, daemon->dhcp_buff, context_tmp->prefix); + state.xid, daemon->dhcp_buff, context_tmp->prefix); else my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP range: %s -- %s"), - xid, daemon->dhcp_buff, daemon->dhcp_buff2); + state.xid, daemon->dhcp_buff, daemon->dhcp_buff2); } } - if ((opt = opt6_find(packet_options, end, OPTION6_CLIENT_ID, 1))) + if ((opt = opt6_find(state.packet_options, state.end, OPTION6_CLIENT_ID, 1))) { - clid = opt6_ptr(opt, 0); - clid_len = opt6_len(opt); + state.clid = opt6_ptr(opt, 0); + state.clid_len = opt6_len(opt); o = new_opt6(OPTION6_CLIENT_ID); - put_opt6(clid, clid_len); + put_opt6(state.clid, state.clid_len); end_opt6(o); } else if (msg_type != DHCP6IREQ) @@ -238,7 +269,7 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh /* server-id must match except for SOLICIT and CONFIRM messages */ if (msg_type != DHCP6SOLICIT && msg_type != DHCP6CONFIRM && msg_type != DHCP6IREQ && - (!(opt = opt6_find(packet_options, end, OPTION6_SERVER_ID, 1)) || + (!(opt = opt6_find(state.packet_options, state.end, OPTION6_SERVER_ID, 1)) || opt6_len(opt) != daemon->duid_len || memcmp(opt6_ptr(opt, 0), daemon->duid, daemon->duid_len) != 0)) return 0; @@ -270,7 +301,7 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh else continue; - if ((opt = opt6_find(packet_options, end, mopt, 2))) + if ((opt = opt6_find(state.packet_options, state.end, mopt, 2))) { void *enc_opt, *enc_end = opt6_ptr(opt, opt6_len(opt)); int offset = 0; @@ -290,15 +321,15 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh for (i = 0; i <= (opt6_len(enc_opt) - vendor->len); i++) if (memcmp(vendor->data, opt6_ptr(enc_opt, i), vendor->len) == 0) { - vendor->netid.next = tags; - tags = &vendor->netid; + vendor->netid.next = state.tags; + state.tags = &vendor->netid; break; } } } - if (option_bool(OPT_LOG_OPTS) && (opt = opt6_find(packet_options, end, OPTION6_VENDOR_CLASS, 4))) - my_syslog(MS_DHCP | LOG_INFO, _("%u vendor class: %u"), xid, opt6_uint(opt, 0, 4)); + if (option_bool(OPT_LOG_OPTS) && (opt = opt6_find(state.packet_options, state.end, OPTION6_VENDOR_CLASS, 4))) + my_syslog(MS_DHCP | LOG_INFO, _("%u vendor class: %u"), state.xid, opt6_uint(opt, 0, 4)); /* 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. @@ -310,9 +341,9 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh if (opt_cfg->flags & DHOPT_RFC3925) { - for (opt = opt6_find(packet_options, end, OPTION6_VENDOR_OPTS, 4); + for (opt = opt6_find(state.packet_options, state.end, OPTION6_VENDOR_OPTS, 4); opt; - opt = opt6_find(opt6_next(opt, end), end, OPTION6_VENDOR_OPTS, 4)) + opt = opt6_find(opt6_next(opt, state.end), state.end, OPTION6_VENDOR_OPTS, 4)) { void *vopt; void *vend = opt6_ptr(opt, opt6_len(opt)); @@ -328,7 +359,7 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh } else { - if (!(opt = opt6_find(packet_options, end, opt_cfg->opt, 1))) + if (!(opt = opt6_find(state.packet_options, state.end, opt_cfg->opt, 1))) continue; match = match_bytes(opt_cfg, opt6_ptr(opt, 0), opt6_len(opt)); @@ -336,23 +367,23 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh if (match) { - opt_cfg->netid->next = tags; - tags = opt_cfg->netid; + opt_cfg->netid->next = state.tags; + state.tags = opt_cfg->netid; } } - if ((opt = opt6_find(packet_options, end, OPTION6_FQDN, 1))) + if ((opt = opt6_find(state.packet_options, state.end, OPTION6_FQDN, 1))) { /* RFC4704 refers */ int len = opt6_len(opt) - 1; - fqdn_flags = opt6_uint(opt, 0, 1); + state.fqdn_flags = opt6_uint(opt, 0, 1); /* Always force update, since the client has no way to do it itself. */ - if (!option_bool(OPT_FQDN_UPDATE) && !(fqdn_flags & 0x01)) - fqdn_flags |= 0x03; + if (!option_bool(OPT_FQDN_UPDATE) && !(state.fqdn_flags & 0x01)) + state.fqdn_flags |= 0x03; - fqdn_flags &= ~0x04; + state.fqdn_flags &= ~0x04; if (len != 0 && len < 255) { @@ -374,36 +405,36 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh if (legal_hostname(daemon->dhcp_buff)) { - client_hostname = daemon->dhcp_buff; + state.client_hostname = daemon->dhcp_buff; if (option_bool(OPT_LOG_OPTS)) - my_syslog(MS_DHCP | LOG_INFO, _("%u client provides name: %s"), xid, client_hostname); + my_syslog(MS_DHCP | LOG_INFO, _("%u client provides name: %s"), state.xid, state.client_hostname); } } } - if (clid) + if (state.clid) { - config = find_config6(daemon->dhcp_conf, context, clid, clid_len, NULL); + config = find_config6(daemon->dhcp_conf, context, state.clid, state.clid_len, NULL); if (have_config(config, CONFIG_NAME)) { - hostname = config->hostname; - domain = config->domain; - hostname_auth = 1; + state.hostname = config->hostname; + state.domain = config->domain; + state.hostname_auth = 1; } - else if (client_hostname) + else if (state.client_hostname) { - domain = strip_hostname(client_hostname); + state.domain = strip_hostname(state.client_hostname); - if (strlen(client_hostname) != 0) + if (strlen(state.client_hostname) != 0) { - hostname = client_hostname; + state.hostname = state.client_hostname; if (!config) { /* Search again now we have a hostname. Only accept configs without CLID here, (it won't match) to avoid impersonation by name. */ - struct dhcp_config *new = find_config6(daemon->dhcp_conf, context, NULL, 0, hostname); + struct dhcp_config *new = find_config6(daemon->dhcp_conf, context, NULL, 0, state.hostname); if (new && !have_config(new, CONFIG_CLID) && !new->hwaddr) config = new; } @@ -417,20 +448,20 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh for (list = config->netid; list; list = list->next) { - list->list->next = tags; - tags = list->list; + list->list->next = state.tags; + state.tags = list->list; } /* set "known" tag for known hosts */ known_id.net = "known"; - known_id.next = tags; - tags = &known_id; + known_id.next = state.tags; + state.tags = &known_id; if (have_config(config, CONFIG_DISABLE)) ignore = 1; } - tagif = run_tag_if(tags); + tagif = run_tag_if(state.tags); /* if all the netids in the ignore list are present, ignore this client */ if (daemon->dhcp_ignore) @@ -443,7 +474,7 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh } /* if all the netids in the ignore_name list are present, ignore client-supplied name */ - if (!hostname_auth) + if (!state.hostname_auth) { struct dhcp_netid_list *id_list; @@ -451,7 +482,7 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh if ((!id_list->list) || match_netid(id_list->list, tagif, 0)) break; if (id_list) - hostname = NULL; + state.hostname = NULL; } @@ -460,13 +491,12 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh default: return 0; - case DHCP6SOLICIT: - case DHCP6REQUEST: - { - void *rapid_commit = opt6_find(packet_options, end, OPTION6_RAPID_COMMIT, 0); - int make_lease = (msg_type == DHCP6REQUEST || rapid_commit); - int serial = 0, used_config = 0; + case DHCP6SOLICIT: + { + void *rapid_commit = opt6_find(state.packet_options, state.end, OPTION6_RAPID_COMMIT, 0); + int used_config = 0, address_assigned = 0; + if (rapid_commit) { o = new_opt6(OPTION6_RAPID_COMMIT); @@ -474,396 +504,268 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh } /* set reply message type */ - *outmsgtypep = make_lease ? DHCP6REPLY : DHCP6ADVERTISE; - - log6_packet(msg_type == DHCP6SOLICIT ? "DHCPSOLICIT" : "DHCPREQUEST", - clid, clid_len, NULL, xid, iface_name, ignore ? "ignored" : NULL); + *outmsgtypep = rapid_commit ? DHCP6REPLY : DHCP6ADVERTISE; + + log6_packet(&state, "DHCPSOLICIT", NULL, ignore ? "ignored" : NULL); if (ignore) return 0; - for (opt = packet_options; opt; opt = opt6_next(opt, end)) + for (opt = state.packet_options; opt; opt = opt6_next(opt, state.end)) { - int iaid, ia_type = opt6_type(opt); void *ia_option, *ia_end; unsigned int min_time = 0xffffffff; - int t1cntr = 0; - int address_assigned = 0; + int t1cntr; + int config_ok = 0; + struct dhcp_context *c; + u32 lease_time, requested_time; + struct dhcp_lease *ltmp; + struct in6_addr *req_addr; + struct in6_addr addr; - if (ia_type != OPTION6_IA_NA && ia_type != OPTION6_IA_TA) + if (!(ia_option = check_ia(&state, opt, &ia_end))) continue; - if (ia_type == OPTION6_IA_NA && opt6_len(opt) < 12) - continue; - - if (ia_type == OPTION6_IA_TA && opt6_len(opt) < 4) - continue; - - iaid = opt6_uint(opt, 0, 4); - ia_end = opt6_ptr(opt, opt6_len(opt)); - ia_option = opt6_find(opt6_ptr(opt, ia_type == OPTION6_IA_NA ? 12 : 4), ia_end, OPTION6_IAADDR, 24); - - /* reset "USED" flags on leases */ - lease6_filter(ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, iaid, context); - - o = new_opt6(ia_type); - put_opt6_long(iaid); - if (ia_type == OPTION6_IA_NA) + if (have_config(config, CONFIG_ADDR6)) { - /* save pointer */ - t1cntr = save_counter(-1); - /* so we can fill these in later */ - put_opt6_long(0); - put_opt6_long(0); + struct dhcp_lease *config_lease = lease6_find_by_addr(&config->addr6, 128, 0); + + config_ok = 1; + + if (config_lease && + (config_lease->clid_len != state.clid_len || + memcmp(config_lease->clid, state.clid, state.clid_len) != 0 || + config_lease->hwaddr_type != state.iaid)) + config_ok = 0; /* configured address leased elsewhere */ + } + + /* reset USED bits in leases */ + lease6_reset(); + + o = build_ia(&state, &t1cntr); + + for (; ia_option; ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) + { + req_addr = opt6_ptr(ia_option, 0); + ltmp = lease6_find_by_addr(req_addr, 128, 0); + requested_time = opt6_uint(ia_option, 16, 4); + + /* reset USED bits in contexts - one address per prefix per IAID */ + for (c = context; c; c = c->current) + c->flags &= ~CONTEXT_USED; + + if ((c = address6_valid(context, req_addr, tagif))) + { + lease_time = c->lease_time; + /* If the client asks for an address on the same network as a configured address, + offer the configured address instead, to make moving to newly-configured + addresses automatic. */ + if (!used_config && config_ok && is_same_net6(req_addr, &config->addr6, c->prefix)) + { + req_addr = &config->addr6; + used_config = 1; + if (have_config(config, CONFIG_TIME)) + lease_time = config->lease_time; + } + else if (!(c = address6_available(context, req_addr, tagif))) + continue; /* not an address we're allowed */ + else if (ltmp && + (ltmp->clid_len != state.clid_len || + memcmp(ltmp->clid, state.clid, state.clid_len) != 0 || + ltmp->hwaddr_type != state.iaid)) + continue; /* address leased elsewhere */ + + /* add address to output packet */ + add_address(&state, c, lease_time, requested_time, &min_time, req_addr, rapid_commit != NULL, now); + get_context_tag(&state, c); + address_assigned = 1; + } } - while (1) + /* Suggest configured address */ + if (!used_config && config_ok && (c = address6_valid(context, &config->addr6, tagif))) { - struct in6_addr alloced_addr, *addrp = NULL; - u32 requested_time = 0; - struct dhcp_lease *lease = NULL; - - if (ia_option) - { - struct in6_addr *req_addr = opt6_ptr(ia_option, 0); - requested_time = opt6_uint(ia_option, 16, 4); - struct dhcp_context *dynamic; + used_config = 1; + if (have_config(config, CONFIG_TIME)) + lease_time = config->lease_time; + /* add address to output packet */ + add_address(&state, c, lease_time, requested_time, &min_time, &config->addr6, rapid_commit != NULL, now); + get_context_tag(&state, c); + address_assigned = 1; + } - if ((dynamic = address6_available(context, req_addr, tags)) || address6_valid(context, req_addr, tags)) - { - if (!dynamic && !(have_config(config, CONFIG_ADDR6) && memcmp(&config->addr6, req_addr, IN6ADDRSZ) == 0)) - { - /* Static range, not configured. */ - o1 = new_opt6(OPTION6_STATUS_CODE); - put_opt6_short(DHCP6UNSPEC); - put_opt6_string("Address unavailable"); - end_opt6(o1); - } - else if (lease6_find_by_addr(req_addr, 128, 0) && - !(lease = lease6_find(clid, clid_len, ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, iaid, req_addr))) - { - /* Address leased to another DUID/IAID */ - o1 = new_opt6(OPTION6_STATUS_CODE); - put_opt6_short(DHCP6UNSPEC); - put_opt6_string("Address in use"); - end_opt6(o1); - } - else - addrp = req_addr; - } - else if (msg_type == DHCP6REQUEST) - { - /* requested address not on the correct link */ - o1 = new_opt6(OPTION6_STATUS_CODE); - put_opt6_short(DHCP6NOTONLINK); - put_opt6_string("Not on link"); - end_opt6(o1); - } - } - else + /* return addresses for existing leases */ + ltmp = NULL; + while ((ltmp = lease6_find_by_client(ltmp, state.ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, state.clid, state.clid_len, state.iaid))) + { + req_addr = (struct in6_addr *)ltmp->hwaddr; + if ((c = address6_available(context, req_addr, tagif))) { - /* must have an address to CONFIRM */ - if (msg_type == DHCP6REQUEST && ia_type == OPTION6_IA_NA) - return 0; - - /* Don't used configured addresses for temporary leases. */ - if (have_config(config, CONFIG_ADDR6) && !used_config && ia_type == OPTION6_IA_NA) - { - struct dhcp_lease *ltmp = lease6_find_by_addr(&config->addr6, 128, 0); - - used_config = 1; - inet_ntop(AF_INET6, &config->addr6, daemon->addrbuff, ADDRSTRLEN); - - if (ltmp && ltmp->clid && - (ltmp->clid_len != clid_len || memcmp(ltmp->clid, clid, clid_len) != 0 || ltmp->hwaddr_type != iaid)) - my_syslog(MS_DHCP | LOG_WARNING, _("not using configured address %s because it is leased to %s#%d"), - daemon->addrbuff, print_mac(daemon->namebuff, ltmp->clid, ltmp->clid_len), ltmp->hwaddr_type); - else if (have_config(config, CONFIG_DECLINED) && - difftime(now, config->decline_time) < (float)DECLINE_BACKOFF) - my_syslog(MS_DHCP | LOG_WARNING, _("not using configured address %s because it was previously declined"), - daemon->addrbuff); - else - { - addrp = &config->addr6; - /* may have existing lease for this address */ - lease = lease6_find(clid, clid_len, - ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, iaid, addrp); - } - } - - /* existing lease */ - if (!addrp && - (lease = lease6_find(clid, clid_len, - ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, iaid, NULL)) && - !config_find_by_address6(daemon->dhcp_conf, (struct in6_addr *)&lease->hwaddr, 128, 0)) - addrp = (struct in6_addr *)&lease->hwaddr; - - if (!addrp && address6_allocate(context, clid, clid_len, serial++, tags, &alloced_addr)) - addrp = &alloced_addr; - } - - if (addrp) - { - unsigned int lease_time; - struct dhcp_context *this_context; - struct dhcp_config *valid_config = config; - - /* don't use a config to set lease time if it specifies an address which isn't this. */ - if (have_config(config, CONFIG_ADDR6) && memcmp(&config->addr6, addrp, IN6ADDRSZ) != 0) - valid_config = NULL; - + add_address(&state, c, c->lease_time, c->lease_time, &min_time, req_addr, rapid_commit != NULL, now); + get_context_tag(&state, c); address_assigned = 1; - - /* shouldn't ever fail */ - if ((this_context = narrow_context6(context, addrp, tagif))) - { - /* get tags from context if we've not used it before */ - if (this_context->netid.next == &this_context->netid && this_context->netid.net) - { - this_context->netid.next = context_tags; - context_tags = &this_context->netid; - if (!hostname_auth) - { - struct dhcp_netid_list *id_list; - - for (id_list = daemon->dhcp_ignore_names; id_list; id_list = id_list->next) - if ((!id_list->list) || match_netid(id_list->list, &this_context->netid, 0)) - break; - if (id_list) - hostname = NULL; - } - } - - if (have_config(valid_config, CONFIG_TIME)) - lease_time = valid_config->lease_time; - else - lease_time = this_context->lease_time; - - if (this_context->valid < lease_time) - lease_time = this_context->valid; - - if (ia_option) - { - if (requested_time < 120u ) - requested_time = 120u; /* sanity */ - if (lease_time == 0xffffffff || (requested_time != 0xffffffff && requested_time < lease_time)) - lease_time = requested_time; - } - - if (lease_time < min_time) - min_time = lease_time; - - /* May fail to create lease */ - if (!lease && make_lease) - lease = lease6_allocate(addrp, ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA); - - if (lease) - { - lease_set_expires(lease, lease_time, now); - lease_set_hwaddr(lease, NULL, clid, 0, iaid, clid_len, now, 0); - lease_set_interface(lease, interface, now); - if (hostname && ia_type == OPTION6_IA_NA) - { - char *addr_domain = get_domain6(addrp); - if (!send_domain) - send_domain = addr_domain; - lease_set_hostname(lease, hostname, hostname_auth, addr_domain, domain); - } - -#ifdef HAVE_SCRIPT - if (daemon->lease_change_command) - { - void *class_opt; - lease->flags |= LEASE_CHANGED; - free(lease->extradata); - lease->extradata = NULL; - lease->extradata_size = lease->extradata_len = 0; - lease->hwaddr_len = 0; /* surrogate for no of vendor classes */ - - if ((class_opt = opt6_find(packet_options, end, OPTION6_VENDOR_CLASS, 4))) - { - void *enc_opt, *enc_end = opt6_ptr(class_opt, opt6_len(class_opt)); - lease->hwaddr_len++; - /* send enterprise number first */ - sprintf(daemon->dhcp_buff2, "%u", opt6_uint(class_opt, 0, 4)); - lease_add_extradata(lease, (unsigned char *)daemon->dhcp_buff2, strlen(daemon->dhcp_buff2), 0); - - if (opt6_len(class_opt) >= 6) - for (enc_opt = opt6_ptr(class_opt, 4); enc_opt; enc_opt = opt6_next(enc_opt, enc_end)) - { - lease->hwaddr_len++; - lease_add_extradata(lease, opt6_ptr(enc_opt, 0), opt6_len(enc_opt), 0); - } - } - - lease_add_extradata(lease, (unsigned char *)client_hostname, - client_hostname ? strlen(client_hostname) : 0, 0); - - /* space-concat tag set */ - if (!tagif && !context_tags) - lease_add_extradata(lease, NULL, 0, 0); - else - { - struct dhcp_netid *n, *l, *tmp = tags; - - /* link temporarily */ - for (n = context_tags; n && n->next; n = n->next); - if ((l = n)) - { - l->next = tags; - tmp = context_tags; - } - - for (n = run_tag_if(tmp); n; n = n->next) - { - struct dhcp_netid *n1; - /* kill dupes */ - for (n1 = n->next; n1; n1 = n1->next) - if (strcmp(n->net, n1->net) == 0) - break; - if (!n1) - lease_add_extradata(lease, (unsigned char *)n->net, strlen(n->net), n->next ? ' ' : 0); - } - - /* unlink again */ - if (l) - l->next = NULL; - } - - if (link_address) - inet_ntop(AF_INET6, link_address, daemon->addrbuff, ADDRSTRLEN); - - lease_add_extradata(lease, (unsigned char *)daemon->addrbuff, link_address ? strlen(daemon->addrbuff) : 0, 0); - - if ((class_opt = opt6_find(packet_options, end, OPTION6_USER_CLASS, 2))) - { - void *enc_opt, *enc_end = opt6_ptr(class_opt, opt6_len(class_opt)); - for (enc_opt = opt6_ptr(class_opt, 0); enc_opt; enc_opt = opt6_next(enc_opt, enc_end)) - lease_add_extradata(lease, opt6_ptr(enc_opt, 0), opt6_len(enc_opt), 0); - } - } -#endif - - } - else if (!send_domain) - send_domain = get_domain6(addrp); - - - if (lease || !make_lease) - { - o1 = new_opt6(OPTION6_IAADDR); - put_opt6(addrp, sizeof(*addrp)); - /* preferred lifetime */ - put_opt6_long(this_context && (this_context->preferred < lease_time) ? - this_context->preferred : lease_time); - put_opt6_long(lease_time); /* valid lifetime */ - end_opt6(o1); - - log6_packet( make_lease ? "DHCPREPLY" : "DHCPADVERTISE", - clid, clid_len, addrp, xid, iface_name, hostname); - } - - } } - - - if (!ia_option || - !(ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24))) - { - if (address_assigned) - { - o1 = new_opt6(OPTION6_STATUS_CODE); - put_opt6_short(DHCP6SUCCESS); - put_opt6_string("Oh hai from dnsmasq"); - end_opt6(o1); - - if (ia_type == OPTION6_IA_NA) - { - /* go back an fill in fields in IA_NA option */ - unsigned int t1 = min_time == 0xffffffff ? 0xffffffff : min_time/2; - unsigned int t2 = min_time == 0xffffffff ? 0xffffffff : (min_time/8) * 7; - int sav = save_counter(t1cntr); - put_opt6_long(t1); - put_opt6_long(t2); - save_counter(sav); - } - } - else - { - /* no address, return error */ - o1 = new_opt6(OPTION6_STATUS_CODE); - put_opt6_short(DHCP6NOADDRS); - put_opt6_string("No addresses available"); - end_opt6(o1); - } - - end_opt6(o); - - if (address_assigned) - { - /* If --dhcp-authoritative is set, we can tell client not to wait for - other possible servers */ - o = new_opt6(OPTION6_PREFERENCE); - put_opt6_char(option_bool(OPT_AUTHORITATIVE) ? 255 : 0); - end_opt6(o); - } - - break; - } - } + } + + /* Return addresses for all valid contexts which don't yet have one */ + while ((c = address6_allocate(c, state.clid, state.clid_len, state.iaid, tagif, &addr))) + { + add_address(&state, c, c->lease_time, c->lease_time, &min_time, &addr, rapid_commit != NULL, now); + get_context_tag(&state, c); + address_assigned = 1; + } + + end_ia(t1cntr, min_time, 0); + end_opt6(o); } - + + if (address_assigned) + { + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6SUCCESS); + put_opt6_string("Oh hai from dnsmasq"); + end_opt6(o1); + + /* If --dhcp-authoritative is set, we can tell client not to wait for + other possible servers */ + o = new_opt6(OPTION6_PREFERENCE); + put_opt6_char(option_bool(OPT_AUTHORITATIVE) ? 255 : 0); + end_opt6(o); + } + else + { + /* no address, return error */ + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6NOADDRS); + put_opt6_string("No addresses available"); + end_opt6(o1); + } + + tagif = add_options(&state, fallback, context); break; - } - + } + + case DHCP6REQUEST: + { + int address_assigned = 0; + + /* set reply message type */ + *outmsgtypep = DHCP6REPLY; + + log6_packet(&state, "DHCPREQUEST", NULL, ignore ? "ignored" : NULL); + + if (ignore) + return 0; + + for (opt = state.packet_options; opt; opt = opt6_next(opt, state.end)) + { + void *ia_option, *ia_end; + unsigned int min_time = 0xffffffff; + int t1cntr; + + if (!(ia_option = check_ia(&state, opt, &ia_end))) + continue; + + o = build_ia(&state, &t1cntr); + + for (; ia_option; ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) + { + struct in6_addr *req_addr = opt6_ptr(ia_option, 0); + u32 requested_time = opt6_uint(ia_option, 16, 4); + struct dhcp_context *dynamic, *c; + unsigned int lease_time; + + if ((dynamic = address6_available(context, req_addr, tagif)) || (c = address6_valid(context, req_addr, tagif))) + { + if (!dynamic && !(have_config(config, CONFIG_ADDR6) || !IN6_ARE_ADDR_EQUAL(&config->addr6, req_addr))) + { + /* Static range, not configured. */ + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6UNSPEC); + put_opt6_string("Address unavailable"); + end_opt6(o1); + } + else if (lease6_find_by_addr(req_addr, 128, 0) && + !lease6_find(state.clid, state.clid_len, state.ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, state.iaid, req_addr)) + { + /* Address leased to another DUID/IAID */ + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6UNSPEC); + put_opt6_string("Address in use"); + end_opt6(o1); + } + else + { + if (!dynamic) + dynamic = c; + + lease_time = dynamic->lease_time; + + if (have_config(config, CONFIG_ADDR6) && + IN6_ARE_ADDR_EQUAL(&config->addr6, req_addr) && + have_config(config, CONFIG_TIME)) + lease_time = config->lease_time; + + add_address(&state, dynamic, lease_time, requested_time, &min_time, req_addr, 1, now); + get_context_tag(&state, dynamic); + address_assigned = 1; + } + } + else if (msg_type == DHCP6REQUEST) + { + /* requested address not on the correct link */ + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6NOTONLINK); + put_opt6_string("Not on link"); + end_opt6(o1); + } + } + + end_ia(t1cntr, min_time, 0); + end_opt6(o); + } + + if (address_assigned) + { + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6SUCCESS); + put_opt6_string("Oh hai from dnsmasq"); + end_opt6(o1); + } + else + { + /* no address, return error */ + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6NOADDRS); + put_opt6_string("No addresses available"); + end_opt6(o1); + } + + tagif = add_options(&state, fallback, context); + break; + } + + case DHCP6RENEW: { /* set reply message type */ *outmsgtypep = DHCP6REPLY; - log6_packet("DHCPRENEW", clid, clid_len, NULL, xid, iface_name, NULL); + log6_packet(&state, "DHCPRENEW", NULL, NULL); - for (opt = packet_options; opt; opt = opt6_next(opt, end)) + for (opt = state.packet_options; opt; opt = opt6_next(opt, state.end)) { - int ia_type = opt6_type(opt); void *ia_option, *ia_end; unsigned int min_time = 0xffffffff; - int t1cntr = 0, iacntr; - unsigned int iaid; + int t1cntr, iacntr; - if (ia_type != OPTION6_IA_NA && ia_type != OPTION6_IA_TA) + if (!(ia_option = check_ia(&state, opt, &ia_end))) continue; - if (ia_type == OPTION6_IA_NA && opt6_len(opt) < 12) - continue; - - if (ia_type == OPTION6_IA_TA && opt6_len(opt) < 4) - continue; - - iaid = opt6_uint(opt, 0, 4); - - o = new_opt6(ia_type); - put_opt6_long(iaid); - if (ia_type == OPTION6_IA_NA) - { - /* save pointer */ - t1cntr = save_counter(-1); - /* so we can fill these in later */ - put_opt6_long(0); - put_opt6_long(0); - } - + o = build_ia(&state, &t1cntr); iacntr = save_counter(-1); - /* reset "USED" flags on leases */ - lease6_filter(ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, iaid, context); - - ia_option = opt6_ptr(opt, ia_type == OPTION6_IA_NA ? 12 : 4); - ia_end = opt6_ptr(opt, opt6_len(opt)); - - for (ia_option = opt6_find(ia_option, ia_end, OPTION6_IAADDR, 24); - ia_option; - ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) + for (; ia_option; ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) { struct dhcp_lease *lease = NULL; struct in6_addr *req_addr = opt6_ptr(ia_option, 0); @@ -873,12 +775,12 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh struct dhcp_config *valid_config = config; /* don't use a config to set lease time if it specifies an address which isn't this. */ - if (have_config(config, CONFIG_ADDR6) && memcmp(&config->addr6, req_addr, IN6ADDRSZ) != 0) + if (have_config(config, CONFIG_ADDR6) && !IN6_ARE_ADDR_EQUAL(&config->addr6, req_addr)) valid_config = NULL; - if (!(lease = lease6_find(clid, clid_len, - ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, - iaid, req_addr))) + if (!(lease = lease6_find(state.clid, state.clid_len, + state.ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, + state.iaid, req_addr))) { /* If the server cannot find a client entry for the IA the server returns the IA containing no addresses with a Status Code option set @@ -886,7 +788,7 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh save_counter(iacntr); t1cntr = 0; - log6_packet("DHCPREPLY", clid, clid_len, req_addr, xid, iface_name, "lease not found"); + log6_packet(&state, "DHCPREPLY", req_addr, "lease not found"); o1 = new_opt6(OPTION6_STATUS_CODE); put_opt6_short(DHCP6NOBINDING); @@ -896,30 +798,10 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh } - if (!address6_available(context, req_addr, tagif) || - !(this_context = narrow_context6(context, req_addr, tagif))) + if ((this_context != address6_available(context, req_addr, tagif)) || + (this_context != address6_valid(context, req_addr, tagif))) { - lease_time = 0; - this_context = NULL; - } - else - { - /* get tags from context if we've not used it before */ - if (this_context->netid.next == &this_context->netid && this_context->netid.net) - { - this_context->netid.next = context_tags; - context_tags = &this_context->netid; - if (!hostname_auth) - { - struct dhcp_netid_list *id_list; - - for (id_list = daemon->dhcp_ignore_names; id_list; id_list = id_list->next) - if ((!id_list->list) || match_netid(id_list->list, &this_context->netid, 0)) - break; - if (id_list) - hostname = NULL; - } - } + get_context_tag(&state, this_context); lease_time = have_config(valid_config, CONFIG_TIME) ? valid_config->lease_time : this_context->lease_time; @@ -929,46 +811,33 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh lease_time = requested_time; lease_set_expires(lease, lease_time, now); - if (ia_type == OPTION6_IA_NA && hostname) + if (state.ia_type == OPTION6_IA_NA && state.hostname) { char *addr_domain = get_domain6(req_addr); - if (!send_domain) - send_domain = addr_domain; - lease_set_hostname(lease, hostname, hostname_auth, addr_domain, domain); + if (!state.send_domain) + state.send_domain = addr_domain; + lease_set_hostname(lease, state.hostname, state.hostname_auth, addr_domain, state.domain); } if (lease_time < min_time) min_time = lease_time; } - log6_packet("DHCPREPLY", clid, clid_len, req_addr, xid, iface_name, hostname); + log6_packet(&state, "DHCPREPLY", req_addr, state.hostname); o1 = new_opt6(OPTION6_IAADDR); put_opt6(req_addr, sizeof(*req_addr)); /* preferred lifetime */ - put_opt6_long(this_context && (this_context->flags & CONTEXT_DEPRECATE) ? 0 : lease_time); - put_opt6_long(lease_time); /* valid lifetime */ + put_opt6_long(!this_context || (this_context->flags & CONTEXT_DEPRECATE) ? 0 : lease_time); + put_opt6_long(!this_context ? 0 : lease_time); /* valid lifetime */ end_opt6(o1); } - if (t1cntr != 0) - { - /* go back an fill in fields in IA_NA option */ - int sav = save_counter(t1cntr); - unsigned int t1, t2, fuzz = rand16(); - - while (fuzz > (min_time/16)) - fuzz = fuzz/2; - t1 = min_time == 0xffffffff ? 0xffffffff : min_time/2 - fuzz; - t2 = min_time == 0xffffffff ? 0xffffffff : ((min_time/8)*7) - fuzz; - - put_opt6_long(t1); - put_opt6_long(t2); - save_counter(sav); - } - + end_ia(t1cntr, min_time, 1); end_opt6(o); } + + tagif = add_options(&state, fallback, context); break; } @@ -978,32 +847,19 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh /* set reply message type */ *outmsgtypep = DHCP6REPLY; - log6_packet("DHCPCONFIRM", clid, clid_len, NULL, xid, iface_name, NULL); + log6_packet(&state, "DHCPCONFIRM", NULL, NULL); - for (opt = packet_options; opt; opt = opt6_next(opt, end)) + for (opt = state.packet_options; opt; opt = opt6_next(opt, state.end)) { - int ia_type = opt6_type(opt); void *ia_option, *ia_end; - if (ia_type != OPTION6_IA_NA && ia_type != OPTION6_IA_TA) - continue; - - if (ia_type == OPTION6_IA_NA && opt6_len(opt) < 12) - continue; - - if (ia_type == OPTION6_IA_TA && opt6_len(opt) < 4) - continue; - - ia_option = opt6_ptr(opt, ia_type == OPTION6_IA_NA ? 12 : 4); - ia_end = opt6_ptr(opt, opt6_len(opt)); - - for (ia_option = opt6_find(ia_option, ia_end, OPTION6_IAADDR, 24); + for (ia_option = check_ia(&state, opt, &ia_end); ia_option; ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) { struct in6_addr *req_addr = opt6_ptr(ia_option, 0); - if (!address6_available(context, req_addr, run_tag_if(tags))) + if (!address6_available(context, req_addr, tagif)) { o1 = new_opt6(OPTION6_STATUS_CODE); put_opt6_short(DHCP6NOTONLINK); @@ -1012,7 +868,7 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh return 1; } - log6_packet("DHCPREPLY", clid, clid_len, req_addr, xid, iface_name, hostname); + log6_packet(&state, "DHCPREPLY", req_addr, state.hostname); } } @@ -1020,7 +876,7 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh put_opt6_short(DHCP6SUCCESS ); put_opt6_string("All addresses still on link"); end_opt6(o1); - return 1; + break; } case DHCP6IREQ: @@ -1030,12 +886,13 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh if (context && context->netid.net && !context->current) { context->netid.next = NULL; - context_tags = &context->netid; + state.context_tags = &context->netid; } - log6_packet("DHCPINFORMATION-REQUEST", clid, clid_len, NULL, xid, iface_name, ignore ? "ignored" : hostname); + log6_packet(&state, "DHCPINFORMATION-REQUEST", NULL, ignore ? "ignored" : state.hostname); if (ignore) return 0; *outmsgtypep = DHCP6REPLY; + tagif = add_options(&state, fallback, context); break; } @@ -1045,46 +902,29 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh /* set reply message type */ *outmsgtypep = DHCP6REPLY; - log6_packet("DHCPRELEASE", clid, clid_len, NULL, xid, iface_name, NULL); + log6_packet(&state, "DHCPRELEASE", NULL, NULL); - for (opt = packet_options; opt; opt = opt6_next(opt, end)) + for (opt = state.packet_options; opt; opt = opt6_next(opt, state.end)) { - int iaid, ia_type = opt6_type(opt); void *ia_option, *ia_end; int made_ia = 0; - if (ia_type != OPTION6_IA_NA && ia_type != OPTION6_IA_TA) - continue; - - if (ia_type == OPTION6_IA_NA && opt6_len(opt) < 12) - continue; - - if (ia_type == OPTION6_IA_TA && opt6_len(opt) < 4) - continue; - - iaid = opt6_uint(opt, 0, 4); - ia_end = opt6_ptr(opt, opt6_len(opt)); - ia_option = opt6_ptr(opt, ia_type == OPTION6_IA_NA ? 12 : 4); - - /* reset "USED" flags on leases */ - lease6_filter(ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, iaid, context); - - for (ia_option = opt6_find(ia_option, ia_end, OPTION6_IAADDR, 24); + for (ia_option = check_ia(&state, opt, &ia_end); ia_option; - ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) + ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) { struct dhcp_lease *lease; - if ((lease = lease6_find(clid, clid_len, ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, - iaid, opt6_ptr(ia_option, 0)))) + if ((lease = lease6_find(state.clid, state.clid_len, state.ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, + state.iaid, opt6_ptr(ia_option, 0)))) lease_prune(lease, now); else { if (!made_ia) { - o = new_opt6(ia_type); - put_opt6_long(iaid); - if (ia_type == OPTION6_IA_NA) + o = new_opt6(state.ia_type); + put_opt6_long(state.iaid); + if (state.ia_type == OPTION6_IA_NA) { put_opt6_long(0); put_opt6_long(0); @@ -1116,7 +956,7 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh put_opt6_string("Release received"); end_opt6(o1); - return 1; + break; } case DHCP6DECLINE: @@ -1124,39 +964,21 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh /* set reply message type */ *outmsgtypep = DHCP6REPLY; - log6_packet("DHCPDECLINE", clid, clid_len, NULL, xid, iface_name, NULL); + log6_packet(&state, "DHCPDECLINE", NULL, NULL); - for (opt = packet_options; opt; opt = opt6_next(opt, end)) + for (opt = state.packet_options; opt; opt = opt6_next(opt, state.end)) { - int iaid, ia_type = opt6_type(opt); void *ia_option, *ia_end; int made_ia = 0; - if (ia_type != OPTION6_IA_NA && ia_type != OPTION6_IA_TA) - continue; - - if (ia_type == OPTION6_IA_NA && opt6_len(opt) < 12) - continue; - - if (ia_type == OPTION6_IA_TA && opt6_len(opt) < 4) - continue; - - iaid = opt6_uint(opt, 0, 4); - ia_end = opt6_ptr(opt, opt6_len(opt)); - ia_option = opt6_ptr(opt, ia_type == OPTION6_IA_NA ? 12 : 4); - - /* reset "USED" flags on leases */ - lease6_filter(ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, iaid, context); - - for (ia_option = opt6_find(ia_option, ia_end, OPTION6_IAADDR, 24); + for (ia_option = check_ia(&state, opt, &ia_end); ia_option; - ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) + ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) { struct dhcp_lease *lease; struct in6_addr *addrp = opt6_ptr(ia_option, 0); - if (have_config(config, CONFIG_ADDR6) && - memcmp(&config->addr6, addrp, IN6ADDRSZ) == 0) + if (have_config(config, CONFIG_ADDR6) && IN6_ARE_ADDR_EQUAL(&config->addr6, addrp)) { prettyprint_time(daemon->dhcp_buff3, DECLINE_BACKOFF); inet_ntop(AF_INET6, addrp, daemon->addrbuff, ADDRSTRLEN); @@ -1170,16 +992,16 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh for (; context; context = context->current) context->addr_epoch++; - if ((lease = lease6_find(clid, clid_len, ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, - iaid, opt6_ptr(ia_option, 0)))) + if ((lease = lease6_find(state.clid, state.clid_len, state.ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, + state.iaid, opt6_ptr(ia_option, 0)))) lease_prune(lease, now); else { if (!made_ia) { - o = new_opt6(ia_type); - put_opt6_long(iaid); - if (ia_type == OPTION6_IA_NA) + o = new_opt6(state.ia_type); + put_opt6_long(state.iaid); + if (state.ia_type == OPTION6_IA_NA) { put_opt6_long(0); put_opt6_long(0); @@ -1206,16 +1028,30 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh } } - return 1; + break; } } + log_tags(tagif, state.xid); + if (option_bool(OPT_LOG_OPTS)) + log6_opts(0, state.xid, daemon->outpacket.iov_base + start_opts, daemon->outpacket.iov_base + save_counter(-1)); + + return 1; + +} + +struct dhcp_netid *add_options(struct state *state, struct in6_addr *fallback, struct dhcp_context *context) +{ + void *opt, *oro; /* filter options based on tags, those we want get DHOPT_TAGOK bit set */ - tagif = option_filter(tags, context_tags, daemon->dhcp_opts6); - - oro = opt6_find(packet_options, end, OPTION6_ORO, 0); + struct dhcp_netid *tagif = option_filter(state->tags, state->context_tags, daemon->dhcp_opts6); + struct dhcp_opt *opt_cfg; + int done_dns = 0, do_encap = 0; + int i, o, o1; + + oro = opt6_find(state->packet_options, state->end, OPTION6_ORO, 0); for (opt_cfg = daemon->dhcp_opts6; opt_cfg; opt_cfg = opt_cfg->next) { @@ -1251,10 +1087,8 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh /* zero means "self" (but not in vendorclass options.) */ if (IN6_IS_ADDR_UNSPECIFIED(a)) { - if (IN6_IS_ADDR_UNSPECIFIED(&context->local6)) + if (!add_local_addrs(context)) put_opt6(fallback, IN6ADDRSZ); - else - put_opt6(&context->local6, IN6ADDRSZ); } else put_opt6(a, IN6ADDRSZ); @@ -1270,10 +1104,8 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh !IN6_IS_ADDR_UNSPECIFIED(fallback))) { o = new_opt6(OPTION6_DNS_SERVER); - if (IN6_IS_ADDR_UNSPECIFIED(&context->local6)) + if (!add_local_addrs(context)) put_opt6(fallback, IN6ADDRSZ); - else - put_opt6(&context->local6, IN6ADDRSZ); end_opt6(o); } @@ -1334,21 +1166,21 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh } - if (hostname) + if (state->hostname) { unsigned char *p; - size_t len = strlen(hostname); + size_t len = strlen(state->hostname); - if (send_domain) - len += strlen(send_domain) + 1; + if (state->send_domain) + len += strlen(state->send_domain) + 1; o = new_opt6(OPTION6_FQDN); if ((p = expand(len + 3))) { - *(p++) = fqdn_flags; - p = do_rfc1035_name(p, hostname); - if (send_domain) - p = do_rfc1035_name(p, send_domain); + *(p++) = state->fqdn_flags; + p = do_rfc1035_name(p, state->hostname); + if (state->send_domain) + p = do_rfc1035_name(p, state->send_domain); *p = 0; } end_opt6(o); @@ -1371,19 +1203,270 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh if ( i > opt6_len(oro) - 3 || (q - daemon->namebuff) > 40) { q = daemon->namebuff; - my_syslog(MS_DHCP | LOG_INFO, _("%u requested options: %s"), xid, daemon->namebuff); + my_syslog(MS_DHCP | LOG_INFO, _("%u requested options: %s"), state->xid, daemon->namebuff); } } - } - - log_tags(tagif, xid); + } - if (option_bool(OPT_LOG_OPTS)) - log6_opts(0, xid, daemon->outpacket.iov_base + start_opts, daemon->outpacket.iov_base + save_counter(-1)); + return tagif; +} + +static int add_local_addrs(struct dhcp_context *context) +{ + int done = 0; - return 1; + for (; context; context = context->current) + if ((context->flags & CONTEXT_USED) && !IN6_IS_ADDR_UNSPECIFIED(&context->local6)) + { + /* squash duplicates */ + struct dhcp_context *c; + for (c = context->current; c; c = c->current) + if ((c->flags & CONTEXT_USED) && + IN6_ARE_ADDR_EQUAL(&context->local6, &c->local6)) + break; + + if (!c) + { + done = 1; + put_opt6(&context->local6, IN6ADDRSZ); + } + } + + return done; } + +static void get_context_tag(struct state *state, struct dhcp_context *context) +{ + /* get tags from context if we've not used it before */ + if (context->netid.next == &context->netid && context->netid.net) + { + context->netid.next = state->context_tags; + state->context_tags = &context->netid; + if (!state->hostname_auth) + { + struct dhcp_netid_list *id_list; + + for (id_list = daemon->dhcp_ignore_names; id_list; id_list = id_list->next) + if ((!id_list->list) || match_netid(id_list->list, &context->netid, 0)) + break; + if (id_list) + state->hostname = NULL; + } + } +} +static void *check_ia(struct state *state, void *opt, void **endp) +{ + state->ia_type = opt6_type(opt); + + if (state->ia_type != OPTION6_IA_NA && state->ia_type != OPTION6_IA_TA) + return NULL; + + if (state->ia_type == OPTION6_IA_NA && opt6_len(opt) < 12) + return NULL; + + if (state->ia_type == OPTION6_IA_TA && opt6_len(opt) < 4) + return NULL; + + *endp = opt6_ptr(opt, opt6_len(opt)); + state->iaid = opt6_uint(opt, 0, 4); + + return opt6_find(opt6_ptr(opt, state->ia_type == OPTION6_IA_NA ? 12 : 4), *endp, OPTION6_IAADDR, 24); +} + + +static int build_ia(struct state *state, int *t1cntr) +{ + int o = new_opt6(state->ia_type); + + put_opt6_long(state->iaid); + *t1cntr = 0; + + if (state->ia_type == OPTION6_IA_NA) + { + /* save pointer */ + *t1cntr = save_counter(-1); + /* so we can fill these in later */ + put_opt6_long(0); + put_opt6_long(0); + } + + return o; +} + +static void end_ia(int t1cntr, unsigned int min_time, int do_fuzz) +{ + if (t1cntr != 0) + { + /* go back an fill in fields in IA_NA option */ + int sav = save_counter(t1cntr); + unsigned int t1, t2, fuzz = 0; + + if (do_fuzz) + { + fuzz = rand16(); + + while (fuzz > (min_time/16)) + fuzz = fuzz/2; + } + + t1 = (min_time == 0xffffffff) ? 0xffffffff : min_time/2 - fuzz; + t2 = (min_time == 0xffffffff) ? 0xffffffff : ((min_time/8)*7) - fuzz; + put_opt6_long(t1); + put_opt6_long(t2); + save_counter(sav); + } +} + +static void add_address(struct state *state, struct dhcp_context *context, unsigned int lease_time, unsigned int requested_time, + unsigned int *min_time, struct in6_addr *addr, int do_update, time_t now) +{ + unsigned int valid_time = (context->valid < lease_time) ? context->valid : lease_time; + int o = new_opt6(OPTION6_IAADDR); + struct dhcp_lease *lease; + + put_opt6(addr, sizeof(*addr)); + + /* preferred lifetime */ + if (requested_time < 120u ) + requested_time = 120u; /* sanity */ + if (lease_time == 0xffffffff || (requested_time != 0xffffffff && requested_time < lease_time)) + lease_time = requested_time; + + if (lease_time < *min_time) + *min_time = lease_time; + + put_opt6_long((context->preferred < lease_time) ? context->preferred : lease_time); + put_opt6_long(valid_time); /* valid lifetime */ + + end_opt6(o); + + if (do_update) + update_leases(state, context, addr, valid_time, now); + + if ((lease = lease6_find_by_addr(addr, 128, 0))) + lease->flags |= LEASE_USED; + + /* get tags from context if we've not used it before */ + if (context->netid.next == &context->netid && context->netid.net) + { + context->netid.next = state->context_tags; + state->context_tags = &context->netid; + + if (!state->hostname_auth) + { + struct dhcp_netid_list *id_list; + + for (id_list = daemon->dhcp_ignore_names; id_list; id_list = id_list->next) + if ((!id_list->list) || match_netid(id_list->list, &context->netid, 0)) + break; + if (id_list) + state->hostname = NULL; + } + } + + /* Mark that we have an address for this prefix. */ + for (context = state->context; context; context = context->current) + if (is_same_net6(addr, &context->start6, context->prefix)) + context->flags |= CONTEXT_USED; + + log6_packet(state, do_update ? "DHCPREPLY" : "DHCPADVERTISE", addr, state->hostname); + +} + +static void update_leases(struct state *state, struct dhcp_context *context, struct in6_addr *addr, unsigned int lease_time, time_t now) +{ + struct dhcp_lease *lease = lease6_find_by_addr(addr, 128, 0); + struct dhcp_netid *tagif = run_tag_if(state->tags); + + if (!lease) + lease = lease6_allocate(addr, state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA); + + if (lease) + { + lease_set_expires(lease, lease_time, now); + lease_set_hwaddr(lease, NULL, state->clid, 0, state->iaid, state->clid_len, now, 0); + lease_set_interface(lease, state->interface, now); + if (state->hostname && state->ia_type == OPTION6_IA_NA) + { + char *addr_domain = get_domain6(addr); + if (!state->send_domain) + state->send_domain = addr_domain; + lease_set_hostname(lease, state->hostname, state->hostname_auth, addr_domain, state->domain); + } + +#ifdef HAVE_SCRIPT + if (daemon->lease_change_command) + { + void *class_opt; + lease->flags |= LEASE_CHANGED; + free(lease->extradata); + lease->extradata = NULL; + lease->extradata_size = lease->extradata_len = 0; + lease->hwaddr_len = 0; /* surrogate for no of vendor classes */ + + if ((class_opt = opt6_find(state->packet_options, state->end, OPTION6_VENDOR_CLASS, 4))) + { + void *enc_opt, *enc_end = opt6_ptr(class_opt, opt6_len(class_opt)); + lease->hwaddr_len++; + /* send enterprise number first */ + sprintf(daemon->dhcp_buff2, "%u", opt6_uint(class_opt, 0, 4)); + lease_add_extradata(lease, (unsigned char *)daemon->dhcp_buff2, strlen(daemon->dhcp_buff2), 0); + + if (opt6_len(class_opt) >= 6) + for (enc_opt = opt6_ptr(class_opt, 4); enc_opt; enc_opt = opt6_next(enc_opt, enc_end)) + { + lease->hwaddr_len++; + lease_add_extradata(lease, opt6_ptr(enc_opt, 0), opt6_len(enc_opt), 0); + } + } + + lease_add_extradata(lease, (unsigned char *)state->client_hostname, + state->client_hostname ? strlen(state->client_hostname) : 0, 0); + + /* space-concat tag set */ + if (!tagif && !context->netid.net) + lease_add_extradata(lease, NULL, 0, 0); + else + { + if (context->netid.net) + lease_add_extradata(lease, (unsigned char *)context->netid.net, strlen(context->netid.net), tagif ? ' ' : 0); + + if (tagif) + { + struct dhcp_netid *n; + for (n = tagif; n; n = n->next) + { + struct dhcp_netid *n1; + /* kill dupes */ + for (n1 = n->next; n1; n1 = n1->next) + if (strcmp(n->net, n1->net) == 0) + break; + if (!n1) + lease_add_extradata(lease, (unsigned char *)n->net, strlen(n->net), n->next ? ' ' : 0); + } + } + } + + if (state->link_address) + inet_ntop(AF_INET6, state->link_address, daemon->addrbuff, ADDRSTRLEN); + + lease_add_extradata(lease, (unsigned char *)daemon->addrbuff, state->link_address ? strlen(daemon->addrbuff) : 0, 0); + + if ((class_opt = opt6_find(state->packet_options, state->end, OPTION6_USER_CLASS, 2))) + { + void *enc_opt, *enc_end = opt6_ptr(class_opt, opt6_len(class_opt)); + for (enc_opt = opt6_ptr(class_opt, 0); enc_opt; enc_opt = opt6_next(enc_opt, enc_end)) + lease_add_extradata(lease, opt6_ptr(enc_opt, 0), opt6_len(enc_opt), 0); + } + } +#endif + + } +} + + + static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_opts) { void *opt; @@ -1441,13 +1524,15 @@ static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_op } } -static void log6_packet(char *type, unsigned char *clid, int clid_len, struct in6_addr *addr, int xid, char *iface, char *string) +static void log6_packet(struct state *state, char *type, struct in6_addr *addr, char *string) { + int clid_len = state->clid_len; + /* avoid buffer overflow */ if (clid_len > 100) clid_len = 100; - print_mac(daemon->namebuff, clid, clid_len); + print_mac(daemon->namebuff, state->clid, clid_len); if (addr) { @@ -1459,16 +1544,16 @@ static void log6_packet(char *type, unsigned char *clid, int clid_len, struct in if(option_bool(OPT_LOG_OPTS)) my_syslog(MS_DHCP | LOG_INFO, "%u %s(%s) %s%s %s", - xid, + state->xid, type, - iface, + state->iface_name, daemon->dhcp_buff2, daemon->namebuff, string ? string : ""); else my_syslog(MS_DHCP | LOG_INFO, "%s(%s) %s%s %s", type, - iface, + state->iface_name, daemon->dhcp_buff2, daemon->namebuff, string ? string : "");