From dc8a1b1bcf1a4420063c3a708580fcd3277130c5 Mon Sep 17 00:00:00 2001 From: Lung-Pin Chang Date: Wed, 2 Jul 2014 10:48:05 +0800 Subject: Set interface with longest prefix in DHCP & DHCPv6 lease - With nested prefixes reside on different interfaces of single host (e.g., in 6to4, 2002::/16 on WAN and 2002::::/64 on LAN), current matching mechanism might return the interface with shorter prefix length instead of the longer one, if it appears later in the netlink message. Signed-off-by: Lung-Pin Chang --- src/dnsmasq.h | 4 ++++ src/lease.c | 40 +++++++++++++++++++++++++++++++--------- src/util.c | 12 ++++++++++++ 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index dfa9773..2c682a3 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -640,6 +640,8 @@ struct dhcp_lease { unsigned char *extradata; unsigned int extradata_len, extradata_size; int last_interface; + int new_interface; /* save possible originated interface */ + int new_prefixlen; /* and its prefix length */ #ifdef HAVE_DHCP6 struct in6_addr addr6; int iaid; @@ -1131,6 +1133,7 @@ int sa_len(union mysockaddr *addr); int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2); int hostname_isequal(const char *a, const char *b); time_t dnsmasq_time(void); +int netmask_length(struct in_addr mask); int is_same_net(struct in_addr a, struct in_addr b, struct in_addr mask); #ifdef HAVE_IPV6 int is_same_net6(struct in6_addr *a, struct in6_addr *b, int prefixlen); @@ -1242,6 +1245,7 @@ char *host_from_dns(struct in_addr addr); #ifdef HAVE_DHCP void lease_update_file(time_t now); void lease_update_dns(int force); +void lease_update_interface(time_t now); void lease_init(time_t now); struct dhcp_lease *lease4_allocate(struct in_addr addr); #ifdef HAVE_DHCP6 diff --git a/src/lease.c b/src/lease.c index 5d1eefc..9479afc 100644 --- a/src/lease.c +++ b/src/lease.c @@ -352,16 +352,21 @@ static int find_interface_v4(struct in_addr local, int if_index, char *label, struct in_addr netmask, struct in_addr broadcast, void *vparam) { struct dhcp_lease *lease; - + int prefix; + (void) label; (void) broadcast; (void) vparam; for (lease = leases; lease; lease = lease->next) - if (!(lease->flags & (LEASE_TA | LEASE_NA))) - if (is_same_net(local, lease->addr, netmask)) - lease_set_interface(lease, if_index, *((time_t *)vparam)); - + if (!(lease->flags & (LEASE_TA | LEASE_NA))) { + prefix = netmask_length(netmask); + if (is_same_net(local, lease->addr, netmask) && prefix > lease->new_prefixlen) { + lease->new_interface = if_index; + lease->new_prefixlen = prefix; + } + } + return 1; } @@ -371,17 +376,23 @@ static int find_interface_v6(struct in6_addr *local, int prefix, int preferred, int valid, void *vparam) { struct dhcp_lease *lease; - + (void)scope; (void)flags; (void)preferred; (void)valid; + (void)vparam; for (lease = leases; lease; lease = lease->next) if ((lease->flags & (LEASE_TA | LEASE_NA))) - if (is_same_net6(local, &lease->addr6, prefix)) - lease_set_interface(lease, if_index, *((time_t *)vparam)); - + if (is_same_net6(local, &lease->addr6, prefix) && prefix > lease->new_prefixlen) { + /* save prefix length for comparison, as we might get shorter matching + * prefix in upcoming netlink GETADDR responses + * */ + lease->new_interface = if_index; + lease->new_prefixlen = prefix; + } + return 1; } @@ -418,6 +429,7 @@ void lease_find_interfaces(time_t now) #ifdef HAVE_DHCP6 iface_enumerate(AF_INET6, &now, find_interface_v6); #endif + lease_update_interface(now); } #ifdef HAVE_DHCP6 @@ -492,6 +504,16 @@ void lease_update_dns(int force) } } +void lease_update_interface(time_t now) +{ + struct dhcp_lease *lease; + + for (lease = leases; lease; lease = lease->next) + if (lease->new_interface > 0) { + lease_set_interface(lease, lease->new_interface, now); + } +} + void lease_prune(struct dhcp_lease *target, time_t now) { struct dhcp_lease *lease, *tmp, **up; diff --git a/src/util.c b/src/util.c index a503082..c1b0c50 100644 --- a/src/util.c +++ b/src/util.c @@ -319,6 +319,18 @@ time_t dnsmasq_time(void) #endif } +int netmask_length(struct in_addr mask) +{ + int zero_count = 0; + + while (0x0 == (mask.s_addr & 0x1)) { + mask.s_addr >>= 1; + ++zero_count; + } + + return 32 - zero_count; +} + int is_same_net(struct in_addr a, struct in_addr b, struct in_addr mask) { return (a.s_addr & mask.s_addr) == (b.s_addr & mask.s_addr); -- cgit v1.2.1