From 353ae4d2707f51ad5e708aed39de9700f0311096 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Mon, 19 Mar 2012 20:07:51 +0000 Subject: [PATCH] Check assumed SLAAC addresses by pinging them. --- Makefile | 2 +- bld/Android.mk | 2 +- src/dhcp.c | 2 +- src/dhcp6.c | 2 +- src/dnsmasq.c | 31 ++++-- src/dnsmasq.h | 39 ++++--- src/lease.c | 124 ++++++++++++---------- src/netlink.c | 5 +- src/radv-protocol.h | 10 +- src/radv.c | 117 +++++++-------------- src/rfc2131.c | 10 +- src/rfc3315.c | 4 +- src/slaac.c | 243 ++++++++++++++++++++++++++++++++++++++++++++ 13 files changed, 414 insertions(+), 177 deletions(-) create mode 100644 src/slaac.c diff --git a/Makefile b/Makefile index 30e70a4..ee7d36f 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ version = -DVERSION='\"`$(top)/bld/get-version $(top)`\"' objs = cache.o rfc1035.o util.o option.o forward.o network.o \ dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \ helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \ - dhcp-common.o outpacket.o radv.o + dhcp-common.o outpacket.o radv.o slaac.o hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ dns-protocol.h radv-protocol.h diff --git a/bld/Android.mk b/bld/Android.mk index a4bc610..53e4a56 100644 --- a/bld/Android.mk +++ b/bld/Android.mk @@ -8,7 +8,7 @@ LOCAL_SRC_FILES := bpf.c cache.c dbus.c dhcp.c dnsmasq.c \ netlink.c network.c option.c rfc1035.c \ rfc2131.c tftp.c util.c conntrack.c \ dhcp6.c rfc3315.c dhcp-common.c outpacket.c \ - radv.c + radv.c slaac.c LOCAL_MODULE := dnsmasq diff --git a/src/dhcp.c b/src/dhcp.c index b95752c..045eb3a 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -299,7 +299,7 @@ void dhcp_packet(time_t now, int pxe_fd) iov.iov_len = dhcp_reply(parm.current, ifr.ifr_name, iface_index, (size_t)sz, now, unicast_dest, &is_inform, pxe_fd, iface_addr); lease_update_file(now); - lease_update_dns(); + lease_update_dns(0); if (iov.iov_len == 0) return; diff --git a/src/dhcp6.c b/src/dhcp6.c index 70c8dcd..e8502fb 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -139,7 +139,7 @@ void dhcp6_packet(time_t now) sz, IN6_IS_ADDR_MULTICAST(&from.sin6_addr), now); lease_update_file(now); - lease_update_dns(); + lease_update_dns(0); /* The port in the source address of the original request should be correct, but at least once client sends from the server port, diff --git a/src/dnsmasq.c b/src/dnsmasq.c index 515f08f..b2810c5 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -23,6 +23,7 @@ struct daemon *daemon; static volatile pid_t pid = 0; static volatile int pipewrite; +static int alarm_queued = 0; static int set_dns_listeners(time_t now, fd_set *set, int *maxfdp); static void check_dns_listeners(fd_set *set, time_t now); @@ -864,9 +865,23 @@ static void sig_handler(int sig) } } -void send_alarm(void) +/* now == 0 -> queue immediate callback */ +void send_alarm(time_t event, time_t now) { - send_event(pipewrite, EVENT_ALARM, 0, NULL); + + if (now != 0 && event == 0) + return; + + if ((now == 0 || difftime(event, now) <= 0.0)) + { + if (!alarm_queued) + { + send_event(pipewrite, EVENT_ALARM, 0, NULL); + alarm_queued = 1; + } + } + else + alarm((unsigned)difftime(event, now)); } void send_event(int fd, int event, int data, char *msg) @@ -980,6 +995,7 @@ static void async_event(int pipe, time_t now) break; case EVENT_ALARM: + alarm_queued = 0; #ifdef HAVE_DHCP if (daemon->dhcp || daemon->dhcp6) { @@ -988,13 +1004,8 @@ static void async_event(int pipe, time_t now) } #ifdef HAVE_DHCP6 else if (daemon->ra_contexts) - { - /* Not doing DHCP, so no lease system, manage - alarms for ra only */ - time_t next_event = periodic_ra(now); - if (next_event != 0) - alarm((unsigned)difftime(next_event, now)); - } + /* Not doing DHCP, so no lease system, manage alarms for ra only */ + send_alarm(periodic_ra(now), now); #endif #endif break; @@ -1158,7 +1169,7 @@ void clear_cache_and_reload(time_t now) check_dhcp_hosts(0); lease_update_from_configs(); lease_update_file(now); - lease_update_dns(); + lease_update_dns(1); } #ifdef HAVE_DHCP6 else if (daemon->ra_contexts) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index b35b580..88075cc 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -466,6 +466,7 @@ struct frec { #define LEASE_USED 16 /* used this DHCPv6 transaction */ #define LEASE_NA 32 /* IPv6 no-temporary lease */ #define LEASE_TA 64 /* IPv6 temporary lease */ +#define LEASE_HAVE_HWADDR 128 /* Have set hwaddress */ struct dhcp_lease { int clid_len; /* length of client identifier */ @@ -483,6 +484,14 @@ struct dhcp_lease { unsigned char *extradata; unsigned int extradata_len, extradata_size; int last_interface; +#ifdef HAVE_DHCP6 + struct slaac_address { + struct in6_addr addr, local; + time_t ping_time; + int backoff; /* zero -> confirmed */ + struct slaac_address *next; + } *slaac_address; +#endif struct dhcp_lease *next; }; @@ -627,7 +636,7 @@ struct dhcp_context { #ifdef HAVE_DHCP6 struct in6_addr start6, end6; /* range of available addresses */ struct in6_addr local6; - int prefix; + int prefix, if_index; time_t ra_time; #endif int flags; @@ -651,13 +660,6 @@ struct ping_result { struct ping_result *next; }; -struct subnet_map { - int iface; - struct in6_addr subnet; - struct subnet_map *next; -}; - - struct tftp_file { int refcount, fd; off_t size; @@ -957,7 +959,7 @@ char *host_from_dns(struct in_addr addr); /* lease.c */ #ifdef HAVE_DHCP void lease_update_file(time_t now); -void lease_update_dns(); +void lease_update_dns(int force); void lease_init(time_t now); struct dhcp_lease *lease4_allocate(struct in_addr addr); #ifdef HAVE_DHCP6 @@ -966,12 +968,13 @@ struct dhcp_lease *lease6_find(unsigned char *clid, int clid_len, int lease_type, int iaid, struct in6_addr *addr); 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); #endif void lease_set_hwaddr(struct dhcp_lease *lease, unsigned char *hwaddr, - unsigned char *clid, int hw_len, int hw_type, int clid_len); + unsigned char *clid, int hw_len, int hw_type, int clid_len, time_t now); void lease_set_hostname(struct dhcp_lease *lease, char *name, int auth, char *domain, char *config_domain); void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now); -void lease_set_interface(struct dhcp_lease *lease, int interface); +void lease_set_interface(struct dhcp_lease *lease, int interface, time_t now); struct dhcp_lease *lease_find_by_client(unsigned char *hwaddr, int hw_len, int hw_type, unsigned char *clid, int clid_len); struct dhcp_lease *lease_find_by_addr(struct in_addr addr); @@ -1000,7 +1003,7 @@ unsigned char *extended_hwaddr(int hwtype, int hwlen, unsigned char *hwaddr, int make_icmp_sock(void); int icmp_ping(struct in_addr addr); #endif -void send_alarm(void); +void send_alarm(time_t event, time_t now); void send_event(int fd, int event, int data, char *msg); void clear_cache_and_reload(time_t now); void poll_resolv(int force, int do_reload, time_t now); @@ -1121,6 +1124,14 @@ void put_opt6_string(char *s); void ra_init(time_t now); void icmp6_packet(void); time_t periodic_ra(time_t now); -void ra_start_unsolicted(time_t now); -struct subnet_map *build_subnet_map(void); +void ra_start_unsolicted(time_t now, struct dhcp_context *context); +#endif + +/* slaac.c */ +#ifdef HAVE_DHCP6 +void build_subnet_map(void); +void slaac_add_addrs(struct dhcp_lease *lease, time_t now); +time_t periodic_slaac(time_t now, struct dhcp_lease *leases); +void slaac_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface, struct dhcp_lease *leases); +void schedule_subnet_map(void); #endif diff --git a/src/lease.c b/src/lease.c index 3bc2222..608ca7d 100644 --- a/src/lease.c +++ b/src/lease.c @@ -97,7 +97,8 @@ void lease_init(time_t now) if (hw_type == 0 && hw_len != 0) hw_type = ARPHRD_ETHER; - lease_set_hwaddr(lease, (unsigned char *)daemon->dhcp_buff2, (unsigned char *)daemon->packet, hw_len, hw_type, clid_len); + lease_set_hwaddr(lease, (unsigned char *)daemon->dhcp_buff2, (unsigned char *)daemon->packet, + hw_len, hw_type, clid_len, now); if (strcmp(daemon->dhcp_buff, "*") != 0) lease_set_hostname(lease, daemon->dhcp_buff, 0, get_domain(lease->addr), NULL); @@ -118,7 +119,7 @@ void lease_init(time_t now) if ((lease = lease6_allocate(&addr.addr.addr6, lease_type))) { - lease_set_hwaddr(lease, NULL, (unsigned char *)daemon->packet, 0, hw_type, clid_len); + lease_set_hwaddr(lease, NULL, (unsigned char *)daemon->packet, 0, hw_type, clid_len, now); if (strcmp(daemon->dhcp_buff, "*") != 0) lease_set_hostname(lease, daemon->dhcp_buff, 0, get_domain6((struct in6_addr *)lease->hwaddr), NULL); @@ -306,9 +307,16 @@ void lease_update_file(time_t now) next_event = 0; #ifdef HAVE_DHCP6 - /* do timed RAs and determine when the next is */ + /* do timed RAs and determine when the next is, also pings to potential SLAAC addresses */ if (daemon->ra_contexts) - next_event = periodic_ra(now); + { + time_t ra_event = periodic_slaac(now, leases); + + next_event = periodic_ra(now); + + if (next_event == 0 || difftime(next_event, ra_event) > 0.0) + next_event = ra_event; + } #endif for (lease = leases; lease; lease = lease->next) @@ -326,8 +334,7 @@ void lease_update_file(time_t now) (unsigned int)difftime(next_event, now)); } - if (next_event != 0) - alarm((unsigned)difftime(next_event, now)); + send_alarm(next_event, now); } @@ -342,7 +349,7 @@ static int find_interface_v4(struct in_addr local, int if_index, for (lease = leases; lease; lease = lease->next) if (!(lease->flags & (LEASE_TA | LEASE_NA))) if (is_same_net(local, lease->addr, netmask)) - lease->last_interface = if_index; + lease_set_interface(lease, if_index, *((time_t *)vparam)); return 1; } @@ -353,17 +360,22 @@ static int find_interface_v6(struct in6_addr *local, int prefix, { struct dhcp_lease *lease; - (void) scope; - (void) vparam; + (void)scope; (void)dad; for (lease = leases; lease; lease = lease->next) if ((lease->flags & (LEASE_TA | LEASE_NA))) if (is_same_net6(local, (struct in6_addr *)&lease->hwaddr, prefix)) - lease->last_interface = if_index; + lease_set_interface(lease, if_index, *((time_t *)vparam)); return 1; } + +void lease_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface) +{ + slaac_ping_reply(sender, packet, interface, leases); +} + #endif @@ -373,9 +385,13 @@ static int find_interface_v6(struct in6_addr *local, int prefix, start-time. */ void lease_find_interfaces(time_t now) { - iface_enumerate(AF_INET, NULL, find_interface_v4); #ifdef HAVE_DHCP6 - iface_enumerate(AF_INET6, NULL, find_interface_v6); + build_subnet_map(); +#endif + + iface_enumerate(AF_INET, &now, find_interface_v4); +#ifdef HAVE_DHCP6 + iface_enumerate(AF_INET6, &now, find_interface_v6); /* If we're not doing DHCPv6, and there are not v6 leases, don't add the DUID to the database */ if (!daemon->duid && daemon->dhcp6) @@ -388,16 +404,12 @@ void lease_find_interfaces(time_t now) -void lease_update_dns(void) +void lease_update_dns(int force) { struct dhcp_lease *lease; - if (daemon->port != 0 && dns_dirty) + if (daemon->port != 0 && (dns_dirty || force)) { -#ifdef HAVE_DHCP6 - struct subnet_map *subnets = build_subnet_map(); -#endif - cache_unhash_dhcp(); for (lease = leases; lease; lease = lease->next) @@ -409,41 +421,15 @@ void lease_update_dns(void) prot = AF_INET6; else if (lease->hostname || lease->fqdn) { - struct subnet_map *map; - for (map = subnets; map; map = map->next) - if (lease->last_interface == map->iface) + struct slaac_address *slaac; + + for (slaac = lease->slaac_address; slaac; slaac = slaac->next) + if (slaac->backoff == 0) { - struct in6_addr addr = map->subnet; - if (lease->hwaddr_len == 6 && - (lease->hwaddr_type == ARPHRD_ETHER || lease->hwaddr_type == ARPHRD_IEEE802)) - { - /* convert MAC address to EUI-64 */ - memcpy(&addr.s6_addr[8], lease->hwaddr, 3); - memcpy(&addr.s6_addr[13], &lease->hwaddr[3], 3); - addr.s6_addr[11] = 0xff; - addr.s6_addr[12] = 0xfe; - } -#if defined(ARPHRD_EUI64) - else if (lease->hwaddr_len == 8 && - lease->hwaddr_type == ARPHRD_EUI64) - memcpy(&addr.s6_addr[8], lease->hwaddr, 8); -#endif -#if defined(ARPHRD_IEEE1394) && defined(ARPHRD_EUI64) - else if (lease->clid_len == 9 && - lease->clid[0] == ARPHRD_EUI64 && - lease->hwaddr_type == ARPHRD_IEEE1394) - /* firewire has EUI-64 identifier as clid */ - memcpy(&addr.s6_addr[8], &lease->clid[1], 8); -#endif - else - continue; - - addr.s6_addr[8] ^= 0x02; - if (lease->fqdn) - cache_add_dhcp_entry(lease->fqdn, AF_INET6, (struct all_addr *)&addr, lease->expires); + cache_add_dhcp_entry(lease->fqdn, AF_INET6, (struct all_addr *)&slaac->addr, lease->expires); if (!option_bool(OPT_DHCP_FQDN) && lease->hostname) - cache_add_dhcp_entry(lease->hostname, AF_INET6, (struct all_addr *)&addr, lease->expires); + cache_add_dhcp_entry(lease->hostname, AF_INET6, (struct all_addr *)&slaac->addr, lease->expires); } } #endif @@ -713,8 +699,13 @@ void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now) } void lease_set_hwaddr(struct dhcp_lease *lease, unsigned char *hwaddr, - unsigned char *clid, int hw_len, int hw_type, int clid_len) + unsigned char *clid, int hw_len, int hw_type, int clid_len, time_t now) { +#ifdef HAVE_DHCP6 + int change = 0; + lease->flags |= LEASE_HAVE_HWADDR; +#endif + if (hw_len != lease->hwaddr_len || hw_type != lease->hwaddr_type || (hw_len != 0 && memcmp(lease->hwaddr, hwaddr, hw_len) != 0)) @@ -725,6 +716,9 @@ void lease_set_hwaddr(struct dhcp_lease *lease, unsigned char *hwaddr, lease->hwaddr_type = hw_type; lease->flags |= LEASE_CHANGED; file_dirty = 1; /* run script on change */ +#ifdef HAVE_DHCP6 + change = 1; +#endif } /* only update clid when one is available, stops packets @@ -742,17 +736,27 @@ void lease_set_hwaddr(struct dhcp_lease *lease, unsigned char *hwaddr, free(lease->clid); if (!(lease->clid = whine_malloc(clid_len))) return; +#ifdef HAVE_DHCP6 + change = 1; +#endif } else if (memcmp(lease->clid, clid, clid_len) != 0) { lease->flags |= LEASE_AUX_CHANGED; file_dirty = 1; +#ifdef HAVE_DHCP6 + change = 1; +#endif } - + lease->clid_len = clid_len; memcpy(lease->clid, clid, clid_len); } - + +#ifdef HAVE_DHCP6 + if (change) + slaac_add_addrs(lease, now); +#endif } static void kill_name(struct dhcp_lease *lease) @@ -870,13 +874,17 @@ void lease_set_hostname(struct dhcp_lease *lease, char *name, int auth, char *do lease->flags |= LEASE_CHANGED; /* run script on change */ } -void lease_set_interface(struct dhcp_lease *lease, int interface) +void lease_set_interface(struct dhcp_lease *lease, int interface, time_t now) { if (lease->last_interface == interface) return; lease->last_interface = interface; lease->flags |= LEASE_CHANGED; + +#ifdef HAVE_DHCP6 + slaac_add_addrs(lease, now); +#endif } void rerun_scripts(void) @@ -919,6 +927,14 @@ int do_script_run(time_t now) } else { +#ifdef HAVE_DHCP6 + struct slaac_address *slaac, *tmp; + for (slaac = lease->slaac_address; slaac; slaac = tmp) + { + tmp = slaac->next; + free(slaac); + } +#endif kill_name(lease); #ifdef HAVE_SCRIPT queue_script(ACTION_DEL, lease, lease->old_hostname, now); diff --git a/src/netlink.c b/src/netlink.c index 993c1f9..d20654d 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -345,10 +345,11 @@ static void nl_routechange(struct nlmsghdr *h) /* force RAs to sync new network and pick up new interfaces. */ if (daemon->ra_contexts) { - ra_start_unsolicted(dnsmasq_time()); + schedule_subnet_map(); + ra_start_unsolicted(dnsmasq_time(), NULL); /* cause lease_update_file to run after we return, in case we were called from iface_enumerate and can't re-enter it now */ - send_alarm(); + send_alarm(0, 0); } #endif diff --git a/src/radv-protocol.h b/src/radv-protocol.h index 8f3244f..8e83169 100644 --- a/src/radv-protocol.h +++ b/src/radv-protocol.h @@ -17,6 +17,13 @@ #define ALL_HOSTS "FF02::1" #define ALL_ROUTERS "FF02::2" +struct ping_packet { + u8 type, code; + u16 checksum; + u16 identifier; + u16 sequence_no; +}; + struct ra_packet { u8 type, code; u16 checksum; @@ -32,9 +39,6 @@ struct prefix_opt { struct in6_addr prefix; }; -#define ICMP6_ROUTER_SOLICIT 133 -#define ICMP6_ROUTER_ADVERT 134 - #define ICMP6_OPT_SOURCE_MAC 1 #define ICMP6_OPT_PREFIX 3 #define ICMP6_OPT_MTU 5 diff --git a/src/radv.c b/src/radv.c index c8e2f01..854f35b 100644 --- a/src/radv.c +++ b/src/radv.c @@ -55,13 +55,20 @@ void ra_init(time_t now) #endif int val = 255; /* radvd uses this value */ socklen_t len = sizeof(int); - + struct dhcp_context *context; + /* ensure this is around even if we're not doing DHCPv6 */ expand_buf(&daemon->outpacket, sizeof(struct dhcp_packet)); - + + /* See if we're guessing SLAAC addresses, if so we need to recieve ping replies */ + for (context = daemon->ra_contexts; context; context = context->next) + if ((context->flags & CONTEXT_RA_NAME)) + break; + ICMP6_FILTER_SETBLOCKALL(&filter); ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter); - ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); + if (context) + ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filter); if ((fd = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) == -1 || getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hop_limit, &len) || @@ -77,21 +84,21 @@ void ra_init(time_t now) daemon->icmp6fd = fd; - ra_start_unsolicted(now); + ra_start_unsolicted(now, NULL); } -void ra_start_unsolicted(time_t now) +void ra_start_unsolicted(time_t now, struct dhcp_context *context) { - struct dhcp_context *context; - - /* init timers so that we do ra's for all soon. some ra_times will end up zeroed + /* init timers so that we do ra's for some/all soon. some ra_times will end up zeroed if it's not appropriate to advertise those contexts. This gets re-called on a netlink route-change to re-do the advertisement and pick up new interfaces */ - /* range 0 - 5 */ - for (context = daemon->ra_contexts; context; context = context->next) - context->ra_time = now + (rand16()/13000); + if (context) + context->ra_time = now; + else + for (context = daemon->ra_contexts; context; context = context->next) + context->ra_time = now + (rand16()/13000); /* range 0 - 5 */ /* re-do frequently for a minute or so, in case the first gets lost. */ ra_short_period_start = now; @@ -158,7 +165,19 @@ void icmp6_packet(void) p = (unsigned char *)daemon->outpacket.iov_base; - if (p[0] != ICMP6_ROUTER_SOLICIT || p[1] != 0) + if (p[1] != 0) + return; + + if (p[0] == ICMP6_ECHO_REPLY) + { + /* We may be doing RA but not DHCPv4, in which case the lease + database may not exist and we have nothing to do anyway */ + if (daemon->dhcp) + lease_ping_reply(&from.sin6_addr, p, interface); + return; + } + + if (p[0] != ND_ROUTER_SOLICIT) return; /* look for link-layer address option for logging */ @@ -184,7 +203,7 @@ static void send_ra(int iface, char *iface_name, struct in6_addr *dest) save_counter(0); ra = expand(sizeof(struct ra_packet)); - ra->type = ICMP6_ROUTER_ADVERT; + ra->type = ND_ROUTER_ADVERT; ra->code = 0; ra->hop_limit = hop_limit; ra->flags = 0; @@ -238,7 +257,7 @@ static void send_ra(int iface, char *iface_name, struct in6_addr *dest) addr.sin6_port = htons(IPPROTO_ICMPV6); if (dest) { - memcpy(&addr.sin6_addr, dest, sizeof(struct in6_addr)); + addr.sin6_addr = *dest; if (IN6_IS_ADDR_LINKLOCAL(dest) || IN6_IS_ADDR_MC_LINKLOCAL(dest)) addr.sin6_scope_id = iface; @@ -406,7 +425,7 @@ static int iface_search(struct in6_addr *local, int prefix, if (prefix == context->prefix && is_same_net6(local, &context->start6, prefix) && is_same_net6(local, &context->end6, prefix)) - if (context->ra_time != 0 && difftime(context->ra_time, param->now) < 0.0) + if (context->ra_time != 0 && difftime(context->ra_time, param->now) <= 0.0) { /* found an interface that's overdue for RA determine new timeout value and zap other contexts on the same interface @@ -426,73 +445,5 @@ static int iface_search(struct in6_addr *local, int prefix, return 1; /* keep searching */ } -static int add_subnet(struct in6_addr *local, int prefix, - int scope, int if_index, int dad, void *vparam) -{ - struct dhcp_context *context; - struct subnet_map **subnets = vparam; - struct subnet_map *map; - - (void)scope; - (void)dad; - - for (context = daemon->ra_contexts; context; context = context->next) - if ((context->flags & CONTEXT_RA_NAME) && - prefix == context->prefix && - is_same_net6(local, &context->start6, prefix) && - is_same_net6(local, &context->end6, prefix)) - { - for (map = *subnets; map; map = map->next) - if (map->iface == 0 || - (map->iface == if_index && is_same_net6(local, &map->subnet, prefix))) - break; - - /* It's there already */ - if (map && map->iface != 0) - continue; - - if (!map && (map = whine_malloc(sizeof(struct subnet_map)))) - { - map->next = *subnets; - *subnets = map; - } - - if (map) - { - map->iface = if_index; - map->subnet = *local; - } - } - - return 1; -} - -/* Build a map from ra-names subnets to corresponding interfaces. This - is used to go from DHCPv4 leases to SLAAC addresses, - interface->IPv6-subnet, IPv6-subnet + MAC address -> SLAAC. -*/ -struct subnet_map *build_subnet_map(void) -{ - struct subnet_map *map; - struct dhcp_context *context; - static struct subnet_map *subnets = NULL; - - for (context = daemon->ra_contexts; context; context = context->next) - if ((context->flags & CONTEXT_RA_NAME)) - break; - - /* no ra-names, no need to go further. */ - if (!context) - return NULL; - - /* mark unused */ - for (map = subnets; map; map = map->next) - map->iface = 0; - - if (iface_enumerate(AF_INET6, &subnets, add_subnet)) - return subnets; - - return NULL; -} #endif diff --git a/src/rfc2131.c b/src/rfc2131.c index 39edf78..83dc4c1 100644 --- a/src/rfc2131.c +++ b/src/rfc2131.c @@ -483,14 +483,14 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, { logaddr = &mess->yiaddr; - lease_set_hwaddr(lease, mess->chaddr, NULL, mess->hlen, mess->htype, 0); + lease_set_hwaddr(lease, mess->chaddr, NULL, mess->hlen, mess->htype, 0, now); if (hostname) lease_set_hostname(lease, hostname, 1, get_domain(lease->addr), domain); /* infinite lease unless nailed in dhcp-host line. */ lease_set_expires(lease, have_config(config, CONFIG_TIME) ? config->lease_time : 0xffffffff, now); - lease_set_interface(lease, int_index); + lease_set_interface(lease, int_index, now); clear_packet(mess, end); do_options(context, mess, end, NULL, hostname, get_domain(mess->yiaddr), @@ -1276,7 +1276,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, } 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); + lease_set_hwaddr(lease, mess->chaddr, clid, mess->hlen, mess->htype, clid_len, now); /* if all the netids in the ignore_name list are present, ignore client-supplied name */ if (!hostname_auth) @@ -1310,7 +1310,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, lease_set_hostname(lease, hostname, hostname_auth, get_domain(lease->addr), domain); lease_set_expires(lease, time, now); - lease_set_interface(lease, int_index); + lease_set_interface(lease, int_index, now); if (override.s_addr != 0) lease->override = override; @@ -1387,7 +1387,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, else time = (unsigned int)difftime(lease->expires, now); option_put(mess, end, OPTION_LEASE_TIME, 4, time); - lease_set_interface(lease, int_index); + lease_set_interface(lease, int_index, now); } do_options(context, mess, end, req_options, hostname, get_domain(mess->ciaddr), diff --git a/src/rfc3315.c b/src/rfc3315.c index 353a03e..c55d90c 100644 --- a/src/rfc3315.c +++ b/src/rfc3315.c @@ -637,8 +637,8 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh if (lease) { lease_set_expires(lease, lease_time, now); - lease_set_hwaddr(lease, NULL, clid, 0, iaid, clid_len); - lease_set_interface(lease, interface); + lease_set_hwaddr(lease, NULL, clid, 0, iaid, clid_len, now); + lease_set_interface(lease, interface, now); if (hostname && ia_type == OPTION6_IA_NA) { char *addr_domain = get_domain6(addrp); diff --git a/src/slaac.c b/src/slaac.c new file mode 100644 index 0000000..fbb21a1 --- /dev/null +++ b/src/slaac.c @@ -0,0 +1,243 @@ +/* dnsmasq is Copyright (c) 2000-2012 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "dnsmasq.h" + +#ifdef HAVE_DHCP6 + +#include + +static int map_rebuild = 0; +static int ping_id = 0; + +void slaac_add_addrs(struct dhcp_lease *lease, time_t now) +{ + struct slaac_address *slaac, *old, **up; + struct dhcp_context *context; + + if (!(lease->flags & LEASE_HAVE_HWADDR) || + lease->last_interface == 0 || + !lease->hostname) + return ; + + old = lease->slaac_address; + lease->slaac_address = NULL; + + for (context = daemon->ra_contexts; context; context = context->next) + if ((context->flags & CONTEXT_RA_NAME) && lease->last_interface == context->if_index) + { + struct in6_addr addr = context->start6; + if (lease->hwaddr_len == 6 && + (lease->hwaddr_type == ARPHRD_ETHER || lease->hwaddr_type == ARPHRD_IEEE802)) + { + /* convert MAC address to EUI-64 */ + memcpy(&addr.s6_addr[8], lease->hwaddr, 3); + memcpy(&addr.s6_addr[13], &lease->hwaddr[3], 3); + addr.s6_addr[11] = 0xff; + addr.s6_addr[12] = 0xfe; + } +#if defined(ARPHRD_EUI64) + else if (lease->hwaddr_len == 8 && + lease->hwaddr_type == ARPHRD_EUI64) + memcpy(&addr.s6_addr[8], lease->hwaddr, 8); +#endif +#if defined(ARPHRD_IEEE1394) && defined(ARPHRD_EUI64) + else if (lease->clid_len == 9 && + lease->clid[0] == ARPHRD_EUI64 && + lease->hwaddr_type == ARPHRD_IEEE1394) + /* firewire has EUI-64 identifier as clid */ + memcpy(&addr.s6_addr[8], &lease->clid[1], 8); +#endif + else + continue; + + addr.s6_addr[8] ^= 0x02; + + /* check if we already have this one */ + for (up = &old, slaac = old; slaac; slaac = slaac->next) + { + if (IN6_ARE_ADDR_EQUAL(&addr, &slaac->addr)) + { + *up = slaac->next; + break; + } + up = &slaac->next; + } + + /* No, make new one */ + if (!slaac && (slaac = whine_malloc(sizeof(struct slaac_address)))) + { + slaac->ping_time = now; + slaac->backoff = 1; + slaac->addr = addr; + slaac->local = context->local6; + /* Do RA's to prod it */ + ra_start_unsolicted(now, context); + } + + if (slaac) + { + slaac->next = lease->slaac_address; + lease->slaac_address = slaac; + } + } + + /* Free any no reused */ + for (; old; old = slaac) + { + slaac = old->next; + free(old); + } +} + + +time_t periodic_slaac(time_t now, struct dhcp_lease *leases) +{ + struct dhcp_context *context; + struct dhcp_lease *lease; + struct slaac_address *slaac; + time_t next_event = 0; + + for (context = daemon->ra_contexts; context; context = context->next) + if ((context->flags & CONTEXT_RA_NAME)) + break; + + /* nothing configured */ + if (!context) + return 0; + + while (ping_id == 0) + ping_id = rand16(); + + if (map_rebuild) + { + map_rebuild = 0; + build_subnet_map(); + } + + for (lease = leases; lease; lease = lease->next) + for (slaac = lease->slaac_address; slaac; slaac = slaac->next) + { + /* confirmed? */ + if (slaac->backoff == 0) + continue; + + if (difftime(slaac->ping_time, now) <= 0.0) + { + struct ping_packet *ping; + struct sockaddr_in6 addr; + + save_counter(0); + ping = expand(sizeof(struct ping_packet)); + ping->type = ICMP6_ECHO_REQUEST; + ping->code = 0; + ping->identifier = ping_id; + ping->sequence_no = slaac->backoff; + + memset(&addr, 0, sizeof(addr)); +#ifdef HAVE_SOCKADDR_SA_LEN + addr.sin6_len = sizeof(struct sockaddr_in6); +#endif + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(IPPROTO_ICMPV6); + addr.sin6_addr = slaac->addr; + + send_from(daemon->icmp6fd, 0, daemon->outpacket.iov_base, save_counter(0), + (union mysockaddr *)&addr, (struct all_addr *)&slaac->local, lease->last_interface); + + slaac->ping_time += (1 << (slaac->backoff - 1)) + (rand16()/21785); /* 0 - 3 */ + if (slaac->backoff > 4) + slaac->ping_time += rand16()/4000; /* 0 - 15 */ + slaac->backoff++; + } + + if (next_event == 0 || difftime(next_event, slaac->ping_time) >= 0.0) + next_event = slaac->ping_time; + } + + return next_event; +} + + +void slaac_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface, struct dhcp_lease *leases) +{ + struct dhcp_lease *lease; + struct slaac_address *slaac; + struct ping_packet *ping = (struct ping_packet *)packet; + int gotone = 0; + + if (ping->identifier == ping_id) + for (lease = leases; lease; lease = lease->next) + for (slaac = lease->slaac_address; slaac; slaac = slaac->next) + if (slaac->backoff != 0 && IN6_ARE_ADDR_EQUAL(sender, &slaac->addr)) + { + slaac->backoff = 0; + gotone = 1; + inet_ntop(AF_INET6, sender, daemon->addrbuff, ADDRSTRLEN); + my_syslog(MS_DHCP | LOG_INFO, "SLAAC-CONFIRM(%s) %s %s", interface, daemon->addrbuff, lease->hostname); + } + + lease_update_dns(gotone); +} + +/* Build a map from ra-names subnets to corresponding interfaces. This + is used to go from DHCPv4 leases to SLAAC addresses, + interface->IPv6-subnet, IPv6-subnet + MAC address -> SLAAC. +*/ +static int add_subnet(struct in6_addr *local, int prefix, + int scope, int if_index, int dad, void *vparam) +{ + struct dhcp_context *context; + + (void)scope; + (void)dad; + (void)vparam; + + for (context = daemon->ra_contexts; context; context = context->next) + if ((context->flags & CONTEXT_RA_NAME) && + prefix == context->prefix && + is_same_net6(local, &context->start6, prefix) && + is_same_net6(local, &context->end6, prefix)) + { + context->if_index = if_index; + context->local6 = *local; + } + + return 1; +} + +void build_subnet_map(void) +{ + struct dhcp_context *context; + int ok = 0; + + for (context = daemon->ra_contexts; context; context = context->next) + { + context->if_index = 0; + if ((context->flags & CONTEXT_RA_NAME)) + ok = 1; + } + + /* ra-names configured */ + if (ok) + iface_enumerate(AF_INET6, NULL, add_subnet); +} + +void schedule_subnet_map(void) +{ + map_rebuild = 1; +} +#endif