diff --git a/CHANGELOG b/CHANGELOG index 48b6070..39749d5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -37,6 +37,9 @@ version 2.67 and do RA but _not_ DHCPv6. Thanks to Trever Adams for the bug report. + Generalise --interface-name to cope with IPv6 addresses + and multiple addresses per interface per address family. + version 2.66 Add the ability to act as an authoritative DNS diff --git a/src/auth.c b/src/auth.c index bd009ae..2a3f323 100644 --- a/src/auth.c +++ b/src/auth.c @@ -152,32 +152,55 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n auth = 0; continue; } - - if (flag == F_IPV4) - { - for (intr = daemon->int_names; intr; intr = intr->next) - { - if (addr.addr.addr4.s_addr == get_ifaddr(intr->intr).s_addr) - break; - else - while (intr->next && strcmp(intr->intr, intr->next->intr) == 0) - intr = intr->next; - } + + intr = NULL; - if (intr) - { - if (in_zone(zone, intr->name, NULL)) - { - found = 1; - log_query(F_IPV4 | F_REVERSE | F_CONFIG, intr->name, &addr, NULL); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - daemon->auth_ttl, NULL, - T_PTR, C_IN, "d", intr->name)) - anscount++; - } + if (flag == F_IPV4) + for (intr = daemon->int_names; intr; intr = intr->next) + { + struct addrlist *addrlist; + + for (addrlist = intr->addr4; addrlist; addrlist = addrlist->next) + if (addr.addr.addr4.s_addr == addrlist->addr.addr.addr4.s_addr) + break; + + if (addrlist) + break; + else + while (intr->next && strcmp(intr->intr, intr->next->intr) == 0) + intr = intr->next; + } +#ifdef HAVE_IPV6 + else if (flag == F_IPV6) + for (intr = daemon->int_names; intr; intr = intr->next) + { + struct addrlist *addrlist; + + for (addrlist = intr->addr6; addrlist; addrlist = addrlist->next) + if (IN6_ARE_ADDR_EQUAL(&addr.addr.addr6, &addrlist->addr.addr.addr6)) + break; + + if (addrlist) + break; + else + while (intr->next && strcmp(intr->intr, intr->next->intr) == 0) + intr = intr->next; + } +#endif + + if (intr) + { + if (in_zone(zone, intr->name, NULL)) + { + found = 1; + log_query(flag| F_REVERSE | F_CONFIG, intr->name, &addr, NULL); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->auth_ttl, NULL, + T_PTR, C_IN, "d", intr->name)) + anscount++; } } - + if ((crecp = cache_find_by_addr(NULL, &addr, now, flag))) do { strcpy(name, cache_get_name(crecp)); @@ -321,20 +344,37 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n anscount++; } } - - + + if (qtype == T_A) + flag = F_IPV4; + +#ifdef HAVE_IPV6 + if (qtype == T_AAAA) + flag = F_IPV6; +#endif + for (intr = daemon->int_names; intr; intr = intr->next) if (hostname_isequal(name, intr->name)) { + struct addrlist *addrlist; + + addrlist = intr->addr4; +#ifdef HAVE_IPV6 + if (qtype == T_AAAA) + addrlist = intr->addr6; +#endif nxdomain = 0; - if (qtype == T_A && (addr.addr.addr4 = get_ifaddr(intr->intr)).s_addr != (in_addr_t) -1) - { - found = 1; - log_query(F_FORWARD | F_CONFIG | F_IPV4, name, &addr, NULL); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - daemon->auth_ttl, NULL, T_A, C_IN, "4", &addr)) - anscount++; - } + + for (; addrlist; addrlist = addrlist->next) + if (filter_constructed_dhcp(zone, flag, &addrlist->addr)) + { + found = 1; + log_query(F_FORWARD | F_CONFIG | flag, name, &addrlist->addr, NULL); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->auth_ttl, NULL, qtype, C_IN, + qtype == T_A ? "4" : "6", &addrlist->addr)) + anscount++; + } } for (a = daemon->cnames; a; a = a->next) @@ -356,14 +396,6 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n goto cname_restart; } - if (qtype == T_A) - flag = F_IPV4; - -#ifdef HAVE_IPV6 - if (qtype == T_AAAA) - flag = F_IPV6; -#endif - if (!cut) { nxdomain = 0; @@ -634,20 +666,32 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n } for (intr = daemon->int_names; intr; intr = intr->next) - if (in_zone(zone, intr->name, &cut) && (addr.addr.addr4 = get_ifaddr(intr->intr)).s_addr != (in_addr_t) -1) + if (in_zone(zone, intr->name, &cut)) { + struct addrlist *addrlist; + if (cut) *cut = 0; - if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, - daemon->auth_ttl, NULL, T_A, C_IN, "4", cut ? intr->name : NULL, &addr)) - anscount++; + for (addrlist = intr->addr4; addrlist; addrlist = addrlist->next) + if (filter_constructed_dhcp(zone, F_IPV4, &addrlist->addr) && + add_resource_record(header, limit, &trunc, -axfroffset, &ansp, + daemon->auth_ttl, NULL, T_A, C_IN, "4", cut ? intr->name : NULL, &addrlist->addr)) + anscount++; + +#ifdef HAVE_IPV6 + for (addrlist = intr->addr6; addrlist; addrlist = addrlist->next) + if (filter_constructed_dhcp(zone, F_IPV6, &addrlist->addr) && + add_resource_record(header, limit, &trunc, -axfroffset, &ansp, + daemon->auth_ttl, NULL, T_AAAA, C_IN, "6", cut ? intr->name : NULL, &addrlist->addr)) + anscount++; +#endif /* restore config data */ if (cut) *cut = '.'; } - + for (a = daemon->cnames; a; a = a->next) if (in_zone(zone, a->alias, &cut)) { diff --git a/src/dnsmasq.c b/src/dnsmasq.c index b0f984d..62e65a9 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -225,7 +225,7 @@ int main (int argc, char **argv) die(_("cannot set --bind-interfaces and --bind-dynamic"), NULL, EC_BADCONF); #endif - if (!enumerate_interfaces()) + if (!enumerate_interfaces(1) || !enumerate_interfaces(0)) die(_("failed to find list of interfaces: %s"), NULL, EC_MISC); if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND)) @@ -820,12 +820,15 @@ int main (int argc, char **argv) now = dnsmasq_time(); check_log_writer(&wset); - + + /* prime. */ + enumerate_interfaces(1); + /* Check the interfaces to see if any have exited DAD state and if so, bind the address. */ if (is_dad_listeners()) { - enumerate_interfaces(); + enumerate_interfaces(0); /* NB, is_dad_listeners() == 1 --> we're binding interfaces */ create_bound_listeners(0); } @@ -1369,7 +1372,7 @@ static void check_dns_listeners(fd_set *set, time_t now) /* In full wildcard mode, need to refresh interface list. This happens automagically in CLEVERBIND */ if (!option_bool(OPT_CLEVERBIND)) - enumerate_interfaces(); + enumerate_interfaces(0); /* if we can find the arrival interface, check it's one that's allowed */ if ((if_index = tcp_interface(confd, tcp_addr.sa.sa_family)) != 0 && diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 8866dd8..b7ca20e 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -307,6 +307,13 @@ struct host_record { struct interface_name { char *name; /* domain name */ char *intr; /* interface name */ + struct addrlist { + struct all_addr addr; + struct addrlist *next; + } *addr4; +#ifdef HAVE_IPV6 + struct addrlist *addr6; +#endif struct interface_name *next; }; @@ -1024,7 +1031,7 @@ int random_sock(int family); void pre_allocate_sfds(void); int reload_servers(char *fname); void check_servers(void); -int enumerate_interfaces(); +int enumerate_interfaces(int reset); void create_wildcard_listeners(void); void create_bound_listeners(int die); int is_dad_listeners(void); @@ -1033,7 +1040,6 @@ int loopback_exception(int fd, int family, struct all_addr *addr, char *name); int label_exception(int index, int family, struct all_addr *addr); int fix_fd(int fd); int tcp_interface(int fd, int af); -struct in_addr get_ifaddr(char *intr); #ifdef HAVE_IPV6 int set_ipv6pktinfo(int fd); #endif diff --git a/src/forward.c b/src/forward.c index 28fe9eb..33a68a0 100644 --- a/src/forward.c +++ b/src/forward.c @@ -788,7 +788,7 @@ void receive_query(struct listener *listen, time_t now) if (!iface_check(listen->family, &dst_addr, ifr.ifr_name, &auth_dns)) { if (!option_bool(OPT_CLEVERBIND)) - enumerate_interfaces(); + enumerate_interfaces(0); if (!loopback_exception(listen->fd, listen->family, &dst_addr, ifr.ifr_name) && !label_exception(if_index, listen->family, &dst_addr)) return; @@ -808,7 +808,7 @@ void receive_query(struct listener *listen, time_t now) /* interface may be new */ if (!iface && !option_bool(OPT_CLEVERBIND)) - enumerate_interfaces(); + enumerate_interfaces(0); for (iface = daemon->interfaces; iface; iface = iface->next) if (iface->addr.sa.sa_family == AF_INET && diff --git a/src/netlink.c b/src/netlink.c index 78d0926..5e4d924 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -193,7 +193,10 @@ int iface_enumerate(int family, void *parm, int (*callback)()) { /* May be multicast arriving async */ if (nl_async(h)) - newaddr = 1; + { + newaddr = 1; + enumerate_interfaces(1); /* reset */ + } } else if (h->nlmsg_type == NLMSG_DONE) { @@ -397,7 +400,7 @@ static int nl_async(struct nlmsghdr *h) static void nl_newaddress(time_t now) { if (option_bool(OPT_CLEVERBIND) || daemon->doing_dhcp6 || daemon->doing_ra) - enumerate_interfaces(); + enumerate_interfaces(0); if (option_bool(OPT_CLEVERBIND)) create_bound_listeners(0); diff --git a/src/network.c b/src/network.c index 473e85f..575af32 100644 --- a/src/network.c +++ b/src/network.c @@ -99,6 +99,8 @@ int indextoname(int fd, int index, char *name) int indextoname(int fd, int index, char *name) { + (void)fd; + if (index == 0 || !if_indextoname(index, name)) return 0; @@ -224,11 +226,16 @@ int label_exception(int index, int family, struct all_addr *addr) return 0; } -static int iface_allowed(struct irec **irecp, int if_index, char *label, +struct iface_param { + struct addrlist *spare; + int fd; +}; + +static int iface_allowed(struct iface_param *param, int if_index, char *label, union mysockaddr *addr, struct in_addr netmask, int dad) { struct irec *iface; - int fd, mtu = 0, loopback; + int mtu = 0, loopback; struct ifreq ifr; int tftp_ok = !!option_bool(OPT_TFTP); int dhcp_ok = 1; @@ -237,39 +244,72 @@ static int iface_allowed(struct irec **irecp, int if_index, char *label, struct iname *tmp; #endif - /* check whether the interface IP has been added already - we call this routine multiple times. */ - for (iface = *irecp; iface; iface = iface->next) - if (sockaddr_isequal(&iface->addr, addr)) - { - iface->dad = dad; - return 1; - } - - if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1 || - !indextoname(fd, if_index, ifr.ifr_name) || - ioctl(fd, SIOCGIFFLAGS, &ifr) == -1) - { - if (fd != -1) - { - int errsave = errno; - close(fd); - errno = errsave; - } - return 0; - } + if (!indextoname(param->fd, if_index, ifr.ifr_name) || + ioctl(param->fd, SIOCGIFFLAGS, &ifr) == -1) + return 0; loopback = ifr.ifr_flags & IFF_LOOPBACK; if (loopback) dhcp_ok = 0; - if (ioctl(fd, SIOCGIFMTU, &ifr) != -1) + if (ioctl(param->fd, SIOCGIFMTU, &ifr) != -1) mtu = ifr.ifr_mtu; - close(fd); + if (!label) + label = ifr.ifr_name; + - /* If we are restricting the set of interfaces to use, make + /* Update addresses from interface_names. These are a set independent + of the set we're listening on. */ +#ifdef HAVE_IPV6 + if (addr->sa.sa_family != AF_INET6 || !IN6_IS_ADDR_LINKLOCAL(&addr->in6.sin6_addr)) +#endif + { + struct interface_name *int_name; + struct addrlist *al; + + for (int_name = daemon->int_names; int_name; int_name = int_name->next) + if (strncmp(label, int_name->intr, IF_NAMESIZE) == 0) + { + if (param->spare) + { + al = param->spare; + param->spare = al->next; + } + else + al = whine_malloc(sizeof(struct addrlist)); + + if (al) + { + if (addr->sa.sa_family == AF_INET) + { + al->addr.addr.addr4 = addr->in.sin_addr; + al->next = int_name->addr4; + int_name->addr4 = al; + } +#ifdef HAVE_IPV6 + else + { + al->addr.addr.addr6 = addr->in6.sin6_addr; + al->next = int_name->addr6; + int_name->addr6 = al; + } +#endif + } + } + } + + /* check whether the interface IP has been added already + we call this routine multiple times. */ + for (iface = daemon->interfaces; iface; iface = iface->next) + if (sockaddr_isequal(&iface->addr, addr)) + { + iface->dad = dad; + return 1; + } + + /* If we are restricting the set of interfaces to use, make sure that loopback interfaces are in that set. */ if (daemon->if_names && loopback) { @@ -292,9 +332,6 @@ static int iface_allowed(struct irec **irecp, int if_index, char *label, } } - if (!label) - label = ifr.ifr_name; - if (addr->sa.sa_family == AF_INET && !iface_check(AF_INET, (struct all_addr *)&addr->in.sin_addr, label, &auth_dns)) return 1; @@ -336,8 +373,8 @@ static int iface_allowed(struct irec **irecp, int if_index, char *label, if ((iface->name = whine_malloc(strlen(ifr.ifr_name)+1))) { strcpy(iface->name, ifr.ifr_name); - iface->next = *irecp; - *irecp = iface; + iface->next = daemon->interfaces; + daemon->interfaces = iface; return 1; } free(iface); @@ -371,7 +408,7 @@ static int iface_allowed_v6(struct in6_addr *local, int prefix, addr.in6.sin6_port = htons(daemon->port); addr.in6.sin6_scope_id = if_index; - return iface_allowed((struct irec **)vparam, if_index, NULL, &addr, netmask, !!(flags & IFACE_TENTATIVE)); + return iface_allowed((struct iface_param *)vparam, if_index, NULL, &addr, netmask, !!(flags & IFACE_TENTATIVE)); } #endif @@ -389,17 +426,73 @@ static int iface_allowed_v4(struct in_addr local, int if_index, char *label, addr.in.sin_addr = local; addr.in.sin_port = htons(daemon->port); - return iface_allowed((struct irec **)vparam, if_index, label, &addr, netmask, 0); + return iface_allowed((struct iface_param *)vparam, if_index, label, &addr, netmask, 0); } -int enumerate_interfaces(void) +int enumerate_interfaces(int reset) { + static struct addrlist *spare = NULL; + static int done = 0; + struct iface_param param; + int errsave, ret = 1; + struct addrlist *addr, *tmp; + struct interface_name *intname; + + /* DO this max once per select cycle */ + if (reset) + { + done = 0; + return 1; + } + + if (done) + return 1; + + done = 1; + + if ((param.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + return 0; + + /* remove addresses stored against interface_names */ + for (intname = daemon->int_names; intname; intname = intname->next) + { + for (addr = intname->addr4; addr; addr = tmp) + { + tmp = addr->next; + addr->next = spare; + spare = addr; + } + + intname->addr4 = NULL; + #ifdef HAVE_IPV6 - if (!iface_enumerate(AF_INET6, &daemon->interfaces, iface_allowed_v6)) - return 0; + for (addr = intname->addr6; addr; addr = tmp) + { + tmp = addr->next; + addr->next = spare; + spare = addr; + } + + intname->addr6 = NULL; +#endif + } + + param.spare = spare; + +#ifdef HAVE_IPV6 + ret = iface_enumerate(AF_INET6, ¶m, iface_allowed_v6); #endif - return iface_enumerate(AF_INET, &daemon->interfaces, iface_allowed_v4); + if (ret) + ret = iface_enumerate(AF_INET, ¶m, iface_allowed_v4); + + errsave = errno; + close(param.fd); + errno = errsave; + + spare = param.spare; + + return ret; } /* set NONBLOCK bit on fd: See Stevens 16.6 */ @@ -971,7 +1064,7 @@ void check_servers(void) /* interface may be new since startup */ if (!option_bool(OPT_NOWILD)) - enumerate_interfaces(); + enumerate_interfaces(0); for (new = daemon->servers; new; new = tmp) { @@ -1179,27 +1272,7 @@ int reload_servers(char *fname) } -/* Use an IPv4 listener socket for ioctling */ -struct in_addr get_ifaddr(char *intr) -{ - struct listener *l; - struct ifreq ifr; - struct sockaddr_in ret; - - ret.sin_addr.s_addr = -1; - - for (l = daemon->listeners; - l && (l->family != AF_INET || l->fd == -1); - l = l->next); - - strncpy(ifr.ifr_name, intr, IF_NAMESIZE); - ifr.ifr_addr.sa_family = AF_INET; - - if (l && ioctl(l->fd, SIOCGIFADDR, &ifr) != -1) - memcpy(&ret, &ifr.ifr_addr, sizeof(ret)); - - return ret.sin_addr; -} + diff --git a/src/option.c b/src/option.c index d2ab689..eb71102 100644 --- a/src/option.c +++ b/src/option.c @@ -3215,6 +3215,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma new = opt_malloc(sizeof(struct interface_name)); new->next = NULL; + new->addr4 = NULL; +#ifdef HAVE_IPV6 + new->addr6 = NULL; +#endif /* Add to the end of the list, so that first name of an interface is used for PTR lookups. */ for (up = &daemon->int_names; *up; up = &((*up)->next)); diff --git a/src/rfc1035.c b/src/rfc1035.c index 1ba62e1..60ed068 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -1450,19 +1450,42 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (is_arpa == F_IPV4) for (intr = daemon->int_names; intr; intr = intr->next) { - if (addr.addr.addr4.s_addr == get_ifaddr(intr->intr).s_addr) + struct addrlist *addrlist; + + for (addrlist = intr->addr4; addrlist; addrlist = addrlist->next) + if (addr.addr.addr4.s_addr == addrlist->addr.addr.addr4.s_addr) + break; + + if (addrlist) break; else while (intr->next && strcmp(intr->intr, intr->next->intr) == 0) intr = intr->next; } +#ifdef HAVE_IPV6 + else if (is_arpa == F_IPV6) + for (intr = daemon->int_names; intr; intr = intr->next) + { + struct addrlist *addrlist; + + for (addrlist = intr->addr6; addrlist; addrlist = addrlist->next) + if (IN6_ARE_ADDR_EQUAL(&addr.addr.addr6, &addrlist->addr.addr.addr6)) + break; + + if (addrlist) + break; + else + while (intr->next && strcmp(intr->intr, intr->next->intr) == 0) + intr = intr->next; + } +#endif if (intr) { ans = 1; if (!dryrun) { - log_query(F_IPV4 | F_REVERSE | F_CONFIG, intr->name, &addr, NULL); + log_query(is_arpa | F_REVERSE | F_CONFIG, intr->name, &addr, NULL); if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, NULL, T_PTR, C_IN, "d", intr->name)) @@ -1546,7 +1569,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, for (flag = F_IPV4; flag; flag = (flag == F_IPV4) ? F_IPV6 : 0) { unsigned short type = T_A; - + struct interface_name *intr; + if (flag == F_IPV6) #ifdef HAVE_IPV6 type = T_AAAA; @@ -1595,31 +1619,38 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } /* interface name stuff */ - if (qtype == T_A) + + for (intr = daemon->int_names; intr; intr = intr->next) + if (hostname_isequal(name, intr->name)) + break; + + if (intr) { - struct interface_name *intr; + struct addrlist *addrlist; - for (intr = daemon->int_names; intr; intr = intr->next) - if (hostname_isequal(name, intr->name)) - break; + enumerate_interfaces(0); - if (intr) + addrlist = intr->addr4; +#ifdef HAVE_IPV6 + if (type == T_AAAA) + addrlist = intr->addr6; +#endif + ans = 1; + if (!dryrun) { - ans = 1; - if (!dryrun) - { - if ((addr.addr.addr4 = get_ifaddr(intr->intr)).s_addr == (in_addr_t) -1) - log_query(F_FORWARD | F_CONFIG | F_IPV4 | F_NEG, name, NULL, NULL); - else - { - log_query(F_FORWARD | F_CONFIG | F_IPV4, name, &addr, NULL); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - daemon->local_ttl, NULL, type, C_IN, "4", &addr)) - anscount++; - } - } - continue; + if (!addrlist) + log_query(F_FORWARD | F_CONFIG | flag | F_NEG, name, NULL, NULL); + else + for (; addrlist; addrlist = addrlist->next) + { + log_query(F_FORWARD | F_CONFIG | flag, name, &addrlist->addr, NULL); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, type, C_IN, + type == T_A ? "4" : "6", &addrlist->addr)) + anscount++; + } } + continue; } cname_restart: diff --git a/src/rfc2131.c b/src/rfc2131.c index 54e444b..499f5c4 100644 --- a/src/rfc2131.c +++ b/src/rfc2131.c @@ -1072,7 +1072,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, Have to set override to make sure we echo back the correct server-id */ struct irec *intr; - enumerate_interfaces(); + enumerate_interfaces(0); for (intr = daemon->interfaces; intr; intr = intr->next) if (intr->addr.sa.sa_family == AF_INET && diff --git a/src/tftp.c b/src/tftp.c index d7d050f..f34351c 100644 --- a/src/tftp.c +++ b/src/tftp.c @@ -201,7 +201,7 @@ void tftp_request(struct listener *listen, time_t now) if (!iface_check(listen->family, &addra, name, NULL)) { if (!option_bool(OPT_CLEVERBIND)) - enumerate_interfaces(); + enumerate_interfaces(0); if (!loopback_exception(listen->tftpfd, listen->family, &addra, name) && !label_exception(if_index, listen->family, &addra) ) return;