diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/arp.c | 33 | ||||
-rw-r--r-- | src/auth.c | 151 | ||||
-rw-r--r-- | src/blockdata.c | 51 | ||||
-rw-r--r-- | src/bpf.c | 24 | ||||
-rw-r--r-- | src/cache.c | 917 | ||||
-rw-r--r-- | src/config.h | 88 | ||||
-rw-r--r-- | src/conntrack.c | 10 | ||||
-rw-r--r-- | src/crypto.c | 143 | ||||
-rw-r--r-- | src/dbus.c | 63 | ||||
-rw-r--r-- | src/dhcp-common.c | 142 | ||||
-rw-r--r-- | src/dhcp-protocol.h | 3 | ||||
-rw-r--r-- | src/dhcp.c | 153 | ||||
-rw-r--r-- | src/dhcp6-protocol.h | 10 | ||||
-rw-r--r-- | src/dhcp6.c | 331 | ||||
-rw-r--r-- | src/dns-protocol.h | 3 | ||||
-rw-r--r-- | src/dnsmasq.c | 429 | ||||
-rw-r--r-- | src/dnsmasq.h | 337 | ||||
-rw-r--r-- | src/dnssec.c | 358 | ||||
-rw-r--r-- | src/domain.c | 60 | ||||
-rw-r--r-- | src/dump.c | 211 | ||||
-rw-r--r-- | src/edns0.c | 16 | ||||
-rw-r--r-- | src/forward.c | 540 | ||||
-rw-r--r-- | src/helper.c | 39 | ||||
-rw-r--r-- | src/inotify.c | 2 | ||||
-rw-r--r-- | src/ip6addr.h | 2 | ||||
-rw-r--r-- | src/ipset.c | 36 | ||||
-rw-r--r-- | src/lease.c | 79 | ||||
-rw-r--r-- | src/log.c | 4 | ||||
-rw-r--r-- | src/loop.c | 2 | ||||
-rw-r--r-- | src/metrics.c | 44 | ||||
-rw-r--r-- | src/metrics.h | 43 | ||||
-rw-r--r-- | src/netlink.c | 38 | ||||
-rw-r--r-- | src/network.c | 293 | ||||
-rw-r--r-- | src/option.c | 1380 | ||||
-rw-r--r-- | src/outpacket.c | 2 | ||||
-rw-r--r-- | src/poll.c | 2 | ||||
-rw-r--r-- | src/radv-protocol.h | 2 | ||||
-rw-r--r-- | src/radv.c | 38 | ||||
-rw-r--r-- | src/rfc1035.c | 512 | ||||
-rw-r--r-- | src/rfc2131.c | 200 | ||||
-rw-r--r-- | src/rfc3315.c | 527 | ||||
-rw-r--r-- | src/rrfilter.c | 2 | ||||
-rw-r--r-- | src/slaac.c | 5 | ||||
-rw-r--r-- | src/tables.c | 9 | ||||
-rw-r--r-- | src/tftp.c | 234 | ||||
-rw-r--r-- | src/ubus.c | 205 | ||||
-rw-r--r-- | src/util.c | 136 |
47 files changed, 5010 insertions, 2899 deletions
@@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -28,7 +28,7 @@ struct arp_record { unsigned short hwlen, status; int family; unsigned char hwaddr[DHCP_CHADDR_MAX]; - struct all_addr addr; + union all_addr addr; struct arp_record *next; }; @@ -44,11 +44,6 @@ static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *p if (maclen > DHCP_CHADDR_MAX) return 1; -#ifndef HAVE_IPV6 - if (family != AF_INET) - return 1; -#endif - /* Look for existing entry */ for (arp = arps; arp; arp = arp->next) { @@ -57,16 +52,14 @@ static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *p if (family == AF_INET) { - if (arp->addr.addr.addr4.s_addr != ((struct in_addr *)addrp)->s_addr) + if (arp->addr.addr4.s_addr != ((struct in_addr *)addrp)->s_addr) continue; } -#ifdef HAVE_IPV6 else { - if (!IN6_ARE_ADDR_EQUAL(&arp->addr.addr.addr6, (struct in6_addr *)addrp)) + if (!IN6_ARE_ADDR_EQUAL(&arp->addr.addr6, (struct in6_addr *)addrp)) continue; } -#endif if (arp->status == ARP_EMPTY) { @@ -102,11 +95,9 @@ static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *p arp->family = family; memcpy(arp->hwaddr, mac, maclen); if (family == AF_INET) - arp->addr.addr.addr4.s_addr = ((struct in_addr *)addrp)->s_addr; -#ifdef HAVE_IPV6 + arp->addr.addr4.s_addr = ((struct in_addr *)addrp)->s_addr; else - memcpy(&arp->addr.addr.addr6, addrp, IN6ADDRSZ); -#endif + memcpy(&arp->addr.addr6, addrp, IN6ADDRSZ); } return 1; @@ -133,14 +124,12 @@ int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy, time_t now) continue; if (arp->family == AF_INET && - arp->addr.addr.addr4.s_addr != addr->in.sin_addr.s_addr) + arp->addr.addr4.s_addr != addr->in.sin_addr.s_addr) continue; -#ifdef HAVE_IPV6 if (arp->family == AF_INET6 && - !IN6_ARE_ADDR_EQUAL(&arp->addr.addr.addr6, &addr->in6.sin6_addr)) + !IN6_ARE_ADDR_EQUAL(&arp->addr.addr6, &addr->in6.sin6_addr)) continue; -#endif /* Only accept positive entries unless in lazy mode. */ if (arp->status != ARP_EMPTY || lazy || updated) @@ -202,11 +191,9 @@ int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy, time_t now) arp->hwlen = 0; if (addr->sa.sa_family == AF_INET) - arp->addr.addr.addr4.s_addr = addr->in.sin_addr.s_addr; -#ifdef HAVE_IPV6 + arp->addr.addr4.s_addr = addr->in.sin_addr.s_addr; else - memcpy(&arp->addr.addr.addr6, &addr->in6.sin6_addr, IN6ADDRSZ); -#endif + memcpy(&arp->addr.addr6, &addr->in6.sin6_addr, IN6ADDRSZ); } return 0; @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -18,32 +18,30 @@ #ifdef HAVE_AUTH -static struct addrlist *find_addrlist(struct addrlist *list, int flag, struct all_addr *addr_u) +static struct addrlist *find_addrlist(struct addrlist *list, int flag, union all_addr *addr_u) { do { if (!(list->flags & ADDRLIST_IPV6)) { - struct in_addr netmask, addr = addr_u->addr.addr4; + struct in_addr netmask, addr = addr_u->addr4; if (!(flag & F_IPV4)) continue; netmask.s_addr = htonl(~(in_addr_t)0 << (32 - list->prefixlen)); - if (is_same_net(addr, list->addr.addr.addr4, netmask)) + if (is_same_net(addr, list->addr.addr4, netmask)) return list; } -#ifdef HAVE_IPV6 - else if (is_same_net6(&(addr_u->addr.addr6), &list->addr.addr.addr6, list->prefixlen)) + else if (is_same_net6(&(addr_u->addr6), &list->addr.addr6, list->prefixlen)) return list; -#endif } while ((list = list->next)); return NULL; } -static struct addrlist *find_subnet(struct auth_zone *zone, int flag, struct all_addr *addr_u) +static struct addrlist *find_subnet(struct auth_zone *zone, int flag, union all_addr *addr_u) { if (!zone->subnet) return NULL; @@ -51,7 +49,7 @@ static struct addrlist *find_subnet(struct auth_zone *zone, int flag, struct all return find_addrlist(zone->subnet, flag, addr_u); } -static struct addrlist *find_exclude(struct auth_zone *zone, int flag, struct all_addr *addr_u) +static struct addrlist *find_exclude(struct auth_zone *zone, int flag, union all_addr *addr_u) { if (!zone->exclude) return NULL; @@ -59,7 +57,7 @@ static struct addrlist *find_exclude(struct auth_zone *zone, int flag, struct al return find_addrlist(zone->exclude, flag, addr_u); } -static int filter_zone(struct auth_zone *zone, int flag, struct all_addr *addr_u) +static int filter_zone(struct auth_zone *zone, int flag, union all_addr *addr_u) { if (find_exclude(zone, flag, addr_u)) return 0; @@ -103,7 +101,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n { char *name = daemon->namebuff; unsigned char *p, *ansp; - int qtype, qclass; + int qtype, qclass, rc; int nameoffset, axfroffset = 0; int q, anscount = 0, authcount = 0; struct crec *crecp; @@ -115,7 +113,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n struct txt_record *txt; struct interface_name *intr; struct naptr *na; - struct all_addr addr; + union all_addr addr; struct cname *a, *candidate; unsigned int wclen; @@ -131,7 +129,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n for (q = ntohs(header->qdcount); q != 0; q--) { - unsigned short flag = 0; + unsigned int flag = 0; int found = 0; int cname_wildcard = 0; @@ -180,7 +178,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n struct addrlist *addrlist; for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) - if (!(addrlist->flags & ADDRLIST_IPV6) && addr.addr.addr4.s_addr == addrlist->addr.addr.addr4.s_addr) + if (!(addrlist->flags & ADDRLIST_IPV6) && addr.addr4.s_addr == addrlist->addr.addr4.s_addr) break; if (addrlist) @@ -189,14 +187,13 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n 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->addr; addrlist; addrlist = addrlist->next) - if ((addrlist->flags & ADDRLIST_IPV6) && IN6_ARE_ADDR_EQUAL(&addr.addr.addr6, &addrlist->addr.addr.addr6)) + if ((addrlist->flags & ADDRLIST_IPV6) && IN6_ARE_ADDR_EQUAL(&addr.addr6, &addrlist->addr.addr6)) break; if (addrlist) @@ -205,7 +202,6 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n while (intr->next && strcmp(intr->intr, intr->next->intr) == 0) intr = intr->next; } -#endif if (intr) { @@ -283,11 +279,11 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n } for (rec = daemon->mxnames; rec; rec = rec->next) - if (!rec->issrv && hostname_isequal(name, rec->name)) + if (!rec->issrv && (rc = hostname_issubdomain(name, rec->name))) { nxdomain = 0; - if (qtype == T_MX) + if (rc == 2 && qtype == T_MX) { found = 1; log_query(F_CONFIG | F_RRNAME, name, NULL, "<MX>"); @@ -298,11 +294,11 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n } for (move = NULL, up = &daemon->mxnames, rec = daemon->mxnames; rec; rec = rec->next) - if (rec->issrv && hostname_isequal(name, rec->name)) + if (rec->issrv && (rc = hostname_issubdomain(name, rec->name))) { nxdomain = 0; - if (qtype == T_SRV) + if (rc == 2 && qtype == T_SRV) { found = 1; log_query(F_CONFIG | F_RRNAME, name, NULL, "<SRV>"); @@ -333,13 +329,13 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n } for (txt = daemon->rr; txt; txt = txt->next) - if (hostname_isequal(name, txt->name)) + if ((rc = hostname_issubdomain(name, txt->name))) { nxdomain = 0; - if (txt->class == qtype) + if (rc == 2 && txt->class == qtype) { found = 1; - log_query(F_CONFIG | F_RRNAME, name, NULL, "<RR>"); + log_query(F_CONFIG | F_RRNAME, name, NULL, querystr(NULL, txt->class)); if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl, NULL, txt->class, C_IN, "t", txt->len, txt->txt)) anscount++; @@ -347,10 +343,10 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n } for (txt = daemon->txt; txt; txt = txt->next) - if (txt->class == C_IN && hostname_isequal(name, txt->name)) + if (txt->class == C_IN && (rc = hostname_issubdomain(name, txt->name))) { nxdomain = 0; - if (qtype == T_TXT) + if (rc == 2 && qtype == T_TXT) { found = 1; log_query(F_CONFIG | F_RRNAME, name, NULL, "<TXT>"); @@ -361,10 +357,10 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n } for (na = daemon->naptr; na; na = na->next) - if (hostname_isequal(name, na->name)) + if ((rc = hostname_issubdomain(name, na->name))) { nxdomain = 0; - if (qtype == T_NAPTR) + if (rc == 2 && qtype == T_NAPTR) { found = 1; log_query(F_CONFIG | F_RRNAME, name, NULL, "<NAPTR>"); @@ -378,27 +374,24 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n 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)) + if ((rc = hostname_issubdomain(name, intr->name))) { struct addrlist *addrlist; nxdomain = 0; - if (flag) + if (rc == 2 && flag) for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) if (((addrlist->flags & ADDRLIST_IPV6) ? T_AAAA : T_A) == qtype && (local_query || filter_zone(zone, flag, &addrlist->addr))) { -#ifdef HAVE_IPV6 if (addrlist->flags & ADDRLIST_REVONLY) continue; -#endif + found = 1; log_query(F_FORWARD | F_CONFIG | flag, name, &addrlist->addr, NULL); if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, @@ -424,27 +417,24 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n if (peer_addr->sa.sa_family == AF_INET) peer_addr->in.sin_port = 0; -#ifdef HAVE_IPV6 else { peer_addr->in6.sin6_port = 0; peer_addr->in6.sin6_scope_id = 0; } -#endif for (peers = daemon->auth_peers; peers; peers = peers->next) if (sockaddr_isequal(peer_addr, &peers->addr)) break; - /* Refuse all AXFR unless --auth-sec-servers is set */ - if ((!peers && daemon->auth_peers) || !daemon->secondary_forward_server) + /* Refuse all AXFR unless --auth-sec-servers or auth-peers is set */ + if ((!daemon->secondary_forward_server && !daemon->auth_peers) || + (daemon->auth_peers && !peers)) { if (peer_addr->sa.sa_family == AF_INET) inet_ntop(AF_INET, &peer_addr->in.sin_addr, daemon->addrbuff, ADDRSTRLEN); -#ifdef HAVE_IPV6 else inet_ntop(AF_INET6, &peer_addr->in6.sin6_addr, daemon->addrbuff, ADDRSTRLEN); -#endif my_syslog(LOG_WARNING, _("ignoring zone transfer request from %s"), daemon->addrbuff); return 0; @@ -478,10 +468,10 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n { nxdomain = 0; if ((crecp->flags & flag) && - (local_query || filter_zone(zone, flag, &(crecp->addr.addr)))) + (local_query || filter_zone(zone, flag, &(crecp->addr)))) { *cut = '.'; /* restore domain part */ - log_query(crecp->flags, name, &crecp->addr.addr, record_source(crecp->uid)); + log_query(crecp->flags, name, &crecp->addr, record_source(crecp->uid)); *cut = 0; /* remove domain part */ found = 1; if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, @@ -501,9 +491,9 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n do { nxdomain = 0; - if ((crecp->flags & flag) && (local_query || filter_zone(zone, flag, &(crecp->addr.addr)))) + if ((crecp->flags & flag) && (local_query || filter_zone(zone, flag, &(crecp->addr)))) { - log_query(crecp->flags, name, &crecp->addr.addr, record_source(crecp->uid)); + log_query(crecp->flags, name, &crecp->addr, record_source(crecp->uid)); found = 1; if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl, NULL, qtype, C_IN, @@ -566,6 +556,8 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n goto cname_restart; } + else if (cache_find_non_terminal(name, now)) + nxdomain = 0; log_query(flag | F_NEG | (nxdomain ? F_NXDOMAIN : 0) | F_FORWARD | F_AUTH, name, NULL, NULL); } @@ -588,7 +580,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n if (!(subnet->flags & ADDRLIST_IPV6)) { - in_addr_t a = ntohl(subnet->addr.addr.addr4.s_addr) >> 8; + in_addr_t a = ntohl(subnet->addr.addr4.s_addr) >> 8; char *p = name; if (subnet->prefixlen >= 24) @@ -600,7 +592,6 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n p += sprintf(p, "%u.in-addr.arpa", a & 0xff); } -#ifdef HAVE_IPV6 else { char *p = name; @@ -608,13 +599,12 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n for (i = subnet->prefixlen-1; i >= 0; i -= 4) { - int dig = ((unsigned char *)&subnet->addr.addr.addr6)[i>>3]; + int dig = ((unsigned char *)&subnet->addr.addr6)[i>>3]; p += sprintf(p, "%.1x.", (i>>2) & 1 ? dig & 15 : dig >> 4); } p += sprintf(p, "ip6.arpa"); } -#endif } /* handle NS and SOA in auth section or for explicit queries */ @@ -638,16 +628,20 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n { struct name_list *secondary; - newoffset = ansp - (unsigned char *)header; - if (add_resource_record(header, limit, &trunc, -offset, &ansp, - daemon->auth_ttl, NULL, T_NS, C_IN, "d", offset == 0 ? authname : NULL, daemon->authserver)) + /* Only include the machine running dnsmasq if it's acting as an auth server */ + if (daemon->authinterface) { - if (offset == 0) - offset = newoffset; - if (ns) - anscount++; - else - authcount++; + newoffset = ansp - (unsigned char *)header; + if (add_resource_record(header, limit, &trunc, -offset, &ansp, + daemon->auth_ttl, NULL, T_NS, C_IN, "d", offset == 0 ? authname : NULL, daemon->authserver)) + { + if (offset == 0) + offset = newoffset; + if (ns) + anscount++; + else + authcount++; + } } if (!subnet) @@ -751,14 +745,12 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n daemon->auth_ttl, NULL, T_A, C_IN, "4", cut ? intr->name : NULL, &addrlist->addr)) anscount++; -#ifdef HAVE_IPV6 for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) if ((addrlist->flags & ADDRLIST_IPV6) && (local_query || filter_zone(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) @@ -795,38 +787,26 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n { char *cache_name = cache_get_name(crecp); if (!strchr(cache_name, '.') && - (local_query || filter_zone(zone, (crecp->flags & (F_IPV6 | F_IPV4)), &(crecp->addr.addr)))) - { - qtype = T_A; -#ifdef HAVE_IPV6 - if (crecp->flags & F_IPV6) - qtype = T_AAAA; -#endif - if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, - daemon->auth_ttl, NULL, qtype, C_IN, - (crecp->flags & F_IPV4) ? "4" : "6", cache_name, &crecp->addr)) - anscount++; - } + (local_query || filter_zone(zone, (crecp->flags & (F_IPV6 | F_IPV4)), &(crecp->addr))) && + add_resource_record(header, limit, &trunc, -axfroffset, &ansp, + daemon->auth_ttl, NULL, (crecp->flags & F_IPV6) ? T_AAAA : T_A, C_IN, + (crecp->flags & F_IPV4) ? "4" : "6", cache_name, &crecp->addr)) + anscount++; } if ((crecp->flags & F_HOSTS) || (((crecp->flags & F_DHCP) && option_bool(OPT_DHCP_FQDN)))) { strcpy(name, cache_get_name(crecp)); if (in_zone(zone, name, &cut) && - (local_query || filter_zone(zone, (crecp->flags & (F_IPV6 | F_IPV4)), &(crecp->addr.addr)))) + (local_query || filter_zone(zone, (crecp->flags & (F_IPV6 | F_IPV4)), &(crecp->addr)))) { - qtype = T_A; -#ifdef HAVE_IPV6 - if (crecp->flags & F_IPV6) - qtype = T_AAAA; -#endif - if (cut) - *cut = 0; - - if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, - daemon->auth_ttl, NULL, qtype, C_IN, - (crecp->flags & F_IPV4) ? "4" : "6", cut ? name : NULL, &crecp->addr)) - anscount++; + if (cut) + *cut = 0; + + if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, + daemon->auth_ttl, NULL, (crecp->flags & F_IPV6) ? T_AAAA : T_A, C_IN, + (crecp->flags & F_IPV4) ? "4" : "6", cut ? name : NULL, &crecp->addr)) + anscount++; } } } @@ -860,6 +840,9 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n header->hb4 &= ~HB4_RA; } + /* data is never DNSSEC signed. */ + header->hb4 &= ~HB4_AD; + /* authoritative */ if (auth) header->hb3 |= HB3_AA; diff --git a/src/blockdata.c b/src/blockdata.c index 72f0575..f33b28e 100644 --- a/src/blockdata.c +++ b/src/blockdata.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -16,8 +16,6 @@ #include "dnsmasq.h" -#ifdef HAVE_DNSSEC - static struct blockdata *keyblock_free; static unsigned int blockdata_count, blockdata_hwm, blockdata_alloced; @@ -54,14 +52,13 @@ void blockdata_init(void) void blockdata_report(void) { - if (option_bool(OPT_DNSSEC_VALID)) - my_syslog(LOG_INFO, _("DNSSEC memory in use %u, max %u, allocated %u"), - blockdata_count * sizeof(struct blockdata), - blockdata_hwm * sizeof(struct blockdata), - blockdata_alloced * sizeof(struct blockdata)); + my_syslog(LOG_INFO, _("pool memory in use %u, max %u, allocated %u"), + blockdata_count * sizeof(struct blockdata), + blockdata_hwm * sizeof(struct blockdata), + blockdata_alloced * sizeof(struct blockdata)); } -struct blockdata *blockdata_alloc(char *data, size_t len) +static struct blockdata *blockdata_alloc_real(int fd, char *data, size_t len) { struct blockdata *block, *ret = NULL; struct blockdata **prev = &ret; @@ -89,8 +86,17 @@ struct blockdata *blockdata_alloc(char *data, size_t len) blockdata_hwm = blockdata_count; blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len; - memcpy(block->key, data, blen); - data += blen; + if (data) + { + memcpy(block->key, data, blen); + data += blen; + } + else if (!read_write(fd, block->key, blen, 1)) + { + /* failed read free partial chain */ + blockdata_free(ret); + return NULL; + } len -= blen; *prev = block; prev = &block->next; @@ -100,6 +106,10 @@ struct blockdata *blockdata_alloc(char *data, size_t len) return ret; } +struct blockdata *blockdata_alloc(char *data, size_t len) +{ + return blockdata_alloc_real(0, data, len); +} void blockdata_free(struct blockdata *blocks) { @@ -148,5 +158,20 @@ void *blockdata_retrieve(struct blockdata *block, size_t len, void *data) return data; } - -#endif + + +void blockdata_write(struct blockdata *block, size_t len, int fd) +{ + for (; len > 0 && block; block = block->next) + { + size_t blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len; + read_write(fd, block->key, blen, 0); + len -= blen; + } +} + +struct blockdata *blockdata_read(int fd, size_t len) +{ + return blockdata_alloc_real(fd, NULL, len); +} + @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -31,9 +31,7 @@ # include <net/if_var.h> #endif #include <netinet/in_var.h> -#ifdef HAVE_IPV6 -# include <netinet6/in6_var.h> -#endif +#include <netinet6/in6_var.h> #ifndef SA_SIZE #define SA_SIZE(sa) \ @@ -44,7 +42,7 @@ #ifdef HAVE_BSD_NETWORK static int del_family = 0; -static struct all_addr del_addr; +static union all_addr del_addr; #endif #if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__) @@ -121,7 +119,7 @@ int iface_enumerate(int family, void *parm, int (*callback)()) if (getifaddrs(&head) == -1) return 0; -#if defined(HAVE_BSD_NETWORK) && defined(HAVE_IPV6) +#if defined(HAVE_BSD_NETWORK) if (family == AF_INET6) fd = socket(PF_INET6, SOCK_DGRAM, 0); #endif @@ -141,7 +139,7 @@ int iface_enumerate(int family, void *parm, int (*callback)()) struct in_addr addr, netmask, broadcast; addr = ((struct sockaddr_in *) addrs->ifa_addr)->sin_addr; #ifdef HAVE_BSD_NETWORK - if (del_family == AF_INET && del_addr.addr.addr4.s_addr == addr.s_addr) + if (del_family == AF_INET && del_addr.addr4.s_addr == addr.s_addr) continue; #endif netmask = ((struct sockaddr_in *) addrs->ifa_netmask)->sin_addr; @@ -152,7 +150,6 @@ int iface_enumerate(int family, void *parm, int (*callback)()) if (!((*callback)(addr, iface_index, NULL, netmask, broadcast, parm))) goto err; } -#ifdef HAVE_IPV6 else if (family == AF_INET6) { struct in6_addr *addr = &((struct sockaddr_in6 *) addrs->ifa_addr)->sin6_addr; @@ -162,14 +159,14 @@ int iface_enumerate(int family, void *parm, int (*callback)()) u32 valid = 0xffffffff, preferred = 0xffffffff; int flags = 0; #ifdef HAVE_BSD_NETWORK - if (del_family == AF_INET6 && IN6_ARE_ADDR_EQUAL(&del_addr.addr.addr6, addr)) + if (del_family == AF_INET6 && IN6_ARE_ADDR_EQUAL(&del_addr.addr6, addr)) continue; #endif #if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__) struct in6_ifreq ifr6; memset(&ifr6, 0, sizeof(ifr6)); - strncpy(ifr6.ifr_name, addrs->ifa_name, sizeof(ifr6.ifr_name)); + safe_strncpy(ifr6.ifr_name, addrs->ifa_name, sizeof(ifr6.ifr_name)); ifr6.ifr_addr = *((struct sockaddr_in6 *) addrs->ifa_addr); if (fd != -1 && ioctl(fd, SIOCGIFAFLAG_IN6, &ifr6) != -1) @@ -219,7 +216,6 @@ int iface_enumerate(int family, void *parm, int (*callback)()) (int) preferred, (int)valid, parm))) goto err; } -#endif /* HAVE_IPV6 */ #ifdef HAVE_DHCP6 else if (family == AF_LINK) @@ -426,11 +422,9 @@ void route_sock(void) { del_family = sa->sa_family; if (del_family == AF_INET) - del_addr.addr.addr4 = ((struct sockaddr_in *)sa)->sin_addr; -#ifdef HAVE_IPV6 + del_addr.addr4 = ((struct sockaddr_in *)sa)->sin_addr; else if (del_family == AF_INET6) - del_addr.addr.addr6 = ((struct sockaddr_in6 *)sa)->sin6_addr; -#endif + del_addr.addr6 = ((struct sockaddr_in6 *)sa)->sin6_addr; else del_family = 0; } diff --git a/src/cache.c b/src/cache.c index 8b1b560..2f2c519 100644 --- a/src/cache.c +++ b/src/cache.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -21,10 +21,14 @@ static struct crec *cache_head = NULL, *cache_tail = NULL, **hash_table = NULL; static struct crec *dhcp_spare = NULL; #endif static struct crec *new_chain = NULL; -static int cache_inserted = 0, cache_live_freed = 0, insert_error; +static int insert_error; static union bigname *big_free = NULL; static int bignames_left, hash_size; +static void make_non_terminals(struct crec *source); +static struct crec *really_insert(char *name, union all_addr *addr, unsigned short class, + time_t now, unsigned long ttl, unsigned int flags); + /* type->string mapping: this is also used by the name-hash function as a mixing table. */ static const struct { unsigned int type; @@ -68,7 +72,8 @@ static const struct { { 252, "AXFR" }, { 253, "MAILB" }, { 254, "MAILA" }, - { 255, "ANY" } + { 255, "ANY" }, + { 257, "CAA" } }; static void cache_free(struct crec *crecp); @@ -77,17 +82,20 @@ static void cache_link(struct crec *crecp); static void rehash(int size); static void cache_hash(struct crec *crecp); -static unsigned int next_uid(void) +void next_uid(struct crec *crecp) { static unsigned int uid = 0; - uid++; - - /* uid == 0 used to indicate CNAME to interface name. */ - if (uid == SRC_INTERFACE) - uid++; + if (crecp->uid == UID_NONE) + { + uid++; - return uid; + /* uid == 0 used to indicate CNAME to interface name. */ + if (uid == UID_NONE) + uid++; + + crecp->uid = uid; + } } void cache_init(void) @@ -105,7 +113,7 @@ void cache_init(void) { cache_link(crecp); crecp->flags = 0; - crecp->uid = next_uid(); + crecp->uid = UID_NONE; } } @@ -190,21 +198,26 @@ static void cache_hash(struct crec *crecp) *up = crecp; } -#ifdef HAVE_DNSSEC static void cache_blockdata_free(struct crec *crecp) { - if (crecp->flags & F_DNSKEY) - blockdata_free(crecp->addr.key.keydata); - else if ((crecp->flags & F_DS) && !(crecp->flags & F_NEG)) - blockdata_free(crecp->addr.ds.keydata); -} + if (!(crecp->flags & F_NEG)) + { + if (crecp->flags & F_SRV) + blockdata_free(crecp->addr.srv.target); +#ifdef HAVE_DNSSEC + else if (crecp->flags & F_DNSKEY) + blockdata_free(crecp->addr.key.keydata); + else if (crecp->flags & F_DS) + blockdata_free(crecp->addr.ds.keydata); #endif + } +} static void cache_free(struct crec *crecp) { crecp->flags &= ~F_FORWARD; crecp->flags &= ~F_REVERSE; - crecp->uid = next_uid(); /* invalidate CNAMES pointing to this. */ + crecp->uid = UID_NONE; /* invalidate CNAMES pointing to this. */ if (cache_tail) cache_tail->next = crecp; @@ -222,9 +235,7 @@ static void cache_free(struct crec *crecp) crecp->flags &= ~F_BIGNAME; } -#ifdef HAVE_DNSSEC cache_blockdata_free(crecp); -#endif } /* insert a new cache entry at the head of the list (youngest entry) */ @@ -265,10 +276,10 @@ char *cache_get_name(struct crec *crecp) char *cache_get_cname_target(struct crec *crecp) { - if (crecp->addr.cname.uid != SRC_INTERFACE) + if (crecp->addr.cname.is_name_ptr) + return crecp->addr.cname.target.name; + else return cache_get_name(crecp->addr.cname.target.cache); - - return crecp->addr.cname.target.int_name->name; } @@ -298,13 +309,13 @@ struct crec *cache_enumerate(int init) static int is_outdated_cname_pointer(struct crec *crecp) { - if (!(crecp->flags & F_CNAME) || crecp->addr.cname.uid == SRC_INTERFACE) + if (!(crecp->flags & F_CNAME) || crecp->addr.cname.is_name_ptr) return 0; /* NB. record may be reused as DS or DNSKEY, where uid is overloaded for something completely different */ if (crecp->addr.cname.target.cache && - (crecp->addr.cname.target.cache->flags & (F_IPV4 | F_IPV6 | F_CNAME)) && + !(crecp->addr.cname.target.cache->flags & (F_DNSKEY | F_DS)) && crecp->addr.cname.uid == crecp->addr.cname.target.cache->uid) return 0; @@ -322,7 +333,8 @@ static int is_expired(time_t now, struct crec *crecp) return 1; } -static struct crec *cache_scan_free(char *name, struct all_addr *addr, time_t now, unsigned short flags) +static struct crec *cache_scan_free(char *name, union all_addr *addr, unsigned short class, time_t now, + unsigned int flags, struct crec **target_crec, unsigned int *target_uid) { /* Scan and remove old entries. If (flags & F_FORWARD) then remove any forward entries for name and any expired @@ -335,34 +347,38 @@ static struct crec *cache_scan_free(char *name, struct all_addr *addr, time_t no to a cache entry if the name exists in the cache as a HOSTS or DHCP entry (these are never deleted) We take advantage of the fact that hash chains have stuff in the order <reverse>,<other>,<immortal> - so that when we hit an entry which isn't reverse and is immortal, we're done. */ + so that when we hit an entry which isn't reverse and is immortal, we're done. + + If we free a crec which is a CNAME target, return the entry and uid in target_crec and target_uid. + This entry will get re-used with the same name, to preserve CNAMEs. */ struct crec *crecp, **up; + + (void)class; if (flags & F_FORWARD) { for (up = hash_bucket(name), crecp = *up; crecp; crecp = crecp->hash_next) { - if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp)) - { - *up = crecp->hash_next; - if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) - { - cache_unlink(crecp); - cache_free(crecp); - } - continue; - } - if ((crecp->flags & F_FORWARD) && hostname_isequal(cache_get_name(crecp), name)) { /* Don't delete DNSSEC in favour of a CNAME, they can co-exist */ - if ((flags & crecp->flags & (F_IPV4 | F_IPV6)) || + if ((flags & crecp->flags & (F_IPV4 | F_IPV6 | F_SRV)) || (((crecp->flags | flags) & F_CNAME) && !(crecp->flags & (F_DNSKEY | F_DS)))) { if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) return crecp; *up = crecp->hash_next; + /* If this record is for the name we're inserting and is the target + of a CNAME record. Make the new record for the same name, in the same + crec, with the same uid to avoid breaking the existing CNAME. */ + if (crecp->uid != UID_NONE) + { + if (target_crec) + *target_crec = crecp; + if (target_uid) + *target_uid = crecp->uid; + } cache_unlink(crecp); cache_free(crecp); continue; @@ -370,7 +386,7 @@ static struct crec *cache_scan_free(char *name, struct all_addr *addr, time_t no #ifdef HAVE_DNSSEC /* Deletion has to be class-sensitive for DS and DNSKEY */ - if ((flags & crecp->flags & (F_DNSKEY | F_DS)) && crecp->uid == addr->addr.dnssec.class) + if ((flags & crecp->flags & (F_DNSKEY | F_DS)) && crecp->uid == class) { if (crecp->flags & F_CONFIG) return crecp; @@ -381,17 +397,26 @@ static struct crec *cache_scan_free(char *name, struct all_addr *addr, time_t no } #endif } + + if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp)) + { + *up = crecp->hash_next; + if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) + { + cache_unlink(crecp); + cache_free(crecp); + } + continue; + } + up = &crecp->hash_next; } } else { int i; -#ifdef HAVE_IPV6 int addrlen = (flags & F_IPV6) ? IN6ADDRSZ : INADDRSZ; -#else - int addrlen = INADDRSZ; -#endif + for (i = 0; i < hash_size; i++) for (crecp = hash_table[i], up = &hash_table[i]; crecp && ((crecp->flags & F_REVERSE) || !(crecp->flags & F_IMMORTAL)); @@ -408,7 +433,7 @@ static struct crec *cache_scan_free(char *name, struct all_addr *addr, time_t no else if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && (flags & crecp->flags & F_REVERSE) && (flags & crecp->flags & (F_IPV4 | F_IPV6)) && - memcmp(&crecp->addr.addr, addr, addrlen) == 0) + memcmp(&crecp->addr, addr, addrlen) == 0) { *up = crecp->hash_next; cache_unlink(crecp); @@ -443,131 +468,158 @@ void cache_start_insert(void) new_chain = NULL; insert_error = 0; } - -struct crec *cache_insert(char *name, struct all_addr *addr, - time_t now, unsigned long ttl, unsigned short flags) -{ - struct crec *new; - union bigname *big_name = NULL; - int freed_all = flags & F_REVERSE; - int free_avail = 0; - /* Don't log DNSSEC records here, done elsewhere */ - if (flags & (F_IPV4 | F_IPV6 | F_CNAME)) +struct crec *cache_insert(char *name, union all_addr *addr, unsigned short class, + time_t now, unsigned long ttl, unsigned int flags) +{ +#ifdef HAVE_DNSSEC + if (flags & (F_DNSKEY | F_DS)) + { + /* The DNSSEC validation process works by getting needed records into the + cache, then retrying the validation until they are all in place. + This can be messed up by very short TTLs, and _really_ messed up by + zero TTLs, so we force the TTL to be at least long enough to do a validation. + Ideally, we should use some kind of reference counting so that records are + locked until the validation that asked for them is complete, but this + is much easier, and just as effective. */ + if (ttl < DNSSEC_MIN_TTL) + ttl = DNSSEC_MIN_TTL; + } + else +#endif { + /* Don't log DNSSEC records here, done elsewhere */ log_query(flags | F_UPSTREAM, name, addr, NULL); - /* Don't mess with TTL for DNSSEC records. */ if (daemon->max_cache_ttl != 0 && daemon->max_cache_ttl < ttl) ttl = daemon->max_cache_ttl; if (daemon->min_cache_ttl != 0 && daemon->min_cache_ttl > ttl) ttl = daemon->min_cache_ttl; - } + } + + return really_insert(name, addr, class, now, ttl, flags); +} + +static struct crec *really_insert(char *name, union all_addr *addr, unsigned short class, + time_t now, unsigned long ttl, unsigned int flags) +{ + struct crec *new, *target_crec = NULL; + union bigname *big_name = NULL; + int freed_all = flags & F_REVERSE; + int free_avail = 0; + unsigned int target_uid; + /* if previous insertion failed give up now. */ if (insert_error) return NULL; + + /* we don't cache zero-TTL records. */ + if (ttl == 0) + { + insert_error = 1; + return NULL; + } /* First remove any expired entries and entries for the name/address we are currently inserting. */ - if ((new = cache_scan_free(name, addr, now, flags))) + if ((new = cache_scan_free(name, addr, class, now, flags, &target_crec, &target_uid))) { /* We're trying to insert a record over one from /etc/hosts or DHCP, or other config. If the - existing record is for an A or AAAA and + existing record is for an A or AAAA or CNAME and the record we're trying to insert is the same, just drop the insert, but don't error the whole process. */ if ((flags & (F_IPV4 | F_IPV6)) && (flags & F_FORWARD) && addr) { if ((flags & F_IPV4) && (new->flags & F_IPV4) && - new->addr.addr.addr.addr4.s_addr == addr->addr.addr4.s_addr) + new->addr.addr4.s_addr == addr->addr4.s_addr) return new; -#ifdef HAVE_IPV6 else if ((flags & F_IPV6) && (new->flags & F_IPV6) && - IN6_ARE_ADDR_EQUAL(&new->addr.addr.addr.addr6, &addr->addr.addr6)) + IN6_ARE_ADDR_EQUAL(&new->addr.addr6, &addr->addr6)) return new; -#endif } - + insert_error = 1; return NULL; } /* Now get a cache entry from the end of the LRU list */ - while (1) { - if (!(new = cache_tail)) /* no entries left - cache is too small, bail */ - { - insert_error = 1; - return NULL; - } - - /* End of LRU list is still in use: if we didn't scan all the hash - chains for expired entries do that now. If we already tried that - then it's time to start spilling things. */ - - if (new->flags & (F_FORWARD | F_REVERSE)) - { - /* If free_avail set, we believe that an entry has been freed. - Bugs have been known to make this not true, resulting in - a tight loop here. If that happens, abandon the - insert. Once in this state, all inserts will probably fail. */ - if (free_avail) - { - static int warned = 0; - if (!warned) - { - my_syslog(LOG_ERR, _("Internal error in cache.")); - warned = 1; - } - insert_error = 1; - return NULL; - } - - if (freed_all) - { - struct all_addr free_addr = new->addr.addr;; + if (!target_crec) + while (1) { + if (!(new = cache_tail)) /* no entries left - cache is too small, bail */ + { + insert_error = 1; + return NULL; + } + + /* Free entry at end of LRU list, use it. */ + if (!(new->flags & (F_FORWARD | F_REVERSE))) + break; -#ifdef HAVE_DNSSEC - /* For DNSSEC records, addr holds class. */ - if (new->flags & (F_DS | F_DNSKEY)) - free_addr.addr.dnssec.class = new->uid; -#endif - - free_avail = 1; /* Must be free space now. */ - cache_scan_free(cache_get_name(new), &free_addr, now, new->flags); - cache_live_freed++; - } - else - { - cache_scan_free(NULL, NULL, now, 0); - freed_all = 1; - } - continue; - } - - /* Check if we need to and can allocate extra memory for a long name. - If that fails, give up now, always succeed for DNSSEC records. */ - if (name && (strlen(name) > SMALLDNAME-1)) - { - if (big_free) - { - big_name = big_free; - big_free = big_free->next; - } - else if ((bignames_left == 0 && !(flags & (F_DS | F_DNSKEY))) || - !(big_name = (union bigname *)whine_malloc(sizeof(union bigname)))) - { - insert_error = 1; - return NULL; - } - else if (bignames_left != 0) - bignames_left--; - - } + /* End of LRU list is still in use: if we didn't scan all the hash + chains for expired entries do that now. If we already tried that + then it's time to start spilling things. */ + + /* If free_avail set, we believe that an entry has been freed. + Bugs have been known to make this not true, resulting in + a tight loop here. If that happens, abandon the + insert. Once in this state, all inserts will probably fail. */ + if (free_avail) + { + static int warned = 0; + if (!warned) + { + my_syslog(LOG_ERR, _("Internal error in cache.")); + warned = 1; + } + insert_error = 1; + return NULL; + } + + if (freed_all) + { + /* For DNSSEC records, uid holds class. */ + free_avail = 1; /* Must be free space now. */ + cache_scan_free(cache_get_name(new), &new->addr, new->uid, now, new->flags, NULL, NULL); + daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED]++; + } + else + { + cache_scan_free(NULL, NULL, class, now, 0, NULL, NULL); + freed_all = 1; + } + } + + /* Check if we need to and can allocate extra memory for a long name. + If that fails, give up now, always succeed for DNSSEC records. */ + if (name && (strlen(name) > SMALLDNAME-1)) + { + if (big_free) + { + big_name = big_free; + big_free = big_free->next; + } + else if ((bignames_left == 0 && !(flags & (F_DS | F_DNSKEY))) || + !(big_name = (union bigname *)whine_malloc(sizeof(union bigname)))) + { + insert_error = 1; + return NULL; + } + else if (bignames_left != 0) + bignames_left--; + + } - /* Got the rest: finally grab entry. */ - cache_unlink(new); - break; - } + /* If we freed a cache entry for our name which was a CNAME target, use that. + and preserve the uid, so that existing CNAMES are not broken. */ + if (target_crec) + { + new = target_crec; + new->uid = target_uid; + } + + /* Got the rest: finally grab entry. */ + cache_unlink(new); new->flags = flags; if (big_name) @@ -581,15 +633,13 @@ struct crec *cache_insert(char *name, struct all_addr *addr, else *cache_get_name(new) = 0; - if (addr) - { #ifdef HAVE_DNSSEC - if (flags & (F_DS | F_DNSKEY)) - new->uid = addr->addr.dnssec.class; - else + if (flags & (F_DS | F_DNSKEY)) + new->uid = class; #endif - new->addr.addr = *addr; - } + + if (addr) + new->addr = *addr; new->ttd = now + (time_t)ttl; new->next = new_chain; @@ -614,13 +664,161 @@ void cache_end_insert(void) { cache_hash(new_chain); cache_link(new_chain); - cache_inserted++; + daemon->metrics[METRIC_DNS_CACHE_INSERTED]++; + + /* If we're a child process, send this cache entry up the pipe to the master. + The marshalling process is rather nasty. */ + if (daemon->pipe_to_parent != -1) + { + char *name = cache_get_name(new_chain); + ssize_t m = strlen(name); + unsigned int flags = new_chain->flags; +#ifdef HAVE_DNSSEC + u16 class = new_chain->uid; +#endif + + read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0); + read_write(daemon->pipe_to_parent, (unsigned char *)name, m, 0); + read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->ttd, sizeof(new_chain->ttd), 0); + read_write(daemon->pipe_to_parent, (unsigned char *)&flags, sizeof(flags), 0); + + if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_SRV)) + read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->addr, sizeof(new_chain->addr), 0); + if (flags & F_SRV) + { + /* A negative SRV entry is possible and has no data, obviously. */ + if (!(flags & F_NEG)) + blockdata_write(new_chain->addr.srv.target, new_chain->addr.srv.targetlen, daemon->pipe_to_parent); + } +#ifdef HAVE_DNSSEC + if (flags & F_DNSKEY) + { + read_write(daemon->pipe_to_parent, (unsigned char *)&class, sizeof(class), 0); + blockdata_write(new_chain->addr.key.keydata, new_chain->addr.key.keylen, daemon->pipe_to_parent); + } + else if (flags & F_DS) + { + read_write(daemon->pipe_to_parent, (unsigned char *)&class, sizeof(class), 0); + /* A negative DS entry is possible and has no data, obviously. */ + if (!(flags & F_NEG)) + blockdata_write(new_chain->addr.ds.keydata, new_chain->addr.ds.keylen, daemon->pipe_to_parent); + } +#endif + } } + new_chain = tmp; } + + /* signal end of cache insert in master process */ + if (daemon->pipe_to_parent != -1) + { + ssize_t m = -1; + read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0); + } + new_chain = NULL; } + +/* A marshalled cache entry arrives on fd, read, unmarshall and insert into cache of master process. */ +int cache_recv_insert(time_t now, int fd) +{ + ssize_t m; + union all_addr addr; + unsigned long ttl; + time_t ttd; + unsigned int flags; + struct crec *crecp = NULL; + + cache_start_insert(); + + while(1) + { + + if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1)) + return 0; + + if (m == -1) + { + cache_end_insert(); + return 1; + } + + if (!read_write(fd, (unsigned char *)daemon->namebuff, m, 1) || + !read_write(fd, (unsigned char *)&ttd, sizeof(ttd), 1) || + !read_write(fd, (unsigned char *)&flags, sizeof(flags), 1)) + return 0; + + daemon->namebuff[m] = 0; + + ttl = difftime(ttd, now); + + if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_SRV)) + { + unsigned short class = C_IN; + + if (!read_write(fd, (unsigned char *)&addr, sizeof(addr), 1)) + return 0; + + if ((flags & F_SRV) && !(flags & F_NEG) && !(addr.srv.target = blockdata_read(fd, addr.srv.targetlen))) + return 0; + +#ifdef HAVE_DNSSEC + if (flags & F_DNSKEY) + { + if (!read_write(fd, (unsigned char *)&class, sizeof(class), 1) || + !(addr.key.keydata = blockdata_read(fd, addr.key.keylen))) + return 0; + } + else if (flags & F_DS) + { + if (!read_write(fd, (unsigned char *)&class, sizeof(class), 1) || + (!(flags & F_NEG) && !(addr.key.keydata = blockdata_read(fd, addr.key.keylen)))) + return 0; + } +#endif + + crecp = really_insert(daemon->namebuff, &addr, class, now, ttl, flags); + } + else if (flags & F_CNAME) + { + struct crec *newc = really_insert(daemon->namebuff, NULL, C_IN, now, ttl, flags); + /* This relies on the fact that the target of a CNAME immediately precedes + it because of the order of extraction in extract_addresses, and + the order reversal on the new_chain. */ + if (newc) + { + newc->addr.cname.is_name_ptr = 0; + + if (!crecp) + newc->addr.cname.target.cache = NULL; + else + { + next_uid(crecp); + newc->addr.cname.target.cache = crecp; + newc->addr.cname.uid = crecp->uid; + } + } + } + } +} + +int cache_find_non_terminal(char *name, time_t now) +{ + struct crec *crecp; + + for (crecp = *hash_bucket(name); crecp; crecp = crecp->hash_next) + if (!is_outdated_cname_pointer(crecp) && + !is_expired(now, crecp) && + (crecp->flags & F_FORWARD) && + !(crecp->flags & F_NXDOMAIN) && + hostname_isequal(name, cache_get_name(crecp))) + return 1; + + return 0; +} + struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsigned int prot) { struct crec *ans; @@ -635,7 +833,7 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi /* first search, look for relevant entries and push to top of list also free anything which has expired */ struct crec *next, **up, **insert = NULL, **chainp = &ans; - unsigned short ins_flags = 0; + unsigned int ins_flags = 0; for (up = hash_bucket(name), crecp = *up; crecp; crecp = next) { @@ -708,15 +906,11 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi return NULL; } -struct crec *cache_find_by_addr(struct crec *crecp, struct all_addr *addr, +struct crec *cache_find_by_addr(struct crec *crecp, union all_addr *addr, time_t now, unsigned int prot) { struct crec *ans; -#ifdef HAVE_IPV6 int addrlen = (prot == F_IPV6) ? IN6ADDRSZ : INADDRSZ; -#else - int addrlen = INADDRSZ; -#endif if (crecp) /* iterating */ ans = crecp->next; @@ -736,7 +930,7 @@ struct crec *cache_find_by_addr(struct crec *crecp, struct all_addr *addr, if (!is_expired(now, crecp)) { if ((crecp->flags & prot) && - memcmp(&crecp->addr.addr, addr, addrlen) == 0) + memcmp(&crecp->addr, addr, addrlen) == 0) { if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) { @@ -767,51 +961,26 @@ struct crec *cache_find_by_addr(struct crec *crecp, struct all_addr *addr, if (ans && (ans->flags & F_REVERSE) && (ans->flags & prot) && - memcmp(&ans->addr.addr, addr, addrlen) == 0) + memcmp(&ans->addr, addr, addrlen) == 0) return ans; return NULL; } -static void add_hosts_cname(struct crec *target) -{ - struct crec *crec; - struct cname *a; - - for (a = daemon->cnames; a; a = a->next) - if (a->alias[1] != '*' && - hostname_isequal(cache_get_name(target), a->target) && - (crec = whine_malloc(sizeof(struct crec)))) - { - crec->flags = F_FORWARD | F_IMMORTAL | F_NAMEP | F_CONFIG | F_CNAME; - crec->ttd = a->ttl; - crec->name.namep = a->alias; - crec->addr.cname.target.cache = target; - crec->addr.cname.uid = target->uid; - crec->uid = next_uid(); - cache_hash(crec); - add_hosts_cname(crec); /* handle chains */ - } -} - -static void add_hosts_entry(struct crec *cache, struct all_addr *addr, int addrlen, +static void add_hosts_entry(struct crec *cache, union all_addr *addr, int addrlen, unsigned int index, struct crec **rhash, int hashsz) { struct crec *lookup = cache_find_by_name(NULL, cache_get_name(cache), 0, cache->flags & (F_IPV4 | F_IPV6)); - int i, nameexists = 0; + int i; unsigned int j; /* Remove duplicates in hosts files. */ - if (lookup && (lookup->flags & F_HOSTS)) + if (lookup && (lookup->flags & F_HOSTS) && memcmp(&lookup->addr, addr, addrlen) == 0) { - nameexists = 1; - if (memcmp(&lookup->addr.addr, addr, addrlen) == 0) - { - free(cache); - return; - } + free(cache); + return; } - + /* Ensure there is only one address -> name mapping (first one trumps) We do this by steam here, The entries are kept in hash chains, linked by ->next (which is unused at this point) held in hash buckets in @@ -837,7 +1006,7 @@ static void add_hosts_entry(struct crec *cache, struct all_addr *addr, int addrl for (lookup = rhash[j]; lookup; lookup = lookup->next) if ((lookup->flags & cache->flags & (F_IPV4 | F_IPV6)) && - memcmp(&lookup->addr.addr, addr, addrlen) == 0) + memcmp(&lookup->addr, addr, addrlen) == 0) { cache->flags &= ~F_REVERSE; break; @@ -859,12 +1028,9 @@ static void add_hosts_entry(struct crec *cache, struct all_addr *addr, int addrl } cache->uid = index; - memcpy(&cache->addr.addr, addr, addrlen); + memcpy(&cache->addr, addr, addrlen); cache_hash(cache); - - /* don't need to do alias stuff for second and subsequent addresses. */ - if (!nameexists) - add_hosts_cname(cache); + make_non_terminals(cache); } static int eatspace(FILE *f) @@ -887,7 +1053,7 @@ static int eatspace(FILE *f) } if (c == '\n') - nl = 1; + nl++; } } @@ -898,7 +1064,7 @@ static int gettok(FILE *f, char *token) while (1) { if ((c = getc(f)) == EOF) - return (count == 0) ? EOF : 1; + return (count == 0) ? -1 : 1; if (isspace(c) || c == '#') { @@ -918,9 +1084,9 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr { FILE *f = fopen(filename, "r"); char *token = daemon->namebuff, *domain_suffix = NULL; - int addr_count = 0, name_count = cache_size, lineno = 0; - unsigned short flags = 0; - struct all_addr addr; + int addr_count = 0, name_count = cache_size, lineno = 1; + unsigned int flags = 0; + union all_addr addr; int atnl, addrlen = 0; if (!f) @@ -929,31 +1095,28 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr return cache_size; } - eatspace(f); + lineno += eatspace(f); - while ((atnl = gettok(f, token)) != EOF) + while ((atnl = gettok(f, token)) != -1) { - lineno++; - if (inet_pton(AF_INET, token, &addr) > 0) { flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4; addrlen = INADDRSZ; - domain_suffix = get_domain(addr.addr.addr4); + domain_suffix = get_domain(addr.addr4); } -#ifdef HAVE_IPV6 else if (inet_pton(AF_INET6, token, &addr) > 0) { flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6; addrlen = IN6ADDRSZ; - domain_suffix = get_domain6(&addr.addr.addr6); + domain_suffix = get_domain6(&addr.addr6); } -#endif else { my_syslog(LOG_ERR, _("bad address at %s line %d"), filename, lineno); while (atnl == 0) atnl = gettok(f, token); + lineno += atnl; continue; } @@ -972,7 +1135,7 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr int fqdn, nomem; char *canon; - if ((atnl = gettok(f, token)) == EOF) + if ((atnl = gettok(f, token)) == -1) break; fqdn = !!strchr(token, '.'); @@ -981,8 +1144,7 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr { /* If set, add a version of the name with a default domain appended */ if (option_bool(OPT_EXPAND) && domain_suffix && !fqdn && - (cache = whine_malloc(sizeof(struct crec) + - strlen(canon)+2+strlen(domain_suffix)-SMALLDNAME))) + (cache = whine_malloc(SIZEOF_BARE_CREC + strlen(canon) + 2 + strlen(domain_suffix)))) { strcpy(cache->name.sname, canon); strcat(cache->name.sname, "."); @@ -992,7 +1154,7 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz); name_count++; } - if ((cache = whine_malloc(sizeof(struct crec) + strlen(canon)+1-SMALLDNAME))) + if ((cache = whine_malloc(SIZEOF_BARE_CREC + strlen(canon) + 1))) { strcpy(cache->name.sname, canon); cache->flags = flags; @@ -1006,6 +1168,8 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr else if (!nomem) my_syslog(LOG_ERR, _("bad name at %s line %d"), filename, lineno); } + + lineno += atnl; } fclose(f); @@ -1026,19 +1190,24 @@ void cache_reload(void) struct host_record *hr; struct name_list *nl; struct cname *a; + struct crec lrec; + struct mx_srv_record *mx; + struct txt_record *txt; struct interface_name *intr; + struct ptr_record *ptr; + struct naptr *naptr; #ifdef HAVE_DNSSEC struct ds_config *ds; #endif - cache_inserted = cache_live_freed = 0; + daemon->metrics[METRIC_DNS_CACHE_INSERTED] = 0; + daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED] = 0; for (i=0; i<hash_size; i++) for (cache = hash_table[i], up = &hash_table[i]; cache; cache = tmp) { -#ifdef HAVE_DNSSEC cache_blockdata_free(cache); -#endif + tmp = cache->hash_next; if (cache->flags & (F_HOSTS | F_CONFIG)) { @@ -1059,26 +1228,24 @@ void cache_reload(void) up = &cache->hash_next; } - /* Add CNAMEs to interface_names to the cache */ + /* Add locally-configured CNAMEs to the cache */ for (a = daemon->cnames; a; a = a->next) - for (intr = daemon->int_names; intr; intr = intr->next) - if (a->alias[1] != '*' && - hostname_isequal(a->target, intr->name) && - ((cache = whine_malloc(sizeof(struct crec))))) - { - cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG; - cache->ttd = a->ttl; - cache->name.namep = a->alias; - cache->addr.cname.target.int_name = intr; - cache->addr.cname.uid = SRC_INTERFACE; - cache->uid = next_uid(); - cache_hash(cache); - add_hosts_cname(cache); /* handle chains */ - } - + if (a->alias[1] != '*' && + ((cache = whine_malloc(SIZEOF_POINTER_CREC)))) + { + cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG; + cache->ttd = a->ttl; + cache->name.namep = a->alias; + cache->addr.cname.target.name = a->target; + cache->addr.cname.is_name_ptr = 1; + cache->uid = UID_NONE; + cache_hash(cache); + make_non_terminals(cache); + } + #ifdef HAVE_DNSSEC for (ds = daemon->ds; ds; ds = ds->next) - if ((cache = whine_malloc(sizeof(struct crec))) && + if ((cache = whine_malloc(SIZEOF_POINTER_CREC)) && (cache->addr.ds.keydata = blockdata_alloc(ds->digest, ds->digestlen))) { cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP; @@ -1090,6 +1257,7 @@ void cache_reload(void) cache->addr.ds.digest = ds->digest_type; cache->uid = ds->class; cache_hash(cache); + make_non_terminals(cache); } #endif @@ -1103,24 +1271,23 @@ void cache_reload(void) for (hr = daemon->host_records; hr; hr = hr->next) for (nl = hr->names; nl; nl = nl->next) { - if (hr->addr.s_addr != 0 && - (cache = whine_malloc(sizeof(struct crec)))) + if ((hr->flags & HR_4) && + (cache = whine_malloc(SIZEOF_POINTER_CREC))) { cache->name.namep = nl->name; cache->ttd = hr->ttl; cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4 | F_NAMEP | F_CONFIG; - add_hosts_entry(cache, (struct all_addr *)&hr->addr, INADDRSZ, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz); + add_hosts_entry(cache, (union all_addr *)&hr->addr, INADDRSZ, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz); } -#ifdef HAVE_IPV6 - if (!IN6_IS_ADDR_UNSPECIFIED(&hr->addr6) && - (cache = whine_malloc(sizeof(struct crec)))) + + if ((hr->flags & HR_6) && + (cache = whine_malloc(SIZEOF_POINTER_CREC))) { cache->name.namep = nl->name; cache->ttd = hr->ttl; cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6 | F_NAMEP | F_CONFIG; - add_hosts_entry(cache, (struct all_addr *)&hr->addr6, IN6ADDRSZ, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz); + add_hosts_entry(cache, (union all_addr *)&hr->addr6, IN6ADDRSZ, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz); } -#endif } if (option_bool(OPT_NO_HOSTS) && !daemon->addn_hosts) @@ -1138,7 +1305,40 @@ void cache_reload(void) if (!(ah->flags & AH_INACTIVE)) total_size = read_hostsfile(ah->fname, ah->index, total_size, (struct crec **)daemon->packet, revhashsz); } + + /* Make non-terminal records for all locally-define RRs */ + lrec.flags = F_FORWARD | F_CONFIG | F_NAMEP | F_IMMORTAL; + + for (txt = daemon->txt; txt; txt = txt->next) + { + lrec.name.namep = txt->name; + make_non_terminals(&lrec); + } + for (naptr = daemon->naptr; naptr; naptr = naptr->next) + { + lrec.name.namep = naptr->name; + make_non_terminals(&lrec); + } + + for (mx = daemon->mxnames; mx; mx = mx->next) + { + lrec.name.namep = mx->name; + make_non_terminals(&lrec); + } + + for (intr = daemon->int_names; intr; intr = intr->next) + { + lrec.name.namep = intr->name; + make_non_terminals(&lrec); + } + + for (ptr = daemon->ptr; ptr; ptr = ptr->next) + { + lrec.name.namep = ptr->name; + make_non_terminals(&lrec); + } + #ifdef HAVE_INOTIFY set_dynamic_inotify(AH_HOSTS, total_size, (struct crec **)daemon->packet, revhashsz); #endif @@ -1153,7 +1353,7 @@ struct in_addr a_record_from_hosts(char *name, time_t now) while ((crecp = cache_find_by_name(crecp, name, now, F_IPV4))) if (crecp->flags & F_HOSTS) - return *(struct in_addr *)&crecp->addr; + return crecp->addr.addr4; my_syslog(MS_DHCP | LOG_WARNING, _("No IPv4 address found for %s"), name); @@ -1178,52 +1378,19 @@ void cache_unhash_dhcp(void) up = &cache->hash_next; } -static void add_dhcp_cname(struct crec *target, time_t ttd) -{ - struct crec *aliasc; - struct cname *a; - - for (a = daemon->cnames; a; a = a->next) - if (a->alias[1] != '*' && - hostname_isequal(cache_get_name(target), a->target)) - { - if ((aliasc = dhcp_spare)) - dhcp_spare = dhcp_spare->next; - else /* need new one */ - aliasc = whine_malloc(sizeof(struct crec)); - - if (aliasc) - { - aliasc->flags = F_FORWARD | F_NAMEP | F_DHCP | F_CNAME | F_CONFIG; - if (ttd == 0) - aliasc->flags |= F_IMMORTAL; - else - aliasc->ttd = ttd; - aliasc->name.namep = a->alias; - aliasc->addr.cname.target.cache = target; - aliasc->addr.cname.uid = target->uid; - aliasc->uid = next_uid(); - cache_hash(aliasc); - add_dhcp_cname(aliasc, ttd); - } - } -} - void cache_add_dhcp_entry(char *host_name, int prot, - struct all_addr *host_address, time_t ttd) + union all_addr *host_address, time_t ttd) { struct crec *crec = NULL, *fail_crec = NULL; - unsigned short flags = F_IPV4; + unsigned int flags = F_IPV4; int in_hosts = 0; size_t addrlen = sizeof(struct in_addr); -#ifdef HAVE_IPV6 if (prot == AF_INET6) { flags = F_IPV6; addrlen = sizeof(struct in6_addr); } -#endif inet_ntop(prot, host_address, daemon->addrbuff, ADDRSTRLEN); @@ -1236,14 +1403,14 @@ void cache_add_dhcp_entry(char *host_name, int prot, my_syslog(MS_DHCP | LOG_WARNING, _("%s is a CNAME, not giving it to the DHCP lease of %s"), host_name, daemon->addrbuff); - else if (memcmp(&crec->addr.addr, host_address, addrlen) == 0) + else if (memcmp(&crec->addr, host_address, addrlen) == 0) in_hosts = 1; else fail_crec = crec; } else if (!(crec->flags & F_DHCP)) { - cache_scan_free(host_name, NULL, 0, crec->flags & (flags | F_CNAME | F_FORWARD)); + cache_scan_free(host_name, NULL, C_IN, 0, crec->flags & (flags | F_CNAME | F_FORWARD), NULL, NULL); /* scan_free deletes all addresses associated with name */ break; } @@ -1256,7 +1423,7 @@ void cache_add_dhcp_entry(char *host_name, int prot, /* Name in hosts, address doesn't match */ if (fail_crec) { - inet_ntop(prot, &fail_crec->addr.addr, daemon->namebuff, MAXDNAME); + inet_ntop(prot, &fail_crec->addr, daemon->namebuff, MAXDNAME); my_syslog(MS_DHCP | LOG_WARNING, _("not giving name %s to the DHCP lease of %s because " "the name exists in %s with address %s"), @@ -1265,12 +1432,12 @@ void cache_add_dhcp_entry(char *host_name, int prot, return; } - if ((crec = cache_find_by_addr(NULL, (struct all_addr *)host_address, 0, flags))) + if ((crec = cache_find_by_addr(NULL, (union all_addr *)host_address, 0, flags))) { if (crec->flags & F_NEG) { flags |= F_REVERSE; - cache_scan_free(NULL, (struct all_addr *)host_address, 0, flags); + cache_scan_free(NULL, (union all_addr *)host_address, C_IN, 0, flags, NULL, NULL); } } else @@ -1279,7 +1446,7 @@ void cache_add_dhcp_entry(char *host_name, int prot, if ((crec = dhcp_spare)) dhcp_spare = dhcp_spare->next; else /* need new one */ - crec = whine_malloc(sizeof(struct crec)); + crec = whine_malloc(SIZEOF_POINTER_CREC); if (crec) /* malloc may fail */ { @@ -1288,16 +1455,108 @@ void cache_add_dhcp_entry(char *host_name, int prot, crec->flags |= F_IMMORTAL; else crec->ttd = ttd; - crec->addr.addr = *host_address; + crec->addr = *host_address; crec->name.namep = host_name; - crec->uid = next_uid(); + crec->uid = UID_NONE; cache_hash(crec); - - add_dhcp_cname(crec, ttd); + make_non_terminals(crec); } } #endif +/* Called when we put a local or DHCP name into the cache. + Creates empty cache entries for subnames (ie, + for three.two.one, for two.one and one), without + F_IPV4 or F_IPV6 or F_CNAME set. These convert + NXDOMAIN answers to NoData ones. */ +static void make_non_terminals(struct crec *source) +{ + char *name = cache_get_name(source); + struct crec *crecp, *tmp, **up; + int type = F_HOSTS | F_CONFIG; +#ifdef HAVE_DHCP + if (source->flags & F_DHCP) + type = F_DHCP; +#endif + + /* First delete any empty entries for our new real name. Note that + we only delete empty entries deriving from DHCP for a new DHCP-derived + entry and vice-versa for HOSTS and CONFIG. This ensures that + non-terminals from DHCP go when we reload DHCP and + for HOSTS/CONFIG when we re-read. */ + for (up = hash_bucket(name), crecp = *up; crecp; crecp = tmp) + { + tmp = crecp->hash_next; + + if (!is_outdated_cname_pointer(crecp) && + (crecp->flags & F_FORWARD) && + (crecp->flags & type) && + !(crecp->flags & (F_IPV4 | F_IPV6 | F_CNAME | F_SRV | F_DNSKEY | F_DS)) && + hostname_isequal(name, cache_get_name(crecp))) + { + *up = crecp->hash_next; +#ifdef HAVE_DHCP + if (type & F_DHCP) + { + crecp->next = dhcp_spare; + dhcp_spare = crecp; + } + else +#endif + free(crecp); + break; + } + else + up = &crecp->hash_next; + } + + while ((name = strchr(name, '.'))) + { + name++; + + /* Look for one existing, don't need another */ + for (crecp = *hash_bucket(name); crecp; crecp = crecp->hash_next) + if (!is_outdated_cname_pointer(crecp) && + (crecp->flags & F_FORWARD) && + (crecp->flags & type) && + hostname_isequal(name, cache_get_name(crecp))) + break; + + if (crecp) + { + /* If the new name expires later, transfer that time to + empty non-terminal entry. */ + if (!(crecp->flags & F_IMMORTAL)) + { + if (source->flags & F_IMMORTAL) + crecp->flags |= F_IMMORTAL; + else if (difftime(crecp->ttd, source->ttd) < 0) + crecp->ttd = source->ttd; + } + continue; + } + +#ifdef HAVE_DHCP + if ((source->flags & F_DHCP) && dhcp_spare) + { + crecp = dhcp_spare; + dhcp_spare = dhcp_spare->next; + } + else +#endif + crecp = whine_malloc(SIZEOF_POINTER_CREC); + + if (crecp) + { + crecp->flags = (source->flags | F_NAMEP) & ~(F_IPV4 | F_IPV6 | F_CNAME | F_SRV | F_DNSKEY | F_DS | F_REVERSE); + crecp->ttd = source->ttd; + crecp->name.namep = name; + + cache_hash(crecp); + } + } +} + #ifndef NO_ID int cache_make_stat(struct txt_record *t) { @@ -1319,24 +1578,24 @@ int cache_make_stat(struct txt_record *t) break; case TXT_STAT_INSERTS: - sprintf(buff+1, "%d", cache_inserted); + sprintf(buff+1, "%d", daemon->metrics[METRIC_DNS_CACHE_INSERTED]); break; case TXT_STAT_EVICTIONS: - sprintf(buff+1, "%d", cache_live_freed); + sprintf(buff+1, "%d", daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED]); break; case TXT_STAT_MISSES: - sprintf(buff+1, "%u", daemon->queries_forwarded); + sprintf(buff+1, "%u", daemon->metrics[METRIC_DNS_QUERIES_FORWARDED]); break; case TXT_STAT_HITS: - sprintf(buff+1, "%u", daemon->local_answer); + sprintf(buff+1, "%u", daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]); break; #ifdef HAVE_AUTH case TXT_STAT_AUTH: - sprintf(buff+1, "%u", daemon->auth_answer); + sprintf(buff+1, "%u", daemon->metrics[METRIC_DNS_AUTH_ANSWERED]); break; #endif @@ -1413,19 +1672,17 @@ static char *sanitise(char *name) void dump_cache(time_t now) { struct server *serv, *serv1; - char *t = ""; my_syslog(LOG_INFO, _("time %lu"), (unsigned long)now); my_syslog(LOG_INFO, _("cache size %d, %d/%d cache insertions re-used unexpired cache entries."), - daemon->cachesize, cache_live_freed, cache_inserted); + daemon->cachesize, daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED], daemon->metrics[METRIC_DNS_CACHE_INSERTED]); my_syslog(LOG_INFO, _("queries forwarded %u, queries answered locally %u"), - daemon->queries_forwarded, daemon->local_answer); + daemon->metrics[METRIC_DNS_QUERIES_FORWARDED], daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]); #ifdef HAVE_AUTH - my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->auth_answer); + my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->metrics[METRIC_DNS_AUTH_ANSWERED]); #endif -#ifdef HAVE_DNSSEC + blockdata_report(); -#endif /* sum counts from different records for same server */ for (serv = daemon->servers; serv; serv = serv->next) @@ -1459,6 +1716,7 @@ void dump_cache(time_t now) for (i=0; i<hash_size; i++) for (cache = hash_table[i]; cache; cache = cache->hash_next) { + char *t = " "; char *a = daemon->addrbuff, *p = daemon->namebuff, *n = cache_get_name(cache); *a = 0; if (strlen(n) == 0 && !(cache->flags & F_REVERSE)) @@ -1466,6 +1724,17 @@ void dump_cache(time_t now) p += sprintf(p, "%-30.30s ", sanitise(n)); if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache)) a = sanitise(cache_get_cname_target(cache)); + else if ((cache->flags & F_SRV) && !(cache->flags & F_NEG)) + { + int targetlen = cache->addr.srv.targetlen; + ssize_t len = sprintf(a, "%u %u %u ", cache->addr.srv.priority, + cache->addr.srv.weight, cache->addr.srv.srvport); + + if (targetlen > (40 - len)) + targetlen = 40 - len; + blockdata_retrieve(cache->addr.srv.target, targetlen, a + len); + a[len + targetlen] = 0; + } #ifdef HAVE_DNSSEC else if (cache->flags & F_DS) { @@ -1481,11 +1750,9 @@ void dump_cache(time_t now) { a = daemon->addrbuff; if (cache->flags & F_IPV4) - inet_ntop(AF_INET, &cache->addr.addr, a, ADDRSTRLEN); -#ifdef HAVE_IPV6 + inet_ntop(AF_INET, &cache->addr, a, ADDRSTRLEN); else if (cache->flags & F_IPV6) - inet_ntop(AF_INET6, &cache->addr.addr, a, ADDRSTRLEN); -#endif + inet_ntop(AF_INET6, &cache->addr, a, ADDRSTRLEN); } if (cache->flags & F_IPV4) @@ -1494,6 +1761,8 @@ void dump_cache(time_t now) t = "6"; else if (cache->flags & F_CNAME) t = "C"; + else if (cache->flags & F_SRV) + t = "V"; #ifdef HAVE_DNSSEC else if (cache->flags & F_DS) t = "S"; @@ -1559,9 +1828,13 @@ char *querystr(char *desc, unsigned short type) break; } - len += 3; /* braces, terminator */ - len += strlen(desc); - + if (desc) + { + len += 2; /* braces */ + len += strlen(desc); + } + len++; /* terminator */ + if (!buff || bufflen < len) { if (buff) @@ -1575,16 +1848,26 @@ char *querystr(char *desc, unsigned short type) if (buff) { - if (types) - sprintf(buff, "%s[%s]", desc, types); + if (desc) + { + if (types) + sprintf(buff, "%s[%s]", desc, types); + else + sprintf(buff, "%s[type=%d]", desc, type); + } else - sprintf(buff, "%s[type=%d]", desc, type); + { + if (types) + sprintf(buff, "<%s>", types); + else + sprintf(buff, "type=%d", type); + } } - + return buff ? buff : ""; } -void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) +void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg) { char *source, *dest = daemon->addrbuff; char *verb = "is"; @@ -1597,16 +1880,24 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) if (addr) { if (flags & F_KEYTAG) - sprintf(daemon->addrbuff, arg, addr->addr.log.keytag, addr->addr.log.algo, addr->addr.log.digest); - else + sprintf(daemon->addrbuff, arg, addr->log.keytag, addr->log.algo, addr->log.digest); + else if (flags & F_RCODE) { -#ifdef HAVE_IPV6 - inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6, - addr, daemon->addrbuff, ADDRSTRLEN); -#else - strncpy(daemon->addrbuff, inet_ntoa(addr->addr.addr4), ADDRSTRLEN); -#endif + unsigned int rcode = addr->log.rcode; + + if (rcode == SERVFAIL) + dest = "SERVFAIL"; + else if (rcode == REFUSED) + dest = "REFUSED"; + else if (rcode == NOTIMP) + dest = "not implemented"; + else + sprintf(daemon->addrbuff, "%u", rcode); } + else + inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6, + addr, daemon->addrbuff, ADDRSTRLEN); + } else dest = arg; @@ -1633,6 +1924,8 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) } else if (flags & F_CNAME) dest = "<CNAME>"; + else if (flags & F_SRV) + dest = "<SRV>"; else if (flags & F_RRNAME) dest = arg; diff --git a/src/config.h b/src/config.h index ecefb87..7187ffa 100644 --- a/src/config.h +++ b/src/config.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -40,9 +40,11 @@ #define DHCP_PACKET_MAX 16384 /* hard limit on DHCP packet size */ #define SMALLDNAME 50 /* most domain names are smaller than this */ #define CNAME_CHAIN 10 /* chains longer than this atr dropped for loop protection */ +#define DNSSEC_MIN_TTL 60 /* DNSKEY and DS records in cache last at least this long */ #define HOSTSFILE "/etc/hosts" #define ETHERSFILE "/etc/ethers" -#define DEFLEASE 3600 /* default lease time, 1 hour */ +#define DEFLEASE 3600 /* default DHCPv4 lease time, one hour */ +#define DEFLEASE6 (3600*24) /* default lease time for DHCPv6. One day. */ #define CHUSER "nobody" #define CHGRP "dip" #define TFTP_MAX_CONNECTIONS 50 /* max simultaneous connections */ @@ -50,6 +52,7 @@ #define RANDFILE "/dev/urandom" #define DNSMASQ_SERVICE "uk.org.thekelleys.dnsmasq" /* Default - may be overridden by config */ #define DNSMASQ_PATH "/uk/org/thekelleys/dnsmasq" +#define DNSMASQ_UBUS_NAME "dnsmasq" /* Default - may be overridden by config */ #define AUTH_TTL 600 /* default TTL for auth DNS */ #define SOA_REFRESH 1200 /* SOA refresh default */ #define SOA_RETRY 180 /* SOA retry default */ @@ -94,6 +97,9 @@ HAVE_DBUS support some methods to allow (re)configuration of the upstream DNS servers via DBus. +HAVE_UBUS + define this if you want to link against libubus + HAVE_IDN define this if you want international domain name 2003 support. @@ -117,6 +123,9 @@ HAVE_AUTH HAVE_DNSSEC include DNSSEC validator. +HAVE_DUMPFILE + include code to dump packets to a libpcap-format file for debugging. + HAVE_LOOP include functionality to probe for and remove DNS forwarding loops. @@ -125,17 +134,17 @@ HAVE_INOTIFY NO_ID Don't report *.bind CHAOS info to clients, forward such requests upstream instead. -NO_IPV6 NO_TFTP NO_DHCP NO_DHCP6 NO_SCRIPT NO_LARGEFILE NO_AUTH +NO_DUMPFILE NO_INOTIFY these are available to explicitly disable compile time options which would - otherwise be enabled automatically (HAVE_IPV6, >2Gb file sizes) or - which are enabled by default in the distributed source tree. Building dnsmasq + otherwise be enabled automatically or which are enabled by default + in the distributed source tree. Building dnsmasq with something like "make COPTS=-DNO_SCRIPT" will do the trick. NO_GMP Don't use and link against libgmp, Useful if nettle is built with --enable-mini-gmp. @@ -164,6 +173,7 @@ RESOLVFILE #define HAVE_AUTH #define HAVE_IPSET #define HAVE_LOOP +#define HAVE_DUMPFILE /* Build options which require external libraries. @@ -232,27 +242,13 @@ HAVE_SOCKADDR_SA_LEN defined if struct sockaddr has sa_len field (*BSD) */ -/* Must precede __linux__ since uClinux defines __linux__ too. */ -#if defined(__uClinux__) -#define HAVE_LINUX_NETWORK -#define HAVE_GETOPT_LONG -#undef HAVE_SOCKADDR_SA_LEN -/* Never use fork() on uClinux. Note that this is subtly different from the - --keep-in-foreground option, since it also suppresses forking new - processes for TCP connections and disables the call-a-script on leasechange - system. It's intended for use on MMU-less kernels. */ -#define NO_FORK - -#elif defined(__UCLIBC__) +#if defined(__UCLIBC__) #define HAVE_LINUX_NETWORK #if defined(__UCLIBC_HAS_GNU_GETOPT__) || \ ((__UCLIBC_MAJOR__==0) && (__UCLIBC_MINOR__==9) && (__UCLIBC_SUBLEVEL__<21)) # define HAVE_GETOPT_LONG #endif #undef HAVE_SOCKADDR_SA_LEN -#if !defined(__ARCH_HAS_MMU__) && !defined(__UCLIBC_HAS_MMU__) -# define NO_FORK -#endif #if defined(__UCLIBC_HAS_IPV6__) # ifndef IPV6_V6ONLY # define IPV6_V6ONLY 26 @@ -280,11 +276,16 @@ HAVE_SOCKADDR_SA_LEN #define HAVE_BSD_NETWORK #define HAVE_GETOPT_LONG #define HAVE_SOCKADDR_SA_LEN +#define NO_IPSET /* Define before sys/socket.h is included so we get socklen_t */ #define _BSD_SOCKLEN_T_ /* Select the RFC_3542 version of the IPv6 socket API. Define before netinet6/in6.h is included. */ -#define __APPLE_USE_RFC_3542 +#define __APPLE_USE_RFC_3542 +/* Required for Mojave. */ +#ifndef SOL_TCP +# define SOL_TCP IPPROTO_TCP +#endif #define NO_IPSET #elif defined(__NetBSD__) @@ -300,29 +301,9 @@ HAVE_SOCKADDR_SA_LEN #endif -/* Decide if we're going to support IPv6 */ -/* We assume that systems which don't have IPv6 - headers don't have ntop and pton either */ - -#if defined(INET6_ADDRSTRLEN) && defined(IPV6_V6ONLY) -# define HAVE_IPV6 -# define ADDRSTRLEN INET6_ADDRSTRLEN -#else -# if !defined(INET_ADDRSTRLEN) -# define INET_ADDRSTRLEN 16 /* 4*3 + 3 dots + NULL */ -# endif -# undef HAVE_IPV6 -# define ADDRSTRLEN INET_ADDRSTRLEN -#endif - - /* rules to implement compile-time option dependencies and the NO_XXX flags */ -#ifdef NO_IPV6 -#undef HAVE_IPV6 -#endif - #ifdef NO_TFTP #undef HAVE_TFTP #endif @@ -332,7 +313,7 @@ HAVE_SOCKADDR_SA_LEN #undef HAVE_DHCP6 #endif -#if defined(NO_DHCP6) || !defined(HAVE_IPV6) +#if defined(NO_DHCP6) #undef HAVE_DHCP6 #endif @@ -341,7 +322,7 @@ HAVE_SOCKADDR_SA_LEN #define HAVE_DHCP #endif -#if defined(NO_SCRIPT) || defined(NO_FORK) +#if defined(NO_SCRIPT) #undef HAVE_SCRIPT #undef HAVE_LUASCRIPT #endif @@ -363,6 +344,10 @@ HAVE_SOCKADDR_SA_LEN #undef HAVE_LOOP #endif +#ifdef NO_DUMPFILE +#undef HAVE_DUMPFILE +#endif + #if defined (HAVE_LINUX_NETWORK) && !defined(NO_INOTIFY) #define HAVE_INOTIFY #endif @@ -373,9 +358,6 @@ HAVE_SOCKADDR_SA_LEN #ifdef DNSMASQ_COMPILE_OPTS static char *compile_opts = -#ifndef HAVE_IPV6 -"no-" -#endif "IPv6 " #ifndef HAVE_GETOPT_LONG "no-" @@ -384,13 +366,14 @@ static char *compile_opts = #ifdef HAVE_BROKEN_RTC "no-RTC " #endif -#ifdef NO_FORK -"no-MMU " -#endif #ifndef HAVE_DBUS "no-" #endif "DBus " +#ifndef HAVE_UBUS +"no-" +#endif +"UBus " #ifndef LOCALEDIR "no-" #endif @@ -451,8 +434,11 @@ static char *compile_opts = #ifndef HAVE_INOTIFY "no-" #endif -"inotify"; - +"inotify " +#ifndef HAVE_DUMPFILE +"no-" +#endif +"dumpfile"; #endif diff --git a/src/conntrack.c b/src/conntrack.c index 2929f8c..33f5ceb 100644 --- a/src/conntrack.c +++ b/src/conntrack.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -24,7 +24,7 @@ static int gotit = 0; /* yuck */ static int callback(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *data); -int get_incoming_mark(union mysockaddr *peer_addr, struct all_addr *local_addr, int istcp, unsigned int *markp) +int get_incoming_mark(union mysockaddr *peer_addr, union all_addr *local_addr, int istcp, unsigned int *markp) { struct nf_conntrack *ct; struct nfct_handle *h; @@ -36,21 +36,19 @@ int get_incoming_mark(union mysockaddr *peer_addr, struct all_addr *local_addr, nfct_set_attr_u8(ct, ATTR_L4PROTO, istcp ? IPPROTO_TCP : IPPROTO_UDP); nfct_set_attr_u16(ct, ATTR_PORT_DST, htons(daemon->port)); -#ifdef HAVE_IPV6 if (peer_addr->sa.sa_family == AF_INET6) { nfct_set_attr_u8(ct, ATTR_L3PROTO, AF_INET6); nfct_set_attr(ct, ATTR_IPV6_SRC, peer_addr->in6.sin6_addr.s6_addr); nfct_set_attr_u16(ct, ATTR_PORT_SRC, peer_addr->in6.sin6_port); - nfct_set_attr(ct, ATTR_IPV6_DST, local_addr->addr.addr6.s6_addr); + nfct_set_attr(ct, ATTR_IPV6_DST, local_addr->addr6.s6_addr); } else -#endif { nfct_set_attr_u8(ct, ATTR_L3PROTO, AF_INET); nfct_set_attr_u32(ct, ATTR_IPV4_SRC, peer_addr->in.sin_addr.s_addr); nfct_set_attr_u16(ct, ATTR_PORT_SRC, peer_addr->in.sin_port); - nfct_set_attr_u32(ct, ATTR_IPV4_DST, local_addr->addr.addr4.s_addr); + nfct_set_attr_u32(ct, ATTR_IPV4_DST, local_addr->addr4.s_addr); } diff --git a/src/crypto.c b/src/crypto.c index ebb871e..ca63111 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -19,10 +19,12 @@ #ifdef HAVE_DNSSEC #include <nettle/rsa.h> -#include <nettle/dsa.h> #include <nettle/ecdsa.h> #include <nettle/ecc-curve.h> #include <nettle/eddsa.h> +#if NETTLE_VERSION_MAJOR == 3 && NETTLE_VERSION_MINOR >= 6 +# include <nettle/gostdsa.h> +#endif #include <nettle/nettle-meta.h> #include <nettle/bignum.h> @@ -207,8 +209,6 @@ static int dnsmasq_rsa_verify(struct blockdata *key_data, unsigned int key_len, switch (algo) { - case 1: - return nettle_rsa_md5_verify_digest(key, digest, sig_mpz); case 5: case 7: return nettle_rsa_sha1_verify_digest(key, digest, sig_mpz); case 8: @@ -220,50 +220,6 @@ static int dnsmasq_rsa_verify(struct blockdata *key_data, unsigned int key_len, return 0; } -static int dnsmasq_dsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, - unsigned char *digest, size_t digest_len, int algo) -{ - unsigned char *p; - unsigned int t; - - static mpz_t y; - static struct dsa_params *params = NULL; - static struct dsa_signature *sig_struct; - - (void)digest_len; - - if (params == NULL) - { - if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))) || - !(params = whine_malloc(sizeof(struct dsa_params)))) - return 0; - - mpz_init(y); - nettle_dsa_params_init(params); - nettle_dsa_signature_init(sig_struct); - } - - if ((sig_len < 41) || !(p = blockdata_retrieve(key_data, key_len, NULL))) - return 0; - - t = *p++; - - if (key_len < (213 + (t * 24))) - return 0; - - mpz_import(params->q, 20, 1, 1, 0, 0, p); p += 20; - mpz_import(params->p, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8); - mpz_import(params->g, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8); - mpz_import(y, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8); - - mpz_import(sig_struct->r, 20, 1, 1, 0, 0, sig+1); - mpz_import(sig_struct->s, 20, 1, 1, 0, 0, sig+21); - - (void)algo; - - return nettle_dsa_verify(params, y, digest_len, digest, sig_struct); -} - static int dnsmasq_ecdsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, unsigned char *digest, size_t digest_len, int algo) @@ -275,6 +231,10 @@ static int dnsmasq_ecdsa_verify(struct blockdata *key_data, unsigned int key_len static struct ecc_point *key_256 = NULL, *key_384 = NULL; static mpz_t x, y; static struct dsa_signature *sig_struct; +#if NETTLE_VERSION_MAJOR == 3 && NETTLE_VERSION_MINOR < 4 +#define nettle_get_secp_256r1() (&nettle_secp_256r1) +#define nettle_get_secp_384r1() (&nettle_secp_384r1) +#endif if (!sig_struct) { @@ -294,7 +254,7 @@ static int dnsmasq_ecdsa_verify(struct blockdata *key_data, unsigned int key_len if (!(key_256 = whine_malloc(sizeof(struct ecc_point)))) return 0; - nettle_ecc_point_init(key_256, &nettle_secp_256r1); + nettle_ecc_point_init(key_256, nettle_get_secp_256r1()); } key = key_256; @@ -307,7 +267,7 @@ static int dnsmasq_ecdsa_verify(struct blockdata *key_data, unsigned int key_len if (!(key_384 = whine_malloc(sizeof(struct ecc_point)))) return 0; - nettle_ecc_point_init(key_384, &nettle_secp_384r1); + nettle_ecc_point_init(key_384, nettle_get_secp_384r1()); } key = key_384; @@ -334,15 +294,54 @@ static int dnsmasq_ecdsa_verify(struct blockdata *key_data, unsigned int key_len return nettle_ecdsa_verify(key, digest_len, digest, sig_struct); } +#if NETTLE_VERSION_MAJOR == 3 && NETTLE_VERSION_MINOR >= 6 +static int dnsmasq_gostdsa_verify(struct blockdata *key_data, unsigned int key_len, + unsigned char *sig, size_t sig_len, + unsigned char *digest, size_t digest_len, int algo) +{ + unsigned char *p; + + static struct ecc_point *gost_key = NULL; + static mpz_t x, y; + static struct dsa_signature *sig_struct; + + if (algo != 12 || + sig_len != 64 || key_len != 64 || + !(p = blockdata_retrieve(key_data, key_len, NULL))) + return 0; + + if (!sig_struct) + { + if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))) || + !(gost_key = whine_malloc(sizeof(struct ecc_point)))) + return 0; + + nettle_dsa_signature_init(sig_struct); + nettle_ecc_point_init(gost_key, nettle_get_gost_gc256b()); + mpz_init(x); + mpz_init(y); + } + + mpz_import(x, 32 , 1, 1, 0, 0, p); + mpz_import(y, 32 , 1, 1, 0, 0, p + 32); + + if (!ecc_point_set(gost_key, x, y)) + return 0; + + mpz_import(sig_struct->r, 32, 1, 1, 0, 0, sig); + mpz_import(sig_struct->s, 32, 1, 1, 0, 0, sig + 32); + + return nettle_gostdsa_verify(gost_key, digest_len, digest, sig_struct); +} +#endif + static int dnsmasq_eddsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, unsigned char *digest, size_t digest_len, int algo) { unsigned char *p; - if (key_len != ED25519_KEY_SIZE || - sig_len != ED25519_SIGNATURE_SIZE || - digest_len != sizeof(struct null_hash_digest) || + if (digest_len != sizeof(struct null_hash_digest) || !(p = blockdata_retrieve(key_data, key_len, NULL))) return 0; @@ -353,13 +352,27 @@ static int dnsmasq_eddsa_verify(struct blockdata *key_data, unsigned int key_len switch (algo) { case 15: + if (key_len != ED25519_KEY_SIZE || + sig_len != ED25519_SIGNATURE_SIZE) + return 0; + return ed25519_sha512_verify(p, ((struct null_hash_digest *)digest)->len, ((struct null_hash_digest *)digest)->buff, sig); + +#if NETTLE_VERSION_MAJOR == 3 && NETTLE_VERSION_MINOR >= 6 case 16: - /* Ed448 when available */ - return 0; + if (key_len != ED448_KEY_SIZE || + sig_len != ED448_SIGNATURE_SIZE) + return 0; + + return ed448_shake256_verify(p, + ((struct null_hash_digest *)digest)->len, + ((struct null_hash_digest *)digest)->buff, + sig); +#endif + } return 0; @@ -369,19 +382,21 @@ static int (*verify_func(int algo))(struct blockdata *key_data, unsigned int key unsigned char *digest, size_t digest_len, int algo) { - /* Enure at runtime that we have support for this digest */ + /* Ensure at runtime that we have support for this digest */ if (!hash_find(algo_digest_name(algo))) return NULL; /* This switch defines which sig algorithms we support, can't introspect Nettle for that. */ switch (algo) { - case 1: case 5: case 7: case 8: case 10: + case 5: case 7: case 8: case 10: return dnsmasq_rsa_verify; + +#if NETTLE_VERSION_MAJOR == 3 && NETTLE_VERSION_MINOR >= 6 + case 12: + return dnsmasq_gostdsa_verify; +#endif - case 3: case 6: - return dnsmasq_dsa_verify; - case 13: case 14: return dnsmasq_ecdsa_verify; @@ -432,17 +447,17 @@ char *algo_digest_name(int algo) { case 1: return NULL; /* RSA/MD5 - Must Not Implement. RFC 6944 para 2.3. */ case 2: return NULL; /* Diffie-Hellman */ - case 3: return "sha1"; /* DSA/SHA1 */ + case 3: return NULL; ; /* DSA/SHA1 - Must Not Implement. RFC 8624 section 3.1 */ case 5: return "sha1"; /* RSA/SHA1 */ - case 6: return "sha1"; /* DSA-NSEC3-SHA1 */ + case 6: return NULL; /* DSA-NSEC3-SHA1 - Must Not Implement. RFC 8624 section 3.1 */ case 7: return "sha1"; /* RSASHA1-NSEC3-SHA1 */ case 8: return "sha256"; /* RSA/SHA-256 */ case 10: return "sha512"; /* RSA/SHA-512 */ - case 12: return NULL; /* ECC-GOST */ + case 12: return "gosthash94"; /* ECC-GOST */ case 13: return "sha256"; /* ECDSAP256SHA256 */ case 14: return "sha384"; /* ECDSAP384SHA384 */ case 15: return "null_hash"; /* ED25519 */ - case 16: return NULL; /* ED448 */ + case 16: return "null_hash"; /* ED448 */ default: return NULL; } } @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -85,6 +85,9 @@ const char* introspection_xml_template = " <arg name=\"success\" type=\"b\" direction=\"out\"/>\n" " </method>\n" #endif +" <method name=\"GetMetrics\">\n" +" <arg name=\"metrics\" direction=\"out\" type=\"a{su}\"/>\n" +" </method>\n" " </interface>\n" "</node>\n"; @@ -182,9 +185,6 @@ static void dbus_read_servers(DBusMessage *message) } } -#ifndef HAVE_IPV6 - my_syslog(LOG_WARNING, _("attempt to set an IPv6 server address via DBus - no IPv6 support")); -#else if (i == sizeof(struct in6_addr)) { memcpy(&addr.in6.sin6_addr, p, sizeof(struct in6_addr)); @@ -199,7 +199,6 @@ static void dbus_read_servers(DBusMessage *message) source_addr.in6.sin6_port = htons(daemon->query_port); skip = 0; } -#endif } else /* At the end */ @@ -238,7 +237,7 @@ static DBusMessage *dbus_reply_server_loop(DBusMessage *message) for (serv = daemon->servers; serv; serv = serv->next) if (serv->flags & SERV_LOOP) { - prettyprint_addr(&serv->addr, daemon->addrbuff); + (void)prettyprint_addr(&serv->addr, daemon->addrbuff); dbus_message_iter_append_basic (&args_iter, DBUS_TYPE_STRING, &daemon->addrbuff); } @@ -457,7 +456,7 @@ static DBusMessage *dbus_add_lease(DBusMessage* message) int clid_len, hostname_len, hw_len, hw_type; dbus_uint32_t expires, ia_id; dbus_bool_t is_temporary; - struct all_addr addr; + union all_addr addr; time_t now = dnsmasq_time(); unsigned char dhcp_chaddr[DHCP_CHADDR_MAX]; @@ -527,20 +526,20 @@ static DBusMessage *dbus_add_lease(DBusMessage* message) dbus_message_iter_get_basic(&iter, &is_temporary); - if (inet_pton(AF_INET, ipaddr, &addr.addr.addr4)) + if (inet_pton(AF_INET, ipaddr, &addr.addr4)) { if (ia_id != 0 || is_temporary) return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, "ia_id and is_temporary must be zero for IPv4 lease"); - if (!(lease = lease_find_by_addr(addr.addr.addr4))) - lease = lease4_allocate(addr.addr.addr4); + if (!(lease = lease_find_by_addr(addr.addr4))) + lease = lease4_allocate(addr.addr4); } #ifdef HAVE_DHCP6 - else if (inet_pton(AF_INET6, ipaddr, &addr.addr.addr6)) + else if (inet_pton(AF_INET6, ipaddr, &addr.addr6)) { - if (!(lease = lease6_find_by_addr(&addr.addr.addr6, 128, 0))) - lease = lease6_allocate(&addr.addr.addr6, + if (!(lease = lease6_find_by_addr(&addr.addr6, 128, 0))) + lease = lease6_allocate(&addr.addr6, is_temporary ? LEASE_TA : LEASE_NA); lease_set_iaid(lease, ia_id); } @@ -571,7 +570,7 @@ static DBusMessage *dbus_del_lease(DBusMessage* message) DBusMessageIter iter; const char *ipaddr; DBusMessage *reply; - struct all_addr addr; + union all_addr addr; dbus_bool_t ret = 1; time_t now = dnsmasq_time(); @@ -585,11 +584,11 @@ static DBusMessage *dbus_del_lease(DBusMessage* message) dbus_message_iter_get_basic(&iter, &ipaddr); - if (inet_pton(AF_INET, ipaddr, &addr.addr.addr4)) - lease = lease_find_by_addr(addr.addr.addr4); + if (inet_pton(AF_INET, ipaddr, &addr.addr4)) + lease = lease_find_by_addr(addr.addr4); #ifdef HAVE_DHCP6 - else if (inet_pton(AF_INET6, ipaddr, &addr.addr.addr6)) - lease = lease6_find_by_addr(&addr.addr.addr6, 128, 0); + else if (inet_pton(AF_INET6, ipaddr, &addr.addr6)) + lease = lease6_find_by_addr(&addr.addr6, 128, 0); #endif else return dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS, @@ -613,6 +612,30 @@ static DBusMessage *dbus_del_lease(DBusMessage* message) } #endif +static DBusMessage *dbus_get_metrics(DBusMessage* message) +{ + DBusMessage *reply = dbus_message_new_method_return(message); + DBusMessageIter array, dict, iter; + int i; + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{su}", &array); + + for (i = 0; i < __METRIC_MAX; i++) { + const char *key = get_metric_name(i); + dbus_uint32_t value = daemon->metrics[i]; + + dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &dict); + dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &key); + dbus_message_iter_append_basic(&dict, DBUS_TYPE_UINT32, &value); + dbus_message_iter_close_container(&array, &dict); + } + + dbus_message_iter_close_container(&iter, &array); + + return reply; +} + DBusHandlerResult message_handler(DBusConnection *connection, DBusMessage *message, void *user_data) @@ -680,6 +703,10 @@ DBusHandlerResult message_handler(DBusConnection *connection, reply = dbus_del_lease(message); } #endif + else if (strcmp(method, "GetMetrics") == 0) + { + reply = dbus_get_metrics(message); + } else if (strcmp(method, "ClearCache") == 0) clear_cache = 1; else diff --git a/src/dhcp-common.c b/src/dhcp-common.c index d9719d1..eae9886 100644 --- a/src/dhcp-common.c +++ b/src/dhcp-common.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -38,7 +38,7 @@ void dhcp_common_init(void) ssize_t recv_dhcp_packet(int fd, struct msghdr *msg) { - ssize_t sz; + ssize_t sz, new_sz; while (1) { @@ -65,9 +65,18 @@ ssize_t recv_dhcp_packet(int fd, struct msghdr *msg) } } - while ((sz = recvmsg(fd, msg, 0)) == -1 && errno == EINTR); + while ((new_sz = recvmsg(fd, msg, 0)) == -1 && errno == EINTR); + + /* Some kernels seem to ignore MSG_PEEK, and dequeue the packet anyway. + If that happens we get EAGAIN here because the socket is non-blocking. + Use the result of the original testing recvmsg as long as the buffer + was big enough. There's a small race here that may lose the odd packet, + but it's UDP anyway. */ + + if (new_sz == -1 && (errno == EWOULDBLOCK || errno == EAGAIN)) + new_sz = sz; - return (msg->msg_flags & MSG_TRUNC) ? -1 : sz; + return (msg->msg_flags & MSG_TRUNC) ? -1 : new_sz; } struct dhcp_netid *run_tag_if(struct dhcp_netid *tags) @@ -271,35 +280,45 @@ static int is_config_in_context(struct dhcp_context *context, struct dhcp_config { if (!context) /* called via find_config() from lease_update_from_configs() */ return 1; - - if (!(config->flags & (CONFIG_ADDR | CONFIG_ADDR6))) - return 1; #ifdef HAVE_DHCP6 - if ((context->flags & CONTEXT_V6) && (config->flags & CONFIG_WILDCARD)) - return 1; -#endif + if (context->flags & CONTEXT_V6) + { + struct addrlist *addr_list; - for (; context; context = context->current) -#ifdef HAVE_DHCP6 - if (context->flags & CONTEXT_V6) - { - if ((config->flags & CONFIG_ADDR6) && is_same_net6(&config->addr6, &context->start6, context->prefix)) - return 1; - } - else + if (!(config->flags & CONFIG_ADDR6)) + return 1; + + for (; context; context = context->current) + for (addr_list = config->addr6; addr_list; addr_list = addr_list->next) + { + if ((addr_list->flags & ADDRLIST_WILDCARD) && context->prefix == 64) + return 1; + + if (is_same_net6(&addr_list->addr.addr6, &context->start6, context->prefix)) + return 1; + } + } + else #endif - if ((config->flags & CONFIG_ADDR) && is_same_net(config->addr, context->start, context->netmask)) + { + if (!(config->flags & CONFIG_ADDR)) return 1; + + for (; context; context = context->current) + if ((config->flags & CONFIG_ADDR) && is_same_net(config->addr, context->start, context->netmask)) + return 1; + } return 0; } -struct dhcp_config *find_config(struct dhcp_config *configs, - struct dhcp_context *context, - unsigned char *clid, int clid_len, - unsigned char *hwaddr, int hw_len, - int hw_type, char *hostname) +static struct dhcp_config *find_config_match(struct dhcp_config *configs, + struct dhcp_context *context, + unsigned char *clid, int clid_len, + unsigned char *hwaddr, int hw_len, + int hw_type, char *hostname, + struct dhcp_netid *tags, int tag_not_needed) { int count, new; struct dhcp_config *config, *candidate; @@ -311,7 +330,9 @@ struct dhcp_config *find_config(struct dhcp_config *configs, { if (config->clid_len == clid_len && memcmp(config->clid, clid, clid_len) == 0 && - is_config_in_context(context, config)) + is_config_in_context(context, config) && + match_netid(config->filter, tags, tag_not_needed)) + return config; /* dhcpcd prefixes ASCII client IDs by zero which is wrong, but we try and @@ -319,7 +340,8 @@ struct dhcp_config *find_config(struct dhcp_config *configs, see lease_update_from_configs() */ if ((!context || !(context->flags & CONTEXT_V6)) && *clid == 0 && config->clid_len == clid_len-1 && memcmp(config->clid, clid+1, clid_len-1) == 0 && - is_config_in_context(context, config)) + is_config_in_context(context, config) && + match_netid(config->filter, tags, tag_not_needed)) return config; } @@ -327,14 +349,16 @@ struct dhcp_config *find_config(struct dhcp_config *configs, if (hwaddr) for (config = configs; config; config = config->next) if (config_has_mac(config, hwaddr, hw_len, hw_type) && - is_config_in_context(context, config)) + is_config_in_context(context, config) && + match_netid(config->filter, tags, tag_not_needed)) return config; if (hostname && context) for (config = configs; config; config = config->next) if ((config->flags & CONFIG_NAME) && hostname_isequal(config->hostname, hostname) && - is_config_in_context(context, config)) + is_config_in_context(context, config) && + match_netid(config->filter, tags, tag_not_needed)) return config; @@ -343,7 +367,8 @@ struct dhcp_config *find_config(struct dhcp_config *configs, /* use match with fewest wildcard octets */ for (candidate = NULL, count = 0, config = configs; config; config = config->next) - if (is_config_in_context(context, config)) + if (is_config_in_context(context, config) && + match_netid(config->filter, tags, tag_not_needed)) for (conf_addr = config->hwaddr; conf_addr; conf_addr = conf_addr->next) if (conf_addr->wildcard_mask != 0 && conf_addr->hwaddr_len == hw_len && @@ -357,6 +382,21 @@ struct dhcp_config *find_config(struct dhcp_config *configs, return candidate; } +/* Find tagged configs first. */ +struct dhcp_config *find_config(struct dhcp_config *configs, + struct dhcp_context *context, + unsigned char *clid, int clid_len, + unsigned char *hwaddr, int hw_len, + int hw_type, char *hostname, struct dhcp_netid *tags) +{ + struct dhcp_config *ret = find_config_match(configs, context, clid, clid_len, hwaddr, hw_len, hw_type, hostname, tags, 0); + + if (!ret) + ret = find_config_match(configs, context, clid, clid_len, hwaddr, hw_len, hw_type, hostname, tags, 1); + + return ret; +} + void dhcp_update_configs(struct dhcp_config *configs) { /* Some people like to keep all static IP addresses in /etc/hosts. @@ -371,8 +411,14 @@ void dhcp_update_configs(struct dhcp_config *configs) int prot = AF_INET; for (config = configs; config; config = config->next) + { if (config->flags & CONFIG_ADDR_HOSTS) - config->flags &= ~(CONFIG_ADDR | CONFIG_ADDR6 | CONFIG_ADDR_HOSTS); + config->flags &= ~(CONFIG_ADDR | CONFIG_ADDR_HOSTS); +#ifdef HAVE_DHCP6 + if (config->flags & CONFIG_ADDR6_HOSTS) + config->flags &= ~(CONFIG_ADDR6 | CONFIG_ADDR6_HOSTS); +#endif + } #ifdef HAVE_DHCP6 again: @@ -403,30 +449,41 @@ void dhcp_update_configs(struct dhcp_config *configs) crec = cache_find_by_name(crec, config->hostname, 0, cacheflags); if (!crec) continue; /* should be never */ - inet_ntop(prot, &crec->addr.addr, daemon->addrbuff, ADDRSTRLEN); + inet_ntop(prot, &crec->addr, daemon->addrbuff, ADDRSTRLEN); my_syslog(MS_DHCP | LOG_WARNING, _("%s has more than one address in hostsfile, using %s for DHCP"), config->hostname, daemon->addrbuff); } if (prot == AF_INET && - (!(conf_tmp = config_find_by_address(configs, crec->addr.addr.addr.addr4)) || conf_tmp == config)) + (!(conf_tmp = config_find_by_address(configs, crec->addr.addr4)) || conf_tmp == config)) { - config->addr = crec->addr.addr.addr.addr4; + config->addr = crec->addr.addr4; config->flags |= CONFIG_ADDR | CONFIG_ADDR_HOSTS; continue; } #ifdef HAVE_DHCP6 if (prot == AF_INET6 && - (!(conf_tmp = config_find_by_address6(configs, &crec->addr.addr.addr.addr6, 128, 0)) || conf_tmp == config)) + (!(conf_tmp = config_find_by_address6(configs, NULL, 0, &crec->addr.addr6)) || conf_tmp == config)) { - memcpy(&config->addr6, &crec->addr.addr.addr.addr6, IN6ADDRSZ); - config->flags |= CONFIG_ADDR6 | CONFIG_ADDR_HOSTS; + /* host must have exactly one address if comming from /etc/hosts. */ + if (!config->addr6 && (config->addr6 = whine_malloc(sizeof(struct addrlist)))) + { + config->addr6->next = NULL; + config->addr6->flags = 0; + } + + if (config->addr6 && !config->addr6->next && !(config->addr6->flags & (ADDRLIST_WILDCARD|ADDRLIST_PREFIX))) + { + memcpy(&config->addr6->addr.addr6, &crec->addr.addr6, IN6ADDRSZ); + config->flags |= CONFIG_ADDR6 | CONFIG_ADDR6_HOSTS; + } + continue; } #endif - inet_ntop(prot, &crec->addr.addr, daemon->addrbuff, ADDRSTRLEN); + inet_ntop(prot, &crec->addr, daemon->addrbuff, ADDRSTRLEN); my_syslog(MS_DHCP | LOG_WARNING, _("duplicate IP address %s (%s) in dhcp-config directive"), daemon->addrbuff, config->hostname); @@ -485,8 +542,11 @@ char *whichdevice(void) void bindtodevice(char *device, int fd) { + size_t len = strlen(device)+1; + if (len > IFNAMSIZ) + len = IFNAMSIZ; /* only allowed by root. */ - if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, device, IFNAMSIZ) == -1 && + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, device, len) == -1 && errno != EPERM) die(_("failed to set SO_BINDTODEVICE on DHCP socket: %s"), NULL, EC_BADNET); } @@ -556,6 +616,7 @@ static const struct opttab_t { { "nntp-server", 71, OT_ADDR_LIST }, { "irc-server", 74, OT_ADDR_LIST }, { "user-class", 77, 0 }, + { "rapid-commit", 80, 0 }, { "FQDN", 81, OT_INTERNAL }, { "agent-id", 82, OT_INTERNAL }, { "client-arch", 93, 2 | OT_DEC }, @@ -566,6 +627,7 @@ static const struct opttab_t { { "sip-server", 120, 0 }, { "classless-static-route", 121, 0 }, { "vendor-id-encap", 125, 0 }, + { "tftp-server-address", 150, OT_ADDR_LIST }, { "server-ip-address", 255, OT_ADDR_LIST }, /* special, internal only, sets siaddr */ { NULL, 0, 0 } }; @@ -596,7 +658,7 @@ static const struct opttab_t opttab6[] = { { "sntp-server", 31, OT_ADDR_LIST }, { "information-refresh-time", 32, OT_TIME }, { "FQDN", 39, OT_INTERNAL | OT_RFC1035_NAME }, - { "ntp-server", 56, 0 }, + { "ntp-server", 56, 0 /* OT_ADDR_LIST | OT_RFC1035_NAME */ }, { "bootfile-url", 59, OT_NAME }, { "bootfile-param", 60, OT_CSTRING }, { NULL, 0, 0 } @@ -689,7 +751,7 @@ char *option_string(int prot, unsigned int opt, unsigned char *val, int opt_len, if (ot[o].size & OT_ADDR_LIST) { - struct all_addr addr; + union all_addr addr; int addr_len = INADDRSZ; #ifdef HAVE_DHCP6 diff --git a/src/dhcp-protocol.h b/src/dhcp-protocol.h index a4a3535..e9007c8 100644 --- a/src/dhcp-protocol.h +++ b/src/dhcp-protocol.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -54,6 +54,7 @@ #define OPTION_SNAME 66 #define OPTION_FILENAME 67 #define OPTION_USER_CLASS 77 +#define OPTION_RAPID_COMMIT 80 #define OPTION_CLIENT_FQDN 81 #define OPTION_AGENT_ID 82 #define OPTION_ARCH 93 @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -232,7 +232,7 @@ void dhcp_packet(time_t now, int pxe_fd) #ifdef HAVE_LINUX_NETWORK /* ARP fiddling uses original interface even if we pretend to use a different one. */ - strncpy(arp_req.arp_dev, ifr.ifr_name, 16); + safe_strncpy(arp_req.arp_dev, ifr.ifr_name, sizeof(arp_req.arp_dev)); #endif /* If the interface on which the DHCP request was received is an @@ -255,7 +255,7 @@ void dhcp_packet(time_t now, int pxe_fd) } else { - strncpy(ifr.ifr_name, bridge->iface, IF_NAMESIZE); + safe_strncpy(ifr.ifr_name, bridge->iface, sizeof(ifr.ifr_name)); break; } } @@ -279,7 +279,7 @@ void dhcp_packet(time_t now, int pxe_fd) is_relay_reply = 1; iov.iov_len = sz; #ifdef HAVE_LINUX_NETWORK - strncpy(arp_req.arp_dev, ifr.ifr_name, 16); + safe_strncpy(arp_req.arp_dev, ifr.ifr_name, sizeof(arp_req.arp_dev)); #endif } else @@ -310,7 +310,7 @@ void dhcp_packet(time_t now, int pxe_fd) parm.relay_local.s_addr = 0; parm.ind = iface_index; - if (!iface_check(AF_INET, (struct all_addr *)&iface_addr, ifr.ifr_name, NULL)) + if (!iface_check(AF_INET, (union all_addr *)&iface_addr, ifr.ifr_name, NULL)) { /* If we failed to match the primary address of the interface, see if we've got a --listen-address for a secondary */ @@ -401,7 +401,8 @@ void dhcp_packet(time_t now, int pxe_fd) pkt = (struct in_pktinfo *)CMSG_DATA(cmptr); pkt->ipi_ifindex = rcvd_iface_index; pkt->ipi_spec_dst.s_addr = 0; - msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + msg.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo)); + cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); cmptr->cmsg_level = IPPROTO_IP; cmptr->cmsg_type = IP_PKTINFO; @@ -507,33 +508,83 @@ static int check_listen_addrs(struct in_addr local, int if_index, char *label, Note that the current chain may be superseded later for configured hosts or those coming via gateways. */ -static int complete_context(struct in_addr local, int if_index, char *label, - struct in_addr netmask, struct in_addr broadcast, void *vparam) +static void guess_range_netmask(struct in_addr addr, struct in_addr netmask) { struct dhcp_context *context; - struct dhcp_relay *relay; - struct iface_param *param = vparam; - (void)label; - for (context = daemon->dhcp; context; context = context->next) - { - if (!(context->flags & CONTEXT_NETMASK) && - (is_same_net(local, context->start, netmask) || - is_same_net(local, context->end, netmask))) + if (!(context->flags & CONTEXT_NETMASK) && + (is_same_net(addr, context->start, netmask) || + is_same_net(addr, context->end, netmask))) { if (context->netmask.s_addr != netmask.s_addr && - !(is_same_net(local, context->start, netmask) && - is_same_net(local, context->end, netmask))) + !(is_same_net(addr, context->start, netmask) && + is_same_net(addr, context->end, netmask))) { strcpy(daemon->dhcp_buff, inet_ntoa(context->start)); strcpy(daemon->dhcp_buff2, inet_ntoa(context->end)); my_syslog(MS_DHCP | LOG_WARNING, _("DHCP range %s -- %s is not consistent with netmask %s"), daemon->dhcp_buff, daemon->dhcp_buff2, inet_ntoa(netmask)); } - context->netmask = netmask; + context->netmask = netmask; } +} + +static int complete_context(struct in_addr local, int if_index, char *label, + struct in_addr netmask, struct in_addr broadcast, void *vparam) +{ + struct dhcp_context *context; + struct dhcp_relay *relay; + struct iface_param *param = vparam; + struct shared_network *share; + + (void)label; + + for (share = daemon->shared_networks; share; share = share->next) + { +#ifdef HAVE_DHCP6 + if (share->shared_addr.s_addr == 0) + continue; +#endif + + if (share->if_index != 0) + { + if (share->if_index != if_index) + continue; + } + else + { + if (share->match_addr.s_addr != local.s_addr) + continue; + } + + for (context = daemon->dhcp; context; context = context->next) + { + if (context->netmask.s_addr != 0 && + is_same_net(share->shared_addr, context->start, context->netmask) && + is_same_net(share->shared_addr, context->end, context->netmask)) + { + /* link it onto the current chain if we've not seen it before */ + if (context->current == context) + { + /* For a shared network, we have no way to guess what the default route should be. */ + context->router.s_addr = 0; + context->local = local; /* Use configured address for Server Identifier */ + context->current = param->current; + param->current = context; + } + + if (!(context->flags & CONTEXT_BRDCAST)) + context->broadcast.s_addr = context->start.s_addr | ~context->netmask.s_addr; + } + } + } + + guess_range_netmask(local, netmask); + + for (context = daemon->dhcp; context; context = context->next) + { if (context->netmask.s_addr != 0 && is_same_net(local, context->start, context->netmask) && is_same_net(local, context->end, context->netmask)) @@ -558,7 +609,7 @@ static int complete_context(struct in_addr local, int if_index, char *label, } for (relay = daemon->relay4; relay; relay = relay->next) - if (if_index == param->ind && relay->local.addr.addr4.s_addr == local.s_addr && relay->current == relay && + if (if_index == param->ind && relay->local.addr4.s_addr == local.s_addr && relay->current == relay && (param->relay_local.s_addr == 0 || param->relay_local.s_addr == local.s_addr)) { relay->current = param->relay; @@ -678,7 +729,7 @@ struct ping_result *do_icmp_ping(time_t now, struct in_addr addr, unsigned int h if ((count >= max) || option_bool(OPT_NO_PING) || loopback) { /* overloaded, or configured not to check, loopback interface, return "not in use" */ - dummy.hash = 0; + dummy.hash = hash; return &dummy; } else if (icmp_ping(addr)) @@ -765,24 +816,33 @@ int address_allocate(struct dhcp_context *context, (!IN_CLASSC(ntohl(addr.s_addr)) || ((ntohl(addr.s_addr) & 0xff) != 0xff && ((ntohl(addr.s_addr) & 0xff) != 0x0)))) { - struct ping_result *r; - - if ((r = do_icmp_ping(now, addr, j, loopback))) - { - /* consec-ip mode: we offered this address for another client - (different hash) recently, don't offer it to this one. */ - if (!option_bool(OPT_CONSEC_ADDR) || r->hash == j) - { - *addrp = addr; - return 1; - } - } + /* in consec-ip mode, skip addresses equal to + the number of addresses rejected by clients. This + should avoid the same client being offered the same + address after it has rjected it. */ + if (option_bool(OPT_CONSEC_ADDR) && c->addr_epoch) + c->addr_epoch--; else { - /* address in use: perturb address selection so that we are - less likely to try this address again. */ - if (!option_bool(OPT_CONSEC_ADDR)) - c->addr_epoch++; + struct ping_result *r; + + if ((r = do_icmp_ping(now, addr, j, loopback))) + { + /* consec-ip mode: we offered this address for another client + (different hash) recently, don't offer it to this one. */ + if (!option_bool(OPT_CONSEC_ADDR) || r->hash == j) + { + *addrp = addr; + return 1; + } + } + else + { + /* address in use: perturb address selection so that we are + less likely to try this address again. */ + if (!option_bool(OPT_CONSEC_ADDR)) + c->addr_epoch++; + } } } @@ -971,7 +1031,7 @@ char *host_from_dns(struct in_addr addr) if (daemon->port == 0) return NULL; /* DNS disabled. */ - lookup = cache_find_by_addr(NULL, (struct all_addr *)&addr, 0, F_IPV4); + lookup = cache_find_by_addr(NULL, (union all_addr *)&addr, 0, F_IPV4); if (lookup && (lookup->flags & F_HOSTS)) { @@ -988,8 +1048,7 @@ char *host_from_dns(struct in_addr addr) if (!legal_hostname(hostname)) return NULL; - strncpy(daemon->dhcp_buff, hostname, 256); - daemon->dhcp_buff[255] = 0; + safe_strncpy(daemon->dhcp_buff, hostname, 256); strip_hostname(daemon->dhcp_buff); return daemon->dhcp_buff; @@ -1001,25 +1060,25 @@ char *host_from_dns(struct in_addr addr) static int relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess, size_t sz, int iface_index) { /* ->local is same value for all relays on ->current chain */ - struct all_addr from; + union all_addr from; if (mess->op != BOOTREQUEST) return 0; /* source address == relay address */ - from.addr.addr4 = relay->local.addr.addr4; + from.addr4 = relay->local.addr4; /* already gatewayed ? */ if (mess->giaddr.s_addr) { /* if so check if by us, to stomp on loops. */ - if (mess->giaddr.s_addr == relay->local.addr.addr4.s_addr) + if (mess->giaddr.s_addr == relay->local.addr4.s_addr) return 1; } else { /* plug in our address */ - mess->giaddr.s_addr = relay->local.addr.addr4.s_addr; + mess->giaddr.s_addr = relay->local.addr4.s_addr; } if ((mess->hops++) > 20) @@ -1030,7 +1089,7 @@ static int relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess, union mysockaddr to; to.sa.sa_family = AF_INET; - to.in.sin_addr = relay->server.addr.addr4; + to.in.sin_addr = relay->server.addr4; to.in.sin_port = htons(daemon->dhcp_server_port); send_from(daemon->dhcpfd, 0, (char *)mess, sz, &to, &from, 0); @@ -1038,7 +1097,7 @@ static int relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess, if (option_bool(OPT_LOG_OPTS)) { inet_ntop(AF_INET, &relay->local, daemon->addrbuff, ADDRSTRLEN); - my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay %s -> %s"), daemon->addrbuff, inet_ntoa(relay->server.addr.addr4)); + my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay %s -> %s"), daemon->addrbuff, inet_ntoa(relay->server.addr4)); } /* Save this for replies */ @@ -1058,7 +1117,7 @@ static struct dhcp_relay *relay_reply4(struct dhcp_packet *mess, char *arrival_i for (relay = daemon->relay4; relay; relay = relay->next) { - if (mess->giaddr.s_addr == relay->local.addr.addr4.s_addr) + if (mess->giaddr.s_addr == relay->local.addr4.s_addr) { if (!relay->interface || wildcard_match(relay->interface, arrival_interface)) return relay->iface_index != 0 ? relay : NULL; diff --git a/src/dhcp6-protocol.h b/src/dhcp6-protocol.h index fee5d28..48b027b 100644 --- a/src/dhcp6-protocol.h +++ b/src/dhcp6-protocol.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -59,12 +59,12 @@ #define OPTION6_REMOTE_ID 37 #define OPTION6_SUBSCRIBER_ID 38 #define OPTION6_FQDN 39 +#define OPTION6_NTP_SERVER 56 #define OPTION6_CLIENT_MAC 79 -/* replace this with the real number when allocated. - defining this also enables the relevant code. */ -/* #define OPTION6_PREFIX_CLASS 99 */ - +#define NTP_SUBOPTION_SRV_ADDR 1 +#define NTP_SUBOPTION_MC_ADDR 2 +#define NTP_SUBOPTION_SRV_FQDN 3 #define DHCP6SUCCESS 0 #define DHCP6UNSPEC 1 diff --git a/src/dhcp6.c b/src/dhcp6.c index 0853664..096e2e1 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -135,7 +135,14 @@ void dhcp6_packet(time_t now) if (!indextoname(daemon->dhcp6fd, if_index, ifr.ifr_name)) return; - if ((port = relay_reply6(&from, sz, ifr.ifr_name)) == 0) + if ((port = relay_reply6(&from, sz, ifr.ifr_name)) != 0) + { + from.sin6_port = htons(port); + while (retry_send(sendto(daemon->dhcp6fd, daemon->outpacket.iov_base, + save_counter(-1), 0, (struct sockaddr *)&from, + sizeof(from)))); + } + else { struct dhcp_bridge *bridge, *alias; @@ -233,21 +240,23 @@ void dhcp6_packet(time_t now) port = dhcp6_reply(parm.current, if_index, ifr.ifr_name, &parm.fallback, &parm.ll_addr, &parm.ula_addr, sz, &from.sin6_addr, now); + /* The port in the source address of the original request should + be correct, but at least once client sends from the server port, + so we explicitly send to the client port to a client, and the + server port to a relay. */ + if (port != 0) + { + from.sin6_port = htons(port); + while (retry_send(sendto(daemon->dhcp6fd, daemon->outpacket.iov_base, + save_counter(-1), 0, (struct sockaddr *)&from, + sizeof(from)))); + } + + /* These need to be called _after_ we send DHCPv6 packet, since lease_update_file() + may trigger sending an RA packet, which overwrites our buffer. */ lease_update_file(now); 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, - so we explicitly send to the client port to a client, and the - server port to a relay. */ - if (port != 0) - { - from.sin6_port = htons(port); - while (retry_send(sendto(daemon->dhcp6fd, daemon->outpacket.iov_base, - save_counter(0), 0, (struct sockaddr *)&from, - sizeof(from)))); - } } void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, unsigned int *maclenp, unsigned int *mactypep, time_t now) @@ -299,106 +308,136 @@ static int complete_context6(struct in6_addr *local, int prefix, unsigned int valid, void *vparam) { struct dhcp_context *context; + struct shared_network *share; struct dhcp_relay *relay; struct iface_param *param = vparam; struct iname *tmp; (void)scope; /* warning */ - if (if_index == param->ind) - { - if (IN6_IS_ADDR_LINKLOCAL(local)) - param->ll_addr = *local; - else if (IN6_IS_ADDR_ULA(local)) - param->ula_addr = *local; - - if (!IN6_IS_ADDR_LOOPBACK(local) && - !IN6_IS_ADDR_LINKLOCAL(local) && - !IN6_IS_ADDR_MULTICAST(local)) - { - /* if we have --listen-address config, see if the - arrival interface has a matching address. */ - for (tmp = daemon->if_addrs; tmp; tmp = tmp->next) - if (tmp->addr.sa.sa_family == AF_INET6 && - IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, local)) - param->addr_match = 1; - - /* Determine a globally address on the arrival interface, even - if we have no matching dhcp-context, because we're only - allocating on remote subnets via relays. This - is used as a default for the DNS server option. */ - param->fallback = *local; - - for (context = daemon->dhcp6; context; context = context->next) - { - if ((context->flags & CONTEXT_DHCP) && - !(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) && - prefix <= context->prefix && - is_same_net6(local, &context->start6, context->prefix) && - is_same_net6(local, &context->end6, context->prefix)) - { - - - /* link it onto the current chain if we've not seen it before */ - if (context->current == context) - { - struct dhcp_context *tmp, **up; - - /* use interface values only for constructed contexts */ - if (!(context->flags & CONTEXT_CONSTRUCTED)) - preferred = valid = 0xffffffff; - else if (flags & IFACE_DEPRECATED) - preferred = 0; - - if (context->flags & CONTEXT_DEPRECATE) - preferred = 0; - - /* order chain, longest preferred time first */ - for (up = ¶m->current, tmp = param->current; tmp; tmp = tmp->current) - if (tmp->preferred <= preferred) - break; - else - up = &tmp->current; - - context->current = *up; - *up = context; - context->local6 = *local; - context->preferred = preferred; - context->valid = valid; - } - } - } - } - - for (relay = daemon->relay6; relay; relay = relay->next) - if (IN6_ARE_ADDR_EQUAL(local, &relay->local.addr.addr6) && relay->current == relay && - (IN6_IS_ADDR_UNSPECIFIED(¶m->relay_local) || IN6_ARE_ADDR_EQUAL(local, ¶m->relay_local))) + if (if_index != param->ind) + return 1; + + if (IN6_IS_ADDR_LINKLOCAL(local)) + param->ll_addr = *local; + else if (IN6_IS_ADDR_ULA(local)) + param->ula_addr = *local; + + if (IN6_IS_ADDR_LOOPBACK(local) || + IN6_IS_ADDR_LINKLOCAL(local) || + IN6_IS_ADDR_MULTICAST(local)) + return 1; + + /* if we have --listen-address config, see if the + arrival interface has a matching address. */ + for (tmp = daemon->if_addrs; tmp; tmp = tmp->next) + if (tmp->addr.sa.sa_family == AF_INET6 && + IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, local)) + param->addr_match = 1; + + /* Determine a globally address on the arrival interface, even + if we have no matching dhcp-context, because we're only + allocating on remote subnets via relays. This + is used as a default for the DNS server option. */ + param->fallback = *local; + + for (context = daemon->dhcp6; context; context = context->next) + if ((context->flags & CONTEXT_DHCP) && + !(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) && + prefix <= context->prefix && + context->current == context) + { + if (is_same_net6(local, &context->start6, context->prefix) && + is_same_net6(local, &context->end6, context->prefix)) { - relay->current = param->relay; - param->relay = relay; - param->relay_local = *local; + struct dhcp_context *tmp, **up; + + /* use interface values only for constructed contexts */ + if (!(context->flags & CONTEXT_CONSTRUCTED)) + preferred = valid = 0xffffffff; + else if (flags & IFACE_DEPRECATED) + preferred = 0; + + if (context->flags & CONTEXT_DEPRECATE) + preferred = 0; + + /* order chain, longest preferred time first */ + for (up = ¶m->current, tmp = param->current; tmp; tmp = tmp->current) + if (tmp->preferred <= preferred) + break; + else + up = &tmp->current; + + context->current = *up; + *up = context; + context->local6 = *local; + context->preferred = preferred; + context->valid = valid; } - - } - - return 1; + else + { + for (share = daemon->shared_networks; share; share = share->next) + { + /* IPv4 shared_address - ignore */ + if (share->shared_addr.s_addr != 0) + continue; + + if (share->if_index != 0) + { + if (share->if_index != if_index) + continue; + } + else + { + if (!IN6_ARE_ADDR_EQUAL(&share->match_addr6, local)) + continue; + } + + if (is_same_net6(&share->shared_addr6, &context->start6, context->prefix) && + is_same_net6(&share->shared_addr6, &context->end6, context->prefix)) + { + context->current = param->current; + param->current = context; + context->local6 = *local; + context->preferred = context->flags & CONTEXT_DEPRECATE ? 0 :0xffffffff; + context->valid = 0xffffffff; + } + } + } + } + + for (relay = daemon->relay6; relay; relay = relay->next) + if (IN6_ARE_ADDR_EQUAL(local, &relay->local.addr6) && relay->current == relay && + (IN6_IS_ADDR_UNSPECIFIED(¶m->relay_local) || IN6_ARE_ADDR_EQUAL(local, ¶m->relay_local))) + { + relay->current = param->relay; + param->relay = relay; + param->relay_local = *local; + } + + return 1; } -struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, int prefix, u64 addr) +struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, int prefix, struct in6_addr *addr) { struct dhcp_config *config; for (config = configs; config; config = config->next) - if ((config->flags & CONFIG_ADDR6) && - is_same_net6(&config->addr6, net, prefix) && - (prefix == 128 || addr6part(&config->addr6) == addr)) - return config; + if (config->flags & CONFIG_ADDR6) + { + struct addrlist *addr_list; + + for (addr_list = config->addr6; addr_list; addr_list = addr_list->next) + if ((!net || is_same_net6(&addr_list->addr.addr6, net, prefix) || ((addr_list->flags & ADDRLIST_WILDCARD) && prefix == 64)) && + is_same_net6(&addr_list->addr.addr6, addr, (addr_list->flags & ADDRLIST_PREFIX) ? addr_list->prefixlen : 128)) + return config; + } return NULL; } struct dhcp_context *address6_allocate(struct dhcp_context *context, unsigned char *clid, int clid_len, int temp_addr, - int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans) + unsigned int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans) { /* Find a free address: exclude anything in use and anything allocated to a particular hwaddr/clientid/hostname in our configuration. @@ -431,8 +470,15 @@ struct dhcp_context *address6_allocate(struct dhcp_context *context, unsigned c else { if (!temp_addr && option_bool(OPT_CONSEC_ADDR)) - /* seed is largest extant lease addr in this context */ - start = lease_find_max_addr6(c) + serial; + { + /* seed is largest extant lease addr in this context, + skip addresses equal to the number of addresses rejected + by clients. This should avoid the same client being offered the same + address after it has rjected it. */ + start = lease_find_max_addr6(c) + 1 + serial + c->addr_epoch; + if (c->addr_epoch) + c->addr_epoch--; + } else { u64 range = 1 + addr6part(&c->end6) - addr6part(&c->start6); @@ -453,16 +499,15 @@ struct dhcp_context *address6_allocate(struct dhcp_context *context, unsigned c for (d = context; d; d = d->current) if (addr == addr6part(&d->local6)) break; + + *ans = c->start6; + setaddr6part (ans, addr); if (!d && !lease6_find_by_addr(&c->start6, c->prefix, addr) && - !config_find_by_address6(daemon->dhcp_conf, &c->start6, c->prefix, addr)) - { - *ans = c->start6; - setaddr6part (ans, addr); - return c; - } - + !config_find_by_address6(daemon->dhcp_conf, &c->start6, c->prefix, ans)) + return c; + addr++; if (addr == addr6part(&c->end6) + 1) @@ -516,27 +561,6 @@ struct dhcp_context *address6_valid(struct dhcp_context *context, return NULL; } -int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr) -{ - if (!config || !(config->flags & CONFIG_ADDR6)) - return 0; - - if ((config->flags & CONFIG_WILDCARD) && context->prefix == 64) - { - *addr = context->start6; - setaddr6part(addr, addr6part(&config->addr6)); - return 1; - } - - if (is_same_net6(&context->start6, &config->addr6, context->prefix)) - { - *addr = config->addr6; - return 1; - } - - return 0; -} - void make_duid(time_t now) { (void)now; @@ -617,7 +641,8 @@ static int construct_worker(struct in6_addr *local, int prefix, char ifrn_name[IFNAMSIZ]; struct in6_addr start6, end6; struct dhcp_context *template, *context; - + struct iname *tmp; + (void)scope; (void)flags; (void)valid; @@ -636,17 +661,30 @@ static int construct_worker(struct in6_addr *local, int prefix, if (flags & IFACE_DEPRECATED) return 1; - if (!indextoname(daemon->icmp6fd, if_index, ifrn_name)) - return 0; + /* Ignore interfaces where we're not doing RA/DHCP6 */ + if (!indextoname(daemon->icmp6fd, if_index, ifrn_name) || + !iface_check(AF_LOCAL, NULL, ifrn_name, NULL)) + return 1; + for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) + if (tmp->name && wildcard_match(tmp->name, ifrn_name)) + return 1; + for (template = daemon->dhcp6; template; template = template->next) - if (!(template->flags & CONTEXT_TEMPLATE)) + if (!(template->flags & (CONTEXT_TEMPLATE | CONTEXT_CONSTRUCTED))) { /* non-template entries, just fill in interface and local addresses */ if (prefix <= template->prefix && is_same_net6(local, &template->start6, template->prefix) && is_same_net6(local, &template->end6, template->prefix)) { + /* First time found, do fast RA. */ + if (template->if_index == 0) + { + ra_start_unsolicited(param->now, template); + param->newone = 1; + } + template->if_index = if_index; template->local6 = *local; } @@ -661,24 +699,33 @@ static int construct_worker(struct in6_addr *local, int prefix, setaddr6part(&end6, addr6part(&template->end6)); for (context = daemon->dhcp6; context; context = context->next) - if ((context->flags & CONTEXT_CONSTRUCTED) && + if (!(context->flags & CONTEXT_TEMPLATE) && IN6_ARE_ADDR_EQUAL(&start6, &context->start6) && IN6_ARE_ADDR_EQUAL(&end6, &context->end6)) { - int flags = context->flags; - context->flags &= ~(CONTEXT_GC | CONTEXT_OLD); - if (flags & CONTEXT_OLD) + /* If there's an absolute address context covering this address + then don't construct one as well. */ + if (!(context->flags & CONTEXT_CONSTRUCTED)) + break; + + if (context->if_index == if_index) { - /* address went, now it's back */ - log_context(AF_INET6, context); - /* fast RAs for a while */ - ra_start_unsolicited(param->now, context); - param->newone = 1; - /* Add address to name again */ - if (context->flags & CONTEXT_RA_NAME) - param->newname = 1; + int cflags = context->flags; + context->flags &= ~(CONTEXT_GC | CONTEXT_OLD); + if (cflags & CONTEXT_OLD) + { + /* address went, now it's back, and on the same interface */ + log_context(AF_INET6, context); + /* fast RAs for a while */ + ra_start_unsolicited(param->now, context); + param->newone = 1; + /* Add address to name again */ + if (context->flags & CONTEXT_RA_NAME) + param->newname = 1; + + } + break; } - break; } if (!context && (context = whine_malloc(sizeof (struct dhcp_context)))) diff --git a/src/dns-protocol.h b/src/dns-protocol.h index 4958830..8edb9f3 100644 --- a/src/dns-protocol.h +++ b/src/dns-protocol.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -76,6 +76,7 @@ #define T_AXFR 252 #define T_MAILB 253 #define T_ANY 255 +#define T_CAA 257 #define EDNS0_OPTION_MAC 65001 /* dyndns.org temporary assignment */ #define EDNS0_OPTION_CLIENT_SUBNET 8 /* IANA */ diff --git a/src/dnsmasq.c b/src/dnsmasq.c index ce44809..2306c48 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -52,8 +52,13 @@ int main (int argc, char **argv) #if defined(HAVE_LINUX_NETWORK) cap_user_header_t hdr = NULL; cap_user_data_t data = NULL; + int need_cap_net_admin = 0; + int need_cap_net_raw = 0; + int need_cap_net_bind_service = 0; char *bound_device = NULL; int did_bind = 0; + struct server *serv; + char *netlink_warn; #endif #if defined(HAVE_DHCP) || defined(HAVE_DHCP6) struct dhcp_context *context; @@ -85,11 +90,15 @@ int main (int argc, char **argv) sigaction(SIGPIPE, &sigact, NULL); umask(022); /* known umask, create leases and pid files as 0644 */ - + rand_init(); /* Must precede read_opts() */ read_opts(argc, argv, compile_opts); +#ifdef HAVE_LINUX_NETWORK + daemon->kernel_version = kernel_version(); +#endif + if (daemon->edns_pktsz < PACKETSZ) daemon->edns_pktsz = PACKETSZ; @@ -122,7 +131,7 @@ int main (int argc, char **argv) daemon->workspacename = safe_malloc(MAXDNAME * 2); /* one char flag per possible RR in answer section (may get extended). */ daemon->rr_status_sz = 64; - daemon->rr_status = safe_malloc(daemon->rr_status_sz); + daemon->rr_status = safe_malloc(sizeof(*daemon->rr_status) * daemon->rr_status_sz); } #endif @@ -134,20 +143,18 @@ int main (int argc, char **argv) } #endif - /* Close any file descriptors we inherited apart from std{in|out|err} - - Ensure that at least stdin, stdout and stderr (fd 0, 1, 2) exist, + /* Ensure that at least stdin, stdout and stderr (fd 0, 1, 2) exist, otherwise file descriptors we create can end up being 0, 1, or 2 and then get accidentally closed later when we make 0, 1, and 2 open to /dev/null. Normally we'll be started with 0, 1 and 2 open, but it's not guaranteed. By opening /dev/null three times, we ensure that we're not using those fds for real stuff. */ - for (i = 0; i < max_fd; i++) - if (i != STDOUT_FILENO && i != STDERR_FILENO && i != STDIN_FILENO) - close(i); - else - open("/dev/null", O_RDWR); - + for (i = 0; i < 3; i++) + open("/dev/null", O_RDWR); + + /* Close any file descriptors we inherited apart from std{in|out|err} */ + close_fds(max_fd, -1, -1, -1); + #ifndef HAVE_LINUX_NETWORK # if !(defined(IP_RECVDSTADDR) && defined(IP_RECVIF) && defined(IP_SENDSRCADDR)) if (!option_bool(OPT_NOWILD)) @@ -216,7 +223,7 @@ int main (int argc, char **argv) #endif #ifndef HAVE_AUTH - if (daemon->authserver) + if (daemon->auth_zones) die(_("authoritative DNS not available: set HAVE_AUTH in src/config.h"), NULL, EC_BADCONF); #endif @@ -225,18 +232,30 @@ int main (int argc, char **argv) die(_("loop detection not available: set HAVE_LOOP in src/config.h"), NULL, EC_BADCONF); #endif +#ifndef HAVE_UBUS + if (option_bool(OPT_UBUS)) + die(_("Ubus not available: set HAVE_UBUS in src/config.h"), NULL, EC_BADCONF); +#endif + if (daemon->max_port < daemon->min_port) die(_("max_port cannot be smaller than min_port"), NULL, EC_BADCONF); now = dnsmasq_time(); - /* Create a serial at startup if not configured. */ - if (daemon->authinterface && daemon->soa_sn == 0) + if (daemon->auth_zones) + { + if (!daemon->authserver) + die(_("--auth-server required when an auth zone is defined."), NULL, EC_BADCONF); + + /* Create a serial at startup if not configured. */ #ifdef HAVE_BROKEN_RTC - die(_("zone serial must be configured in --auth-soa"), NULL, EC_BADCONF); + if (daemon->soa_sn == 0) + die(_("zone serial must be configured in --auth-soa"), NULL, EC_BADCONF); #else - daemon->soa_sn = now; + if (daemon->soa_sn == 0) + daemon->soa_sn = now; #endif + } #ifdef HAVE_DHCP6 if (daemon->dhcp6) @@ -273,11 +292,24 @@ int main (int argc, char **argv) } if (daemon->dhcp || daemon->relay4) - dhcp_init(); + { + dhcp_init(); +# ifdef HAVE_LINUX_NETWORK + if (!option_bool(OPT_NO_PING)) + need_cap_net_raw = 1; + need_cap_net_admin = 1; +# endif + } # ifdef HAVE_DHCP6 if (daemon->doing_ra || daemon->doing_dhcp6 || daemon->relay6) - ra_init(now); + { + ra_init(now); +# ifdef HAVE_LINUX_NETWORK + need_cap_net_raw = 1; + need_cap_net_admin = 1; +# endif + } if (daemon->doing_dhcp6 || daemon->relay6) dhcp6_init(); @@ -287,11 +319,16 @@ int main (int argc, char **argv) #ifdef HAVE_IPSET if (daemon->ipsets) - ipset_init(); + { + ipset_init(); +# ifdef HAVE_LINUX_NETWORK + need_cap_net_admin = 1; +# endif + } #endif #if defined(HAVE_LINUX_NETWORK) - netlink_init(); + netlink_warn = netlink_init(); #elif defined(HAVE_BSD_NETWORK) route_init(); #endif @@ -354,9 +391,7 @@ int main (int argc, char **argv) { cache_init(); -#ifdef HAVE_DNSSEC blockdata_init(); -#endif } #ifdef HAVE_INOTIFY @@ -366,7 +401,16 @@ int main (int argc, char **argv) else daemon->inotifyfd = -1; #endif - + + if (daemon->dump_file) +#ifdef HAVE_DUMPFILE + dump_init(); + else + daemon->dumpfd = -1; +#else + die(_("Packet dumps not available: set HAVE_DUMP in src/config.h"), NULL, EC_BADCONF); +#endif + if (option_bool(OPT_DBUS)) #ifdef HAVE_DBUS { @@ -380,6 +424,16 @@ int main (int argc, char **argv) die(_("DBus not available: set HAVE_DBUS in src/config.h"), NULL, EC_BADCONF); #endif + if (option_bool(OPT_UBUS)) +#ifdef HAVE_UBUS + { + daemon->ubus = NULL; + ubus_init(); + } +#else + die(_("UBus not available: set HAVE_UBUS in src/config.h"), NULL, EC_BADCONF); +#endif + if (daemon->port != 0) pre_allocate_sfds(); @@ -421,28 +475,81 @@ int main (int argc, char **argv) } #if defined(HAVE_LINUX_NETWORK) + /* We keep CAP_NETADMIN (for ARP-injection) and + CAP_NET_RAW (for icmp) if we're doing dhcp, + if we have yet to bind ports because of DAD, + or we're doing it dynamically, we need CAP_NET_BIND_SERVICE. */ + if ((is_dad_listeners() || option_bool(OPT_CLEVERBIND)) && + (option_bool(OPT_TFTP) || (daemon->port != 0 && daemon->port <= 1024))) + need_cap_net_bind_service = 1; + + /* usptream servers which bind to an interface call SO_BINDTODEVICE + for each TCP connection, so need CAP_NET_RAW */ + for (serv = daemon->servers; serv; serv = serv->next) + if (serv->interface[0] != 0) + need_cap_net_raw = 1; + + /* If we're doing Dbus or UBus, the above can be set dynamically, + (as can ports) so always (potentially) needed. */ +#ifdef HAVE_DBUS + if (option_bool(OPT_DBUS)) + { + need_cap_net_bind_service = 1; + need_cap_net_raw = 1; + } +#endif + +#ifdef HAVE_UBUS + if (option_bool(OPT_UBUS)) + { + need_cap_net_bind_service = 1; + need_cap_net_raw = 1; + } +#endif + /* determine capability API version here, while we can still call safe_malloc */ - if (ent_pw && ent_pw->pw_uid != 0) + int capsize = 1; /* for header version 1 */ + char *fail = NULL; + + hdr = safe_malloc(sizeof(*hdr)); + + /* find version supported by kernel */ + memset(hdr, 0, sizeof(*hdr)); + capget(hdr, NULL); + + if (hdr->version != LINUX_CAPABILITY_VERSION_1) { - int capsize = 1; /* for header version 1 */ - hdr = safe_malloc(sizeof(*hdr)); - - /* find version supported by kernel */ - memset(hdr, 0, sizeof(*hdr)); - capget(hdr, NULL); - - if (hdr->version != LINUX_CAPABILITY_VERSION_1) - { - /* if unknown version, use largest supported version (3) */ - if (hdr->version != LINUX_CAPABILITY_VERSION_2) - hdr->version = LINUX_CAPABILITY_VERSION_3; - capsize = 2; - } - - data = safe_malloc(sizeof(*data) * capsize); - memset(data, 0, sizeof(*data) * capsize); + /* if unknown version, use largest supported version (3) */ + if (hdr->version != LINUX_CAPABILITY_VERSION_2) + hdr->version = LINUX_CAPABILITY_VERSION_3; + capsize = 2; } + + data = safe_malloc(sizeof(*data) * capsize); + capget(hdr, data); /* Get current values, for verification */ + + if (need_cap_net_admin && !(data->permitted & (1 << CAP_NET_ADMIN))) + fail = "NET_ADMIN"; + else if (need_cap_net_raw && !(data->permitted & (1 << CAP_NET_RAW))) + fail = "NET_RAW"; + else if (need_cap_net_bind_service && !(data->permitted & (1 << CAP_NET_BIND_SERVICE))) + fail = "NET_BIND_SERVICE"; + + if (fail) + die(_("process is missing required capability %s"), fail, EC_MISC); + + /* Now set bitmaps to set caps after daemonising */ + memset(data, 0, sizeof(*data) * capsize); + + if (need_cap_net_admin) + data->effective |= (1 << CAP_NET_ADMIN); + if (need_cap_net_raw) + data->effective |= (1 << CAP_NET_RAW); + if (need_cap_net_bind_service) + data->effective |= (1 << CAP_NET_BIND_SERVICE); + + data->permitted = data->effective; #endif /* Use a pipe to carry signals and other events back to the event loop @@ -464,7 +571,6 @@ int main (int argc, char **argv) if (chdir("/") != 0) die(_("cannot chdir to filesystem root: %s"), NULL, EC_MISC); -#ifndef NO_FORK if (!option_bool(OPT_NO_FORK)) { pid_t pid; @@ -483,7 +589,7 @@ int main (int argc, char **argv) char *msg; /* close our copy of write-end */ - while (retry_send(close(err_pipe[1]))); + close(err_pipe[1]); /* check for errors after the fork */ if (read_event(err_pipe[0], &ev, &msg)) @@ -492,7 +598,7 @@ int main (int argc, char **argv) _exit(EC_GOOD); } - while (retry_send(close(err_pipe[0]))); + close(err_pipe[0]); /* NO calls to die() from here on. */ @@ -504,7 +610,6 @@ int main (int argc, char **argv) if (pid != 0) _exit(0); } -#endif /* write pidfile _after_ forking ! */ if (daemon->runfile) @@ -555,8 +660,7 @@ int main (int argc, char **argv) err = 1; else { - while (retry_send(close(fd))); - if (errno != 0) + if (close(fd) == -1) err = 1; } } @@ -609,18 +713,9 @@ int main (int argc, char **argv) if (ent_pw && ent_pw->pw_uid != 0) { #if defined(HAVE_LINUX_NETWORK) - /* On linux, we keep CAP_NETADMIN (for ARP-injection) and - CAP_NET_RAW (for icmp) if we're doing dhcp. If we have yet to bind - ports because of DAD, or we're doing it dynamically, - we need CAP_NET_BIND_SERVICE too. */ - if (is_dad_listeners() || option_bool(OPT_CLEVERBIND)) - data->effective = data->permitted = data->inheritable = - (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | - (1 << CAP_SETUID) | (1 << CAP_NET_BIND_SERVICE); - else - data->effective = data->permitted = data->inheritable = - (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_SETUID); - + /* Need to be able to drop root. */ + data->effective |= (1 << CAP_SETUID); + data->permitted |= (1 << CAP_SETUID); /* Tell kernel to not clear capabilities when dropping root */ if (capset(hdr, data) == -1 || prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) bad_capabilities = errno; @@ -661,15 +756,10 @@ int main (int argc, char **argv) } #ifdef HAVE_LINUX_NETWORK - if (is_dad_listeners() || option_bool(OPT_CLEVERBIND)) - data->effective = data->permitted = - (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_NET_BIND_SERVICE); - else - data->effective = data->permitted = - (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW); - data->inheritable = 0; + data->effective &= ~(1 << CAP_SETUID); + data->permitted &= ~(1 << CAP_SETUID); - /* lose the setuid and setgid capabilities */ + /* lose the setuid capability */ if (capset(hdr, data) == -1) { send_event(err_pipe[1], EVENT_CAP_ERR, errno, NULL); @@ -731,7 +821,11 @@ int main (int argc, char **argv) else { if (daemon->cachesize != 0) - my_syslog(LOG_INFO, _("started, version %s cachesize %d"), VERSION, daemon->cachesize); + { + my_syslog(LOG_INFO, _("started, version %s cachesize %d"), VERSION, daemon->cachesize); + if (daemon->cachesize > 10000) + my_syslog(LOG_WARNING, _("cache size greater than 10000 may cause performance issues, and is unlikely to be useful.")); + } else my_syslog(LOG_INFO, _("started, version %s cache disabled"), VERSION); @@ -754,11 +848,22 @@ int main (int argc, char **argv) } #endif +#ifdef HAVE_UBUS + if (option_bool(OPT_UBUS)) + { + if (daemon->ubus) + my_syslog(LOG_INFO, _("UBus support enabled: connected to system bus")); + else + my_syslog(LOG_INFO, _("UBus support enabled: bus connection pending")); + } +#endif + #ifdef HAVE_DNSSEC if (option_bool(OPT_DNSSEC_VALID)) { int rc; - + struct ds_config *ds; + /* Delay creating the timestamp file until here, after we've changed user, so that it has the correct owner to allow updating the mtime later. This means we have to report fatal errors via the pipe. */ @@ -768,7 +873,10 @@ int main (int argc, char **argv) _exit(0); } - my_syslog(LOG_INFO, _("DNSSEC validation enabled")); + if (option_bool(OPT_DNSSEC_IGN_NS)) + my_syslog(LOG_INFO, _("DNSSEC validation enabled but all unsigned answers are trusted")); + else + my_syslog(LOG_INFO, _("DNSSEC validation enabled")); daemon->dnssec_no_time_check = option_bool(OPT_DNSSEC_TIME); if (option_bool(OPT_DNSSEC_TIME) && !daemon->back_to_the_future) @@ -776,6 +884,10 @@ int main (int argc, char **argv) if (rc == 1) my_syslog(LOG_INFO, _("DNSSEC signature timestamps not checked until system time valid")); + + for (ds = daemon->ds; ds; ds = ds->next) + my_syslog(LOG_INFO, _("configured with trust anchor for %s keytag %u"), + ds->name[0] == 0 ? "<root>" : ds->name, ds->keytag); } #endif @@ -835,6 +947,9 @@ int main (int argc, char **argv) # ifdef HAVE_LINUX_NETWORK if (did_bind) my_syslog(MS_DHCP | LOG_INFO, _("DHCP, sockets bound exclusively to interface %s"), bound_device); + + if (netlink_warn) + my_syslog(LOG_WARNING, netlink_warn); # endif /* after dhcp_construct_contexts */ @@ -847,10 +962,11 @@ int main (int argc, char **argv) { struct tftp_prefix *p; - my_syslog(MS_TFTP | LOG_INFO, "TFTP %s%s %s", + my_syslog(MS_TFTP | LOG_INFO, "TFTP %s%s %s %s", daemon->tftp_prefix ? _("root is ") : _("enabled"), - daemon->tftp_prefix ? daemon->tftp_prefix: "", - option_bool(OPT_TFTP_SECURE) ? _("secure mode") : ""); + daemon->tftp_prefix ? daemon->tftp_prefix : "", + option_bool(OPT_TFTP_SECURE) ? _("secure mode") : "", + option_bool(OPT_SINGLE_PORT) ? _("single port mode") : ""); if (tftp_prefix_missing) my_syslog(MS_TFTP | LOG_WARNING, _("warning: %s inaccessible"), daemon->tftp_prefix); @@ -868,7 +984,7 @@ int main (int argc, char **argv) if (max_fd < 0) max_fd = 5; - else if (max_fd < 100) + else if (max_fd < 100 && !option_bool(OPT_SINGLE_PORT)) max_fd = max_fd/2; else max_fd = max_fd - 20; @@ -891,12 +1007,16 @@ int main (int argc, char **argv) /* finished start-up - release original process */ if (err_pipe[1] != -1) - while (retry_send(close(err_pipe[1]))); + close(err_pipe[1]); if (daemon->port != 0) check_servers(); pid = getpid(); + + daemon->pipe_to_parent = -1; + for (i = 0; i < MAX_PROCS; i++) + daemon->tcp_pipes[i] = -1; #ifdef HAVE_INOTIFY /* Using inotify, have to select a resolv file at startup */ @@ -926,8 +1046,13 @@ int main (int argc, char **argv) #ifdef HAVE_DBUS set_dbus_listeners(); -#endif - +#endif + +#ifdef HAVE_UBUS + if (option_bool(OPT_UBUS)) + set_ubus_listeners(); +#endif + #ifdef HAVE_DHCP if (daemon->dhcp || daemon->relay4) { @@ -989,7 +1114,7 @@ int main (int argc, char **argv) #endif - /* must do this just before select(), when we know no + /* must do this just before do_poll(), when we know no more calls to my_syslog() can occur */ set_log_writer(); @@ -1057,7 +1182,20 @@ int main (int argc, char **argv) } check_dbus_listeners(); #endif - + +#ifdef HAVE_UBUS + if (option_bool(OPT_UBUS)) + { + /* if we didn't create a UBus connection, retry now. */ + if (!daemon->ubus) + { + ubus_init(); + } + + check_ubus_listeners(); + } +#endif + check_dns_listeners(now); #ifdef HAVE_TFTP @@ -1403,7 +1541,7 @@ static void async_event(int pipe, time_t now) do { helper_write(); } while (!helper_buf_empty() || do_script_run(now)); - while (retry_send(close(daemon->helperfd))); + close(daemon->helperfd); } #endif @@ -1421,6 +1559,11 @@ static void async_event(int pipe, time_t now) if (daemon->runfile) unlink(daemon->runfile); + +#ifdef HAVE_DUMPFILE + if (daemon->dumpfd != -1) + close(daemon->dumpfd); +#endif my_syslog(LOG_INFO, _("exiting on receipt of SIGTERM")); flush_log(); @@ -1534,16 +1677,17 @@ static int set_dns_listeners(time_t now) #ifdef HAVE_TFTP int tftp = 0; struct tftp_transfer *transfer; - for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next) - { - tftp++; - poll_listen(transfer->sockfd, POLLIN); - } + if (!option_bool(OPT_SINGLE_PORT)) + for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next) + { + tftp++; + poll_listen(transfer->sockfd, POLLIN); + } #endif /* will we be able to get memory? */ if (daemon->port != 0) - get_new_frec(now, &wait, 0); + get_new_frec(now, &wait, NULL); for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) poll_listen(serverfdp->fd, POLLIN); @@ -1563,19 +1707,25 @@ static int set_dns_listeners(time_t now) we don't need to explicitly arrange to wake up here */ if (listener->tcpfd != -1) for (i = 0; i < MAX_PROCS; i++) - if (daemon->tcp_pids[i] == 0) + if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1) { poll_listen(listener->tcpfd, POLLIN); break; } #ifdef HAVE_TFTP + /* tftp == 0 in single-port mode. */ if (tftp <= daemon->tftp_max && listener->tftpfd != -1) poll_listen(listener->tftpfd, POLLIN); #endif } + if (!option_bool(OPT_DEBUG)) + for (i = 0; i < MAX_PROCS; i++) + if (daemon->tcp_pipes[i] != -1) + poll_listen(daemon->tcp_pipes[i], POLLIN); + return wait; } @@ -1584,7 +1734,8 @@ static void check_dns_listeners(time_t now) struct serverfd *serverfdp; struct listener *listener; int i; - + int pipefd[2]; + for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) if (poll_check(serverfdp->fd, POLLIN)) reply_query(serverfdp->fd, serverfdp->source_addr.sa.sa_family, now); @@ -1594,7 +1745,24 @@ static void check_dns_listeners(time_t now) if (daemon->randomsocks[i].refcount != 0 && poll_check(daemon->randomsocks[i].fd, POLLIN)) reply_query(daemon->randomsocks[i].fd, daemon->randomsocks[i].family, now); - + + /* Races. The child process can die before we read all of the data from the + pipe, or vice versa. Therefore send tcp_pids to zero when we wait() the + process, and tcp_pipes to -1 and close the FD when we read the last + of the data - indicated by cache_recv_insert returning zero. + The order of these events is indeterminate, and both are needed + to free the process slot. Once the child process has gone, poll() + returns POLLHUP, not POLLIN, so have to check for both here. */ + if (!option_bool(OPT_DEBUG)) + for (i = 0; i < MAX_PROCS; i++) + if (daemon->tcp_pipes[i] != -1 && + poll_check(daemon->tcp_pipes[i], POLLIN | POLLHUP) && + !cache_recv_insert(now, daemon->tcp_pipes[i])) + { + close(daemon->tcp_pipes[i]); + daemon->tcp_pipes[i] = -1; + } + for (listener = daemon->listeners; listener; listener = listener->next) { if (listener->fd != -1 && poll_check(listener->fd, POLLIN)) @@ -1620,7 +1788,7 @@ static void check_dns_listeners(time_t now) if (getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) == -1) { - while (retry_send(close(confd))); + close(confd); continue; } @@ -1648,15 +1816,16 @@ static void check_dns_listeners(time_t now) if ((if_index = tcp_interface(confd, tcp_addr.sa.sa_family)) != 0 && indextoname(listener->tcpfd, if_index, intr_name)) { - struct all_addr addr; - addr.addr.addr4 = tcp_addr.in.sin_addr; -#ifdef HAVE_IPV6 + union all_addr addr; + if (tcp_addr.sa.sa_family == AF_INET6) - addr.addr.addr6 = tcp_addr.in6.sin6_addr; -#endif + addr.addr6 = tcp_addr.in6.sin6_addr; + else + addr.addr4 = tcp_addr.in.sin_addr; for (iface = daemon->interfaces; iface; iface = iface->next) - if (iface->index == if_index) + if (iface->index == if_index && + iface->addr.sa.sa_family == tcp_addr.sa.sa_family) break; if (!iface && !loopback_exception(listener->tcpfd, tcp_addr.sa.sa_family, &addr, intr_name)) @@ -1685,27 +1854,48 @@ static void check_dns_listeners(time_t now) if (!client_ok) { shutdown(confd, SHUT_RDWR); - while (retry_send(close(confd))); + close(confd); } -#ifndef NO_FORK - else if (!option_bool(OPT_DEBUG) && (p = fork()) != 0) + else if (!option_bool(OPT_DEBUG) && pipe(pipefd) == 0 && (p = fork()) != 0) { - if (p != -1) + close(pipefd[1]); /* parent needs read pipe end. */ + if (p == -1) + close(pipefd[0]); + else { int i; +#ifdef HAVE_LINUX_NETWORK + /* The child process inherits the netlink socket, + which it never uses, but when the parent (us) + uses it in the future, the answer may go to the + child, resulting in the parent blocking + forever awaiting the result. To avoid this + the child closes the netlink socket, but there's + a nasty race, since the parent may use netlink + before the child has done the close. + + To avoid this, the parent blocks here until a + single byte comes back up the pipe, which + is sent by the child after it has closed the + netlink socket. */ + + unsigned char a; + read_write(pipefd[0], &a, 1, 1); +#endif + for (i = 0; i < MAX_PROCS; i++) - if (daemon->tcp_pids[i] == 0) + if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1) { daemon->tcp_pids[i] = p; + daemon->tcp_pipes[i] = pipefd[0]; break; } } - while (retry_send(close(confd))); + close(confd); /* The child can use up to TCP_MAX_QUERIES ids, so skip that many. */ daemon->log_id += TCP_MAX_QUERIES; } -#endif else { unsigned char *buff; @@ -1713,7 +1903,7 @@ static void check_dns_listeners(time_t now) int flags; struct in_addr netmask; int auth_dns; - + if (iface) { netmask = iface->netmask; @@ -1725,12 +1915,21 @@ static void check_dns_listeners(time_t now) auth_dns = 0; } -#ifndef NO_FORK /* Arrange for SIGALRM after CHILD_LIFETIME seconds to terminate the process. */ if (!option_bool(OPT_DEBUG)) - alarm(CHILD_LIFETIME); -#endif + { +#ifdef HAVE_LINUX_NETWORK + /* See comment above re: netlink socket. */ + unsigned char a = 0; + + close(daemon->netlinkfd); + read_write(pipefd[1], &a, 1, 0); +#endif + alarm(CHILD_LIFETIME); + close(pipefd[0]); /* close read end in child. */ + daemon->pipe_to_parent = pipefd[1]; + } /* start with no upstream connections. */ for (s = daemon->servers; s; s = s->next) @@ -1745,7 +1944,7 @@ static void check_dns_listeners(time_t now) buff = tcp_request(confd, now, &tcp_addr, netmask, auth_dns); shutdown(confd, SHUT_RDWR); - while (retry_send(close(confd))); + close(confd); if (buff) free(buff); @@ -1754,15 +1953,15 @@ static void check_dns_listeners(time_t now) if (s->tcpfd != -1) { shutdown(s->tcpfd, SHUT_RDWR); - while (retry_send(close(s->tcpfd))); + close(s->tcpfd); } -#ifndef NO_FORK + if (!option_bool(OPT_DEBUG)) { + close(daemon->pipe_to_parent); flush_log(); _exit(0); } -#endif } } } @@ -1832,7 +2031,7 @@ int icmp_ping(struct in_addr addr) gotreply = delay_dhcp(dnsmasq_time(), PING_WAIT, fd, addr.s_addr, id); #if defined(HAVE_LINUX_NETWORK) || defined(HAVE_SOLARIS_NETWORK) - while (retry_send(close(fd))); + close(fd); #else opt = 1; setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)); @@ -1919,6 +2118,4 @@ int delay_dhcp(time_t start, int sec, int fd, uint32_t addr, unsigned short id) return 0; } -#endif - - +#endif /* HAVE_DHCP */ diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 6773b69..4220798 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -14,7 +14,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#define COPYRIGHT "Copyright (c) 2000-2018 Simon Kelley" +#define COPYRIGHT "Copyright (c) 2000-2020 Simon Kelley" /* We do defines that influence behavior of stdio.h, so complain if included too early. */ @@ -42,6 +42,12 @@ # define __EXTENSIONS__ #endif +#if (defined(__GNUC__) && __GNUC__ >= 3) || defined(__clang__) +#define ATTRIBUTE_NORETURN __attribute__ ((noreturn)) +#else +#define ATTRIBUTE_NORETURN +#endif + /* get these before config.h for IPv6 stuff... */ #include <sys/types.h> #include <sys/socket.h> @@ -57,6 +63,7 @@ #include "config.h" #include "ip6addr.h" +#include "metrics.h" typedef unsigned char u8; typedef unsigned short u16; @@ -88,7 +95,11 @@ typedef unsigned long long u64; #if defined(HAVE_SOLARIS_NETWORK) # include <sys/sockio.h> #endif -#include <sys/poll.h> +#if defined(HAVE_POLL_H) +# include <poll.h> +#else +# include <sys/poll.h> +#endif #include <sys/wait.h> #include <sys/time.h> #include <sys/un.h> @@ -119,7 +130,9 @@ typedef unsigned long long u64; #include <net/if_arp.h> #include <netinet/in_systm.h> #include <netinet/ip.h> +#include <netinet/ip6.h> #include <netinet/ip_icmp.h> +#include <netinet/tcp.h> #include <sys/uio.h> #include <syslog.h> #include <dirent.h> @@ -128,6 +141,8 @@ typedef unsigned long long u64; #endif #if defined(HAVE_LINUX_NETWORK) +#include <linux/version.h> +#include <linux/sockios.h> #include <linux/capability.h> /* There doesn't seem to be a universally-available userspace header for these. */ @@ -149,6 +164,8 @@ extern int capget(cap_user_header_t header, cap_user_data_t data); /* daemon is function in the C library.... */ #define daemon dnsmasq_daemon +#define ADDRSTRLEN INET6_ADDRSTRLEN + /* Async event queue */ struct event_desc { int event, data, msg_sz; @@ -190,9 +207,6 @@ struct event_desc { #define EC_MISC 5 #define EC_INIT_OFFSET 10 -/* Trust the compiler dead-code eliminator.... */ -#define option_bool(x) (((x) < 32) ? daemon->options & (1u << (x)) : daemon->options2 & (1u << ((x) - 32))) - #define OPT_BOGUSPRIV 0 #define OPT_FILTER 1 #define OPT_LOG 2 @@ -241,7 +255,7 @@ struct event_desc { #define OPT_DNSSEC_VALID 45 #define OPT_DNSSEC_TIME 46 #define OPT_DNSSEC_DEBUG 47 -#define OPT_DNSSEC_NO_SIGN 48 +#define OPT_DNSSEC_IGN_NS 48 #define OPT_LOCAL_SERVICE 49 #define OPT_LOOP_DETECT 50 #define OPT_EXTRALOG 51 @@ -250,7 +264,18 @@ struct event_desc { #define OPT_MAC_B64 54 #define OPT_MAC_HEX 55 #define OPT_TFTP_APREF_MAC 56 -#define OPT_LAST 57 +#define OPT_RAPID_COMMIT 57 +#define OPT_UBUS 58 +#define OPT_IGNORE_CLID 59 +#define OPT_SINGLE_PORT 60 +#define OPT_LEASE_RENEW 61 +#define OPT_LAST 62 + +#define OPTION_BITS (sizeof(unsigned int)*8) +#define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) ) +#define option_var(x) (daemon->options[(x) / OPTION_BITS]) +#define option_val(x) ((1u) << ((x) % OPTION_BITS)) +#define option_bool(x) (option_var(x) & option_val(x)) /* extra flags for my_syslog, we use a couple of facilities since they are known not to occupy the same bits as priorities, no matter how syslog.h is set up. */ @@ -258,23 +283,44 @@ struct event_desc { #define MS_DHCP LOG_DAEMON #define MS_SCRIPT LOG_MAIL -struct all_addr { - union { - struct in_addr addr4; -#ifdef HAVE_IPV6 - struct in6_addr addr6; -#endif - /* for log_query */ - struct { - unsigned short keytag, algo, digest; - } log; - /* for cache_insert of DNSKEY, DS */ - struct { - unsigned short class, type; - } dnssec; - } addr; +/* Note that this is used widely as a container for IPv4/IPv6 addresses, + so for that reason, was well as to avoid wasting memory in almost every + cache entry, the other variants should not be larger than + sizeof(struct in6_addr) - 16 bytes. +*/ +union all_addr { + struct in_addr addr4; + struct in6_addr addr6; + struct { + union { + struct crec *cache; + char *name; + } target; + unsigned int uid; + int is_name_ptr; /* disciminates target union */ + } cname; + struct { + struct blockdata *keydata; + unsigned short keylen, flags, keytag; + unsigned char algo; + } key; + struct { + struct blockdata *keydata; + unsigned short keylen, keytag; + unsigned char algo; + unsigned char digest; + } ds; + struct { + struct blockdata *target; + unsigned short targetlen, srvport, priority, weight; + } srv; + /* for log_query */ + struct { + unsigned short keytag, algo, digest, rcode; + } log; }; + struct bogus_addr { struct in_addr addr; struct bogus_addr *next; @@ -334,13 +380,17 @@ struct ds_config { struct ds_config *next; }; -#define ADDRLIST_LITERAL 1 -#define ADDRLIST_IPV6 2 -#define ADDRLIST_REVONLY 4 +#define ADDRLIST_LITERAL 1 +#define ADDRLIST_IPV6 2 +#define ADDRLIST_REVONLY 4 +#define ADDRLIST_PREFIX 8 +#define ADDRLIST_WILDCARD 16 +#define ADDRLIST_DECLINED 32 struct addrlist { - struct all_addr addr; - int flags, prefixlen; + union all_addr addr; + int flags, prefixlen; + time_t decline_time; struct addrlist *next; }; @@ -359,17 +409,17 @@ struct auth_zone { struct auth_zone *next; }; +#define HR_6 1 +#define HR_4 2 struct host_record { - int ttl; + int ttl, flags; struct name_list { char *name; struct name_list *next; } *names; struct in_addr addr; -#ifdef HAVE_IPV6 struct in6_addr addr6; -#endif struct host_record *next; }; @@ -393,32 +443,11 @@ struct blockdata { struct crec { struct crec *next, *prev, *hash_next; - /* union is 16 bytes when doing IPv6, 8 bytes on 32 bit machines without IPv6 */ - union { - struct all_addr addr; - struct { - union { - struct crec *cache; - struct interface_name *int_name; - } target; - unsigned int uid; /* 0 if union is interface-name */ - } cname; - struct { - struct blockdata *keydata; - unsigned short keylen, flags, keytag; - unsigned char algo; - } key; - struct { - struct blockdata *keydata; - unsigned short keylen, keytag; - unsigned char algo; - unsigned char digest; - } ds; - } addr; + union all_addr addr; time_t ttd; /* time to die */ /* used as class if DNSKEY/DS, index to source for F_HOSTS */ unsigned int uid; - unsigned short flags; + unsigned int flags; union { char sname[SMALLDNAME]; union bigname *bname; @@ -426,6 +455,9 @@ struct crec { } name; }; +#define SIZEOF_BARE_CREC (sizeof(struct crec) - SMALLDNAME) +#define SIZEOF_POINTER_CREC (sizeof(struct crec) + sizeof(char *) - SMALLDNAME) + #define F_IMMORTAL (1u<<0) #define F_NAMEP (1u<<1) #define F_REVERSE (1u<<2) @@ -442,9 +474,6 @@ struct crec { #define F_CONFIG (1u<<13) #define F_DS (1u<<14) #define F_DNSSECOK (1u<<15) - -/* below here are only valid as args to log_query: cache - entries are limited to 16 bits */ #define F_UPSTREAM (1u<<16) #define F_RRNAME (1u<<17) #define F_SERVER (1u<<18) @@ -457,10 +486,12 @@ struct crec { #define F_NO_RR (1u<<25) #define F_IPSET (1u<<26) #define F_NOEXTRA (1u<<27) -#define F_SERVFAIL (1u<<28) +#define F_SERVFAIL (1u<<28) /* currently unused. */ +#define F_RCODE (1u<<29) +#define F_SRV (1u<<30) +#define UID_NONE 0 /* Values of uid in crecs with F_CONFIG bit set. */ -#define SRC_INTERFACE 0 #define SRC_CONFIG 1 #define SRC_HOSTS 2 #define SRC_AH 3 @@ -472,9 +503,7 @@ struct crec { union mysockaddr { struct sockaddr sa; struct sockaddr_in in; -#if defined(HAVE_IPV6) struct sockaddr_in6 in6; -#endif }; /* bits in flag param to IPv6 callbacks from iface_enumerate() */ @@ -505,7 +534,7 @@ struct serverfd { int fd; union mysockaddr source_addr; char interface[IF_NAMESIZE+1]; - unsigned int ifindex, used; + unsigned int ifindex, used, preallocated; struct serverfd *next; }; @@ -543,7 +572,8 @@ struct irec { }; struct listener { - int fd, tcpfd, tftpfd, family; + int fd, tcpfd, tftpfd, used; + union mysockaddr addr; struct irec *iface; /* only sometimes valid for non-wildcard */ struct listener *next; }; @@ -592,6 +622,16 @@ struct hostsfile { unsigned int index; /* matches to cache entries for logging */ }; +/* packet-dump flags */ +#define DUMP_QUERY 0x0001 +#define DUMP_REPLY 0x0002 +#define DUMP_UP_QUERY 0x0004 +#define DUMP_UP_REPLY 0x0008 +#define DUMP_SEC_QUERY 0x0010 +#define DUMP_SEC_REPLY 0x0020 +#define DUMP_BOGUS 0x0040 +#define DUMP_SEC_BOGUS 0x0080 + /* DNSSEC status values. */ #define STAT_SECURE 1 @@ -623,12 +663,10 @@ struct hostsfile { struct frec { union mysockaddr source; - struct all_addr dest; + union all_addr dest; struct server *sentto; /* NULL means free */ struct randfd *rfd4; -#ifdef HAVE_IPV6 struct randfd *rfd6; -#endif unsigned int iface; unsigned short orig_id, new_id; int log_id, fd, forwardall, flags; @@ -670,6 +708,7 @@ struct frec { #define LEASE_NA 32 /* IPv6 no-temporary lease */ #define LEASE_TA 64 /* IPv6 temporary lease */ #define LEASE_HAVE_HWADDR 128 /* Have set hwaddress */ +#define LEASE_EXP_CHANGED 256 /* Lease expiry time changed */ struct dhcp_lease { int clid_len; /* length of client identifier */ @@ -691,7 +730,7 @@ struct dhcp_lease { int new_prefixlen; /* and its prefix length */ #ifdef HAVE_DHCP6 struct in6_addr addr6; - int iaid; + unsigned int iaid; struct slaac_address { struct in6_addr addr; time_t ping_time; @@ -738,8 +777,9 @@ struct dhcp_config { unsigned char *clid; /* clientid */ char *hostname, *domain; struct dhcp_netid_list *netid; + struct dhcp_netid *filter; #ifdef HAVE_DHCP6 - struct in6_addr addr6; + struct addrlist *addr6; #endif struct in_addr addr; time_t decline_time; @@ -761,7 +801,7 @@ struct dhcp_config { #define CONFIG_DECLINED 1024 /* address declined by client */ #define CONFIG_BANK 2048 /* from dhcp hosts file */ #define CONFIG_ADDR6 4096 -#define CONFIG_WILDCARD 8192 +#define CONFIG_ADDR6_HOSTS 16384 /* address added by from /etc/hosts */ struct dhcp_opt { int opt, len, flags; @@ -797,6 +837,13 @@ struct dhcp_boot { struct dhcp_boot *next; }; +struct dhcp_match_name { + char *name; + int wildcard; + struct dhcp_netid *netid; + struct dhcp_match_name *next; +}; + struct pxe_service { unsigned short CSA, type; char *menu, *basename, *sname; @@ -836,21 +883,11 @@ struct dhcp_bridge { struct cond_domain { char *domain, *prefix; struct in_addr start, end; -#ifdef HAVE_IPV6 struct in6_addr start6, end6; -#endif int is6, indexed; struct cond_domain *next; }; -#ifdef OPTION6_PREFIX_CLASS -struct prefix_class { - int class; - struct dhcp_netid tag; - struct prefix_class *next; -}; -#endif - struct ra_interface { char *name; char *mtu_name; @@ -876,6 +913,16 @@ struct dhcp_context { struct dhcp_context *next, *current; }; +struct shared_network { + int if_index; + struct in_addr match_addr, shared_addr; +#ifdef HAVE_DHCP6 + /* shared_addr == 0 for IP6 entries. */ + struct in6_addr match_addr6, shared_addr6; +#endif + struct shared_network *next; +}; + #define CONTEXT_STATIC (1u<<0) #define CONTEXT_NETMASK (1u<<1) #define CONTEXT_BRDCAST (1u<<2) @@ -895,6 +942,7 @@ struct dhcp_context { #define CONTEXT_OLD (1u<<16) #define CONTEXT_V6 (1u<<17) #define CONTEXT_RA_OFF_LINK (1u<<18) +#define CONTEXT_SETLEASE (1u<<19) struct ping_result { struct in_addr addr; @@ -918,6 +966,8 @@ struct tftp_transfer { unsigned int block, blocksize, expansion; off_t offset; union mysockaddr peer; + union all_addr source; + int if_index; char opt_blocksize, opt_transize, netascii, carrylf; struct tftp_file *file; struct tftp_transfer *next; @@ -936,7 +986,7 @@ struct tftp_prefix { }; struct dhcp_relay { - struct all_addr local, server; + union all_addr local, server; char *interface; /* Allowable interface for replies from server, and dest for IPv6 multicast */ int iface_index; /* working - interface in which requests arrived, for return */ struct dhcp_relay *current, *next; @@ -947,7 +997,7 @@ extern struct daemon { config file arguments. All set (including defaults) in option.c */ - unsigned int options, options2; + unsigned int options[OPTION_SIZE]; struct resolvc default_resolv, *resolv_files; time_t last_resolv; char *servers_file; @@ -989,6 +1039,7 @@ extern struct daemon { struct ra_interface *ra_interfaces; struct dhcp_config *dhcp_conf; struct dhcp_opt *dhcp_opts, *dhcp_match, *dhcp_opts6, *dhcp_match6; + struct dhcp_match_name *dhcp_name_match; struct dhcp_vendor *dhcp_vendors; struct dhcp_mac *dhcp_macs; struct dhcp_boot *boot_config; @@ -1014,14 +1065,13 @@ extern struct daemon { unsigned int duid_enterprise, duid_config_len; unsigned char *duid_config; char *dbus_name; + char *ubus_name; + char *dump_file; + int dump_mask; unsigned long soa_sn, soa_refresh, soa_retry, soa_expiry; -#ifdef OPTION6_PREFIX_CLASS - struct prefix_class *prefix_classes; -#endif + u32 metrics[__METRIC_MAX]; #ifdef HAVE_DNSSEC struct ds_config *ds; - int dnssec_no_time_check; - int back_to_the_future; char *timestamp_file; #endif @@ -1032,10 +1082,11 @@ extern struct daemon { #ifdef HAVE_DNSSEC char *keyname; /* MAXDNAME size buffer */ char *workspacename; /* ditto */ - char *rr_status; /* flags for individual RRs */ + unsigned long *rr_status; /* ceiling in TTL from DNSSEC or zero for insecure */ int rr_status_sz; + int dnssec_no_time_check; + int back_to_the_future; #endif - unsigned int local_answer, queries_forwarded, auth_answer; struct frec *frec_list; struct serverfd *sfds; struct irec *interfaces; @@ -1047,6 +1098,8 @@ extern struct daemon { size_t packet_len; /* " " */ struct randfd *rfd_save; /* " " */ pid_t tcp_pids[MAX_PROCS]; + int tcp_pipes[MAX_PROCS]; + int pipe_to_parent; struct randfd randomsocks[RANDOM_SOCKS]; int v6pktinfo; struct addrlist *interface_addrs; /* list of all addresses/prefix lengths associated with all local interfaces */ @@ -1059,7 +1112,7 @@ extern struct daemon { int inotifyfd; #endif #if defined(HAVE_LINUX_NETWORK) - int netlinkfd; + int netlinkfd, kernel_version; #elif defined(HAVE_BSD_NETWORK) int dhcp_raw_fd, dhcp_icmp_fd, routefd; #endif @@ -1068,6 +1121,7 @@ extern struct daemon { struct ping_result *ping_results; FILE *lease_stream; struct dhcp_bridge *bridges; + struct shared_network *shared_networks; #ifdef HAVE_DHCP6 int duid_len; unsigned char *duid; @@ -1080,6 +1134,11 @@ extern struct daemon { #ifdef HAVE_DBUS struct watch *watches; #endif + /* UBus stuff */ +#ifdef HAVE_UBUS + /* void * here to avoid depending on ubus headers outside ubus.c */ + void *ubus; +#endif /* TFTP stuff */ struct tftp_transfer *tftp_trans, *tftp_done_trans; @@ -1088,24 +1147,31 @@ extern struct daemon { char *addrbuff; char *addrbuff2; /* only allocated when OPT_EXTRALOG */ +#ifdef HAVE_DUMPFILE + /* file for packet dumps. */ + int dumpfd; +#endif } *daemon; /* cache.c */ void cache_init(void); -void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg); +void next_uid(struct crec *crecp); +void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg); char *record_source(unsigned int index); char *querystr(char *desc, unsigned short type); +int cache_find_non_terminal(char *name, time_t now); struct crec *cache_find_by_addr(struct crec *crecp, - struct all_addr *addr, time_t now, + union all_addr *addr, time_t now, unsigned int prot); struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsigned int prot); void cache_end_insert(void); void cache_start_insert(void); -struct crec *cache_insert(char *name, struct all_addr *addr, - time_t now, unsigned long ttl, unsigned short flags); +int cache_recv_insert(time_t now, int fd); +struct crec *cache_insert(char *name, union all_addr *addr, unsigned short class, + time_t now, unsigned long ttl, unsigned int flags); void cache_reload(void); -void cache_add_dhcp_entry(char *host_name, int prot, struct all_addr *host_address, time_t ttd); +void cache_add_dhcp_entry(char *host_name, int prot, union all_addr *host_address, time_t ttd); struct in_addr a_record_from_hosts(char *name, time_t now); void cache_unhash_dhcp(void); void dump_cache(time_t now); @@ -1119,21 +1185,19 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct crec **rhash, int hashsz); /* blockdata.c */ -#ifdef HAVE_DNSSEC void blockdata_init(void); void blockdata_report(void); struct blockdata *blockdata_alloc(char *data, size_t len); void *blockdata_retrieve(struct blockdata *block, size_t len, void *data); +struct blockdata *blockdata_read(int fd, size_t len); +void blockdata_write(struct blockdata *block, size_t len, int fd); void blockdata_free(struct blockdata *blocks); -#endif /* domain.c */ char *get_domain(struct in_addr addr); -#ifdef HAVE_IPV6 char *get_domain6(struct in6_addr *addr); -#endif -int is_name_synthetic(int flags, char *name, struct all_addr *addr); -int is_rev_synth(int flag, struct all_addr *addr, char *name); +int is_name_synthetic(int flags, char *name, union all_addr *addr); +int is_rev_synth(int flag, union all_addr *addr, char *name); /* rfc1035.c */ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, @@ -1144,7 +1208,7 @@ unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *h unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, unsigned short *typep); size_t setup_reply(struct dns_header *header, size_t qlen, - struct all_addr *addrp, unsigned int flags, + union all_addr *addrp, unsigned int flags, unsigned long ttl); int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now, char **ipsets, int is_sign, int check_rebind, @@ -1162,10 +1226,7 @@ size_t resize_packet(struct dns_header *header, size_t plen, int add_resource_record(struct dns_header *header, char *limit, int *truncp, int nameoffset, unsigned char **pp, unsigned long ttl, int *offset, unsigned short type, unsigned short class, char *format, ...); -unsigned char *skip_questions(struct dns_header *header, size_t plen); -int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, - char *name, int isExtract, int extrabytes); -int in_arpa_name_2_addr(char *namein, struct all_addr *addrp); +int in_arpa_name_2_addr(char *namein, union all_addr *addrp); int private_net(struct in_addr addr, int ban_localhost); /* auth.c */ @@ -1177,11 +1238,11 @@ int in_zone(struct auth_zone *zone, char *name, char **cut); #endif /* dnssec.c */ -size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int type, union mysockaddr *addr, int edns_pktsz); +size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int type, int edns_pktsz); int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, - int check_unsigned, int *neganswer, int *nons); + int check_unsigned, int *neganswer, int *nons, int *nsec_ttl); int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen); size_t filter_rrsigs(struct dns_header *header, size_t plen); unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name); @@ -1205,19 +1266,19 @@ int legal_hostname(char *name); char *canonicalise(char *in, int *nomem); unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit); void *safe_malloc(size_t size); +void safe_strncpy(char *dest, const char *src, size_t size); void safe_pipe(int *fd, int read_noblock); void *whine_malloc(size_t size); int sa_len(union mysockaddr *addr); int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2); int hostname_isequal(const char *a, const char *b); +int hostname_issubdomain(char *a, 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); u64 addr6part(struct in6_addr *addr); void setaddr6part(struct in6_addr *addr, u64 host); -#endif int retry_send(ssize_t rc); void prettyprint_time(char *buf, unsigned int t); int prettyprint_addr(union mysockaddr *addr, char *buf); @@ -1228,12 +1289,15 @@ int memcmp_masked(unsigned char *a, unsigned char *b, int len, int expand_buf(struct iovec *iov, size_t size); char *print_mac(char *buff, unsigned char *mac, int len); int read_write(int fd, unsigned char *packet, int size, int rw); - +void close_fds(long max_fd, int spare1, int spare2, int spare3); int wildcard_match(const char* wildcard, const char* match); int wildcard_matchn(const char* wildcard, const char* match, int num); +#ifdef HAVE_LINUX_NETWORK +int kernel_version(void); +#endif /* log.c */ -void die(char *message, char *arg1, int exit_code); +void die(char *message, char *arg1, int exit_code) ATTRIBUTE_NORETURN; int log_start(struct passwd *ent_pw, int errfd); int log_reopen(char *log_file); @@ -1262,9 +1326,9 @@ void receive_query(struct listener *listen, time_t now); unsigned char *tcp_request(int confd, time_t now, union mysockaddr *local_addr, struct in_addr netmask, int auth_dns); void server_gone(struct server *server); -struct frec *get_new_frec(time_t now, int *wait, int force); +struct frec *get_new_frec(time_t now, int *wait, struct frec *force); int send_from(int fd, int nowild, char *packet, size_t len, - union mysockaddr *to, struct all_addr *source, + union mysockaddr *to, union all_addr *source, unsigned int iface); void resend_query(void); struct randfd *allocate_rfd(int family); @@ -1291,14 +1355,12 @@ void warn_bound_listeners(void); void warn_wild_labels(void); void warn_int_names(void); int is_dad_listeners(void); -int iface_check(int family, struct all_addr *addr, char *name, int *auth); -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 iface_check(int family, union all_addr *addr, char *name, int *auth); +int loopback_exception(int fd, int family, union all_addr *addr, char *name); +int label_exception(int index, int family, union all_addr *addr); int fix_fd(int fd); int tcp_interface(int fd, int af); -#ifdef HAVE_IPV6 int set_ipv6pktinfo(int fd); -#endif #ifdef HAVE_DHCP6 void join_multicast(int dienow); #endif @@ -1336,14 +1398,15 @@ struct dhcp_lease *lease4_allocate(struct in_addr addr); #ifdef HAVE_DHCP6 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); + int lease_type, unsigned int iaid, struct in6_addr *addr); 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_client(struct dhcp_lease *first, int lease_type, + unsigned char *clid, int clid_len, unsigned 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); void lease_update_slaac(time_t now); -void lease_set_iaid(struct dhcp_lease *lease, int iaid); +void lease_set_iaid(struct dhcp_lease *lease, unsigned int iaid); void lease_make_duid(time_t now); #endif void lease_set_hwaddr(struct dhcp_lease *lease, const unsigned char *hwaddr, @@ -1389,7 +1452,7 @@ void clear_cache_and_reload(time_t now); /* netlink.c */ #ifdef HAVE_LINUX_NETWORK -void netlink_init(void); +char *netlink_init(void); void netlink_multicast(void); #endif @@ -1415,10 +1478,18 @@ void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname); # endif #endif +/* ubus.c */ +#ifdef HAVE_UBUS +void ubus_init(void); +void set_ubus_listeners(void); +void check_ubus_listeners(void); +void ubus_event_bcast(const char *type, const char *mac, const char *ip, const char *name, const char *interface); +#endif + /* ipset.c */ #ifdef HAVE_IPSET void ipset_init(void); -int add_to_ipset(const char *setname, const struct all_addr *ipaddr, int flags, int remove); +int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove); #endif /* helper.c */ @@ -1431,7 +1502,7 @@ void queue_script(int action, struct dhcp_lease *lease, void queue_tftp(off_t file_len, char *filename, union mysockaddr *peer); #endif void queue_arp(int action, unsigned char *mac, int maclen, - int family, struct all_addr *addr); + int family, union all_addr *addr); int helper_buf_empty(void); #endif @@ -1444,7 +1515,7 @@ int do_tftp_script_run(void); /* conntrack.c */ #ifdef HAVE_CONNTRACK -int get_incoming_mark(union mysockaddr *peer_addr, struct all_addr *local_addr, +int get_incoming_mark(union mysockaddr *peer_addr, union all_addr *local_addr, int istcp, unsigned int *markp); #endif @@ -1453,8 +1524,7 @@ int get_incoming_mark(union mysockaddr *peer_addr, struct all_addr *local_addr, void dhcp6_init(void); void dhcp6_packet(time_t now); struct dhcp_context *address6_allocate(struct dhcp_context *context, unsigned char *clid, int clid_len, int temp_addr, - int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans); -int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr); + unsigned int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans); struct dhcp_context *address6_available(struct dhcp_context *context, struct in6_addr *taddr, struct dhcp_netid *netids, @@ -1464,7 +1534,7 @@ struct dhcp_context *address6_valid(struct dhcp_context *context, struct dhcp_netid *netids, int plain_range); struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, - int prefix, u64 addr); + int prefix, struct in6_addr *addr); void make_duid(time_t now); void dhcp_construct_contexts(time_t now); void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, @@ -1497,13 +1567,12 @@ void dhcp_update_configs(struct dhcp_config *configs); void display_opts(void); int lookup_dhcp_opt(int prot, char *name); int lookup_dhcp_len(int prot, int val); -char *option_string(int prot, unsigned int opt, unsigned char *val, - int opt_len, char *buf, int buf_len); struct dhcp_config *find_config(struct dhcp_config *configs, struct dhcp_context *context, unsigned char *clid, int clid_len, unsigned char *hwaddr, int hw_len, - int hw_type, char *hostname); + int hw_type, char *hostname, + struct dhcp_netid *filter); int config_has_mac(struct dhcp_config *config, unsigned char *hwaddr, int len, int type); #ifdef HAVE_LINUX_NETWORK char *whichdevice(void); @@ -1582,3 +1651,9 @@ int check_source(struct dns_header *header, size_t plen, unsigned char *pseudohe /* arp.c */ int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy, time_t now); int do_arp_script_run(void); + +/* dump.c */ +#ifdef HAVE_DUMPFILE +void dump_init(void); +void dump_packet(int mask, void *packet, size_t len, union mysockaddr *src, union mysockaddr *dst); +#endif diff --git a/src/dnssec.c b/src/dnssec.c index 8143185..db5c2d1 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -1,5 +1,5 @@ /* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com> - and Copyright (c) 2012-2018 Simon Kelley + and Copyright (c) 2012-2020 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 @@ -186,10 +186,8 @@ int setup_timestamp(void) } /* Check whether today/now is between date_start and date_end */ -static int check_date_range(u32 date_start, u32 date_end) +static int is_check_date(unsigned long curtime) { - unsigned long curtime = time(0); - /* Checking timestamps may be temporarily disabled */ /* If the current time if _before_ the timestamp @@ -211,12 +209,15 @@ static int check_date_range(u32 date_start, u32 date_end) queue_event(EVENT_RELOAD); /* purge cache */ } - if (daemon->back_to_the_future == 0) - return 1; + return daemon->back_to_the_future; } - else if (daemon->dnssec_no_time_check) - return 1; - + else + return !daemon->dnssec_no_time_check; +} + +/* Check whether today/now is between date_start and date_end */ +static int check_date_range(unsigned long curtime, u32 date_start, u32 date_end) +{ /* We must explicitly check against wanted values, because of SERIAL_UNDEF */ return serial_compare_32(curtime, date_start) == SERIAL_GT && serial_compare_32(curtime, date_end) == SERIAL_LT; @@ -374,7 +375,7 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int int gotkey = 0; if (!(p = skip_questions(header, plen))) - return STAT_BOGUS; + return 0; /* look for RRSIGs for this RRset and get pointers to each RR in the set. */ for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); @@ -386,14 +387,14 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int pstart = p; if (!(res = extract_name(header, plen, &p, name, 0, 10))) - return STAT_BOGUS; /* bad packet */ + return 0; /* bad packet */ GETSHORT(stype, p); GETSHORT(sclass, p); - p += 4; /* TTL */ - + pdata = p; + p += 4; /* TTL */ GETSHORT(rdlen, p); if (!CHECK_LEN(header, p, plen, rdlen)) @@ -456,7 +457,7 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int sigs[sigidx++] = pdata; } - p = pdata + 2; /* restore for ADD_RDLEN */ + p = pdata + 6; /* restore for ADD_RDLEN */ } } @@ -485,16 +486,22 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int Name is unchanged on exit. keyname is used as workspace and trashed. Call explore_rrset first to find and count RRs and sigs. + + ttl_out is the floor on TTL, based on TTL and orig_ttl and expiration of sig used to validate. */ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx, - char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in) + char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, + int algo_in, int keytag_in, unsigned long *ttl_out) { unsigned char *p; - int rdlen, j, name_labels, algo, labels, orig_ttl, key_tag; + int rdlen, j, name_labels, algo, labels, key_tag; struct crec *crecp = NULL; u16 *rr_desc = rrfilter_desc(type); - u32 sig_expiration, sig_inception -; + u32 sig_expiration, sig_inception; + + unsigned long curtime = time(0); + int time_check = is_check_date(curtime); + if (wildcard_out) *wildcard_out = NULL; @@ -513,9 +520,10 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in const struct nettle_hash *hash; void *ctx; char *name_start; - u32 nsigttl; + u32 nsigttl, ttl, orig_ttl; p = sigs[j]; + GETLONG(ttl, p); GETSHORT(rdlen, p); /* rdlen >= 18 checked previously */ psav = p; @@ -530,16 +538,28 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in if (!extract_name(header, plen, &p, keyname, 1, 0)) return STAT_BOGUS; - if (!check_date_range(sig_inception, sig_expiration) || + if ((time_check && !check_date_range(curtime, sig_inception, sig_expiration)) || labels > name_labels || !(hash = hash_find(algo_digest_name(algo))) || !hash_init(hash, &ctx, &digest)) continue; - + /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */ if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY))) return STAT_NEED_KEY; - + + if (ttl_out) + { + /* 4035 5.3.3 rules on TTLs */ + if (orig_ttl < ttl) + ttl = orig_ttl; + + if (time_check && difftime(sig_expiration, curtime) < ttl) + ttl = difftime(sig_expiration, curtime); + + *ttl_out = ttl; + } + sig = p; sig_len = rdlen - (p - psav); @@ -653,11 +673,13 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch { unsigned char *psave, *p = (unsigned char *)(header+1); struct crec *crecp, *recp1; - int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag; + int rc, j, qtype, qclass, rdlen, flags, algo, valid, keytag; + unsigned long ttl, sig_ttl; struct blockdata *key; - struct all_addr a; + union all_addr a; if (ntohs(header->qdcount) != 1 || + RCODE(header) == SERVFAIL || RCODE(header) == REFUSED || !extract_name(header, plen, &p, name, 1, 4)) return STAT_BOGUS; @@ -747,12 +769,12 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch if (!(recp1->flags & F_NEG) && recp1->addr.ds.keylen == (int)hash->digest_size && - (ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->addr.ds.keylen, NULL)) && + (ds_digest = blockdata_retrieve(recp1->addr.ds.keydata, recp1->addr.ds.keylen, NULL)) && memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 && explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) && sigcnt != 0 && rrcnt != 0 && validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, - NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE) + NULL, key, rdlen - 4, algo, keytag, &sig_ttl) == STAT_SECURE) { valid = 1; break; @@ -779,6 +801,10 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch GETSHORT(qclass, p); GETLONG(ttl, p); GETSHORT(rdlen, p); + + /* TTL may be limited by sig. */ + if (sig_ttl < ttl) + ttl = sig_ttl; if (!CHECK_LEN(header, p, plen, rdlen)) return STAT_BOGUS; /* bad packet */ @@ -798,30 +824,27 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch algo = *p++; keytag = dnskey_keytag(algo, flags, p, rdlen - 4); - /* Cache needs to known class for DNSSEC stuff */ - a.addr.dnssec.class = class; - if ((key = blockdata_alloc((char*)p, rdlen - 4))) { - if (!(recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK))) + a.key.keylen = rdlen - 4; + a.key.keydata = key; + a.key.algo = algo; + a.key.keytag = keytag; + a.key.flags = flags; + + if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK)) { blockdata_free(key); return STAT_BOGUS; } else { - a.addr.log.keytag = keytag; - a.addr.log.algo = algo; + a.log.keytag = keytag; + a.log.algo = algo; if (algo_digest_name(algo)) log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu"); else log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)"); - - recp1->addr.key.keylen = rdlen - 4; - recp1->addr.key.keydata = key; - recp1->addr.key.algo = algo; - recp1->addr.key.keytag = keytag; - recp1->addr.key.flags = flags; } } } @@ -857,10 +880,10 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) { unsigned char *p = (unsigned char *)(header+1); - int qtype, qclass, rc, i, neganswer, nons; + int qtype, qclass, rc, i, neganswer, nons, neg_ttl = 0; int aclass, atype, rdlen; unsigned long ttl; - struct all_addr a; + union all_addr a; if (ntohs(header->qdcount) != 1 || !(p = skip_name(p, header, plen, 4))) @@ -872,11 +895,14 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char if (qtype != T_DS || qclass != class) rc = STAT_BOGUS; else - rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons); + rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl); if (rc == STAT_INSECURE) - rc = STAT_BOGUS; - + { + my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name); + rc = STAT_BOGUS; + } + p = (unsigned char *)(header+1); extract_name(header, plen, &p, name, 1, 4); p += 4; /* qtype, qclass */ @@ -915,8 +941,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char int algo, digest, keytag; unsigned char *psave = p; struct blockdata *key; - struct crec *crecp; - + if (rdlen < 4) return STAT_BOGUS; /* bad packet */ @@ -924,31 +949,28 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char algo = *p++; digest = *p++; - /* Cache needs to known class for DNSSEC stuff */ - a.addr.dnssec.class = class; - if ((key = blockdata_alloc((char*)p, rdlen - 4))) { - if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK))) + a.ds.digest = digest; + a.ds.keydata = key; + a.ds.algo = algo; + a.ds.keytag = keytag; + a.ds.keylen = rdlen - 4; + + if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_DS | F_DNSSECOK)) { blockdata_free(key); return STAT_BOGUS; } else { - a.addr.log.keytag = keytag; - a.addr.log.algo = algo; - a.addr.log.digest = digest; + a.log.keytag = keytag; + a.log.algo = algo; + a.log.digest = digest; if (ds_digest_name(digest) && algo_digest_name(algo)) log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu"); else log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu (not supported)"); - - crecp->addr.ds.digest = digest; - crecp->addr.ds.keydata = key; - crecp->addr.ds.algo = algo; - crecp->addr.ds.keytag = keytag; - crecp->addr.ds.keylen = rdlen - 4; } } @@ -964,11 +986,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char else { int flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK; - unsigned long minttl = ULONG_MAX; - - if (!(p = skip_section(p, ntohs(header->ancount), header, plen))) - return STAT_BOGUS; - + if (RCODE(header) == NXDOMAIN) flags |= F_NXDOMAIN; @@ -977,55 +995,15 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char if (nons) flags &= ~F_DNSSECOK; - for (i = ntohs(header->nscount); i != 0; i--) - { - if (!(p = skip_name(p, header, plen, 0))) - return STAT_BOGUS; - - GETSHORT(atype, p); - GETSHORT(aclass, p); - GETLONG(ttl, p); - GETSHORT(rdlen, p); - - if (!CHECK_LEN(header, p, plen, rdlen)) - return STAT_BOGUS; /* bad packet */ - - if (aclass != class || atype != T_SOA) - { - p += rdlen; - continue; - } - - if (ttl < minttl) - minttl = ttl; - - /* MNAME */ - if (!(p = skip_name(p, header, plen, 0))) - return STAT_BOGUS; - /* RNAME */ - if (!(p = skip_name(p, header, plen, 20))) - return STAT_BOGUS; - p += 16; /* SERIAL REFRESH RETRY EXPIRE */ - - GETLONG(ttl, p); /* minTTL */ - if (ttl < minttl) - minttl = ttl; + cache_start_insert(); - break; - } + /* Use TTL from NSEC for negative cache entries */ + if (!cache_insert(name, NULL, class, now, neg_ttl, flags)) + return STAT_BOGUS; - if (i != 0) - { - cache_start_insert(); - - a.addr.dnssec.class = class; - if (!cache_insert(name, &a, now, ttl, flags)) - return STAT_BOGUS; - - cache_end_insert(); - - log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "no DS"); - } + cache_end_insert(); + + log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, nons ? "no DS/cut" : "no DS"); } return STAT_OK; @@ -1539,7 +1517,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns return 1; } -static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons) +static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons, int *nsec_ttl) { static unsigned char **nsecset = NULL, **rrsig_labels = NULL; static int nsecset_sz = 0, rrsig_labels_sz = 0; @@ -1547,6 +1525,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key int type_found = 0; unsigned char *auth_start, *p = skip_questions(header, plen); int type, class, rdlen, i, nsecs_found; + unsigned long ttl; /* Move to NS section */ if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen))) @@ -1554,7 +1533,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key auth_start = p; - for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--) + for (nsecs_found = 0, i = 0; i < ntohs(header->nscount); i++) { unsigned char *pstart = p; @@ -1563,11 +1542,19 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key GETSHORT(type, p); GETSHORT(class, p); - p += 4; /* TTL */ + GETLONG(ttl, p); GETSHORT(rdlen, p); if (class == qclass && (type == T_NSEC || type == T_NSEC3)) { + if (nsec_ttl) + { + /* Limit TTL with sig TTL */ + if (daemon->rr_status[ntohs(header->ancount) + i] < ttl) + ttl = daemon->rr_status[ntohs(header->ancount) + i]; + *nsec_ttl = ttl; + } + /* No mixed NSECing 'round here, thankyouverymuch */ if (type_found != 0 && type_found != type) return 0; @@ -1748,33 +1735,37 @@ static int zone_status(char *name, int class, char *keyname, time_t now) STAT_NEED_DS need DS to complete validation (name is returned in keyname) daemon->rr_status points to a char array which corressponds to the RRs in the - answer section (only). This is set to 1 for each RR which is validated, and 0 for any which aren't. + answer and auth sections. This is set to 1 for each RR which is validated, and 0 for any which aren't. + + When validating replies to DS records, we're only interested in the NSEC{3} RRs in the auth section. + Other RRs in that section missing sigs will not cause am INSECURE reply. We determine this mode + is the nons argument is non-NULL. */ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, - int *class, int check_unsigned, int *neganswer, int *nons) + int *class, int check_unsigned, int *neganswer, int *nons, int *nsec_ttl) { static unsigned char **targets = NULL; static int target_sz = 0; unsigned char *ans_start, *p1, *p2; int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx; - int i, j, rc; + int i, j, rc = STAT_INSECURE; int secure = STAT_SECURE; /* extend rr_status if necessary */ - if (daemon->rr_status_sz < ntohs(header->ancount)) + if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount)) { - char *new = whine_malloc(ntohs(header->ancount) + 64); + unsigned long *new = whine_malloc(sizeof(*daemon->rr_status) * (ntohs(header->ancount) + ntohs(header->nscount) + 64)); if (!new) return STAT_BOGUS; free(daemon->rr_status); daemon->rr_status = new; - daemon->rr_status_sz = ntohs(header->ancount) + 64; + daemon->rr_status_sz = ntohs(header->ancount) + ntohs(header->nscount) + 64; } - memset(daemon->rr_status, 0, ntohs(header->ancount)); + memset(daemon->rr_status, 0, sizeof(*daemon->rr_status) * daemon->rr_status_sz); if (neganswer) *neganswer = 0; @@ -1832,10 +1823,10 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++) { - if (i != 0 && !ADD_RDLEN(header, p1, plen, rdlen1)) - return STAT_BOGUS; - - if (!extract_name(header, plen, &p1, name, 1, 10)) + if (i != 0 && !ADD_RDLEN(header, p1, plen, rdlen1)) + return STAT_BOGUS; + + if (!extract_name(header, plen, &p1, name, 1, 10)) return STAT_BOGUS; /* bad packet */ GETSHORT(type1, p1); @@ -1865,12 +1856,9 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch return STAT_BOGUS; } + /* Done already: copy the validation status */ if (j != i) - { - /* Done already: copy the validation status */ - if (i < ntohs(header->ancount)) - daemon->rr_status[i] = daemon->rr_status[j]; - } + daemon->rr_status[i] = daemon->rr_status[j]; else { /* Not done, validate now */ @@ -1883,19 +1871,31 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch /* No signatures for RRset. We can be configured to assume this is OK and return an INSECURE result. */ if (sigcnt == 0) { - if (check_unsigned) + /* NSEC and NSEC3 records must be signed. We make this assumption elsewhere. */ + if (type1 == T_NSEC || type1 == T_NSEC3) + rc = STAT_INSECURE; + else if (nons && i >= ntohs(header->ancount)) + /* If we're validating a DS reply, rather than looking for the value of AD bit, + we only care that NSEC and NSEC3 RRs in the auth section are signed. + Return SECURE even if others (SOA....) are not. */ + rc = STAT_SECURE; + else { - rc = zone_status(name, class1, keyname, now); - if (rc == STAT_SECURE) - rc = STAT_BOGUS; - if (class) - *class = class1; /* Class for NEED_DS or NEED_KEY */ + /* unsigned RRsets in auth section are not BOGUS, but do make reply insecure. */ + if (check_unsigned && i < ntohs(header->ancount)) + { + rc = zone_status(name, class1, keyname, now); + if (rc == STAT_SECURE) + rc = STAT_BOGUS; + if (class) + *class = class1; /* Class for NEED_DS or NEED_KEY */ + } + else + rc = STAT_INSECURE; + + if (rc != STAT_INSECURE) + return rc; } - else - rc = STAT_INSECURE; - - if (rc != STAT_INSECURE) - return rc; } else { @@ -1906,7 +1906,6 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS) { - /* Zone is insecure, don't need to validate RRset */ if (class) *class = class1; /* Class for NEED_DS or NEED_KEY */ return rc; @@ -1915,8 +1914,9 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch /* Zone is insecure, don't need to validate RRset */ if (rc == STAT_SECURE) { + unsigned long sig_ttl; rc = validate_rrset(now, header, plen, class1, type1, sigcnt, - rrcnt, name, keyname, &wildname, NULL, 0, 0, 0); + rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl); if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS) { @@ -1928,8 +1928,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch /* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */ /* Note that RR is validated */ - if (i < ntohs(header->ancount)) - daemon->rr_status[i] = 1; + daemon->rr_status[i] = sig_ttl; /* Note if we've validated either the answer to the question or the target of a CNAME. Any not noted will need NSEC or @@ -1953,7 +1952,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch That's not a problem since if the RRsets later fail we'll return BOGUS then. */ if (rc == STAT_SECURE_WILDCARD && - !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL)) + !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL)) return STAT_BOGUS; rc = STAT_SECURE; @@ -1966,35 +1965,36 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch } /* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */ - for (j = 0; j <targetidx; j++) - if ((p2 = targets[j])) - { - if (neganswer) - *neganswer = 1; - - if (!extract_name(header, plen, &p2, name, 1, 10)) - return STAT_BOGUS; /* bad packet */ - - /* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */ - - /* For anything other than a DS record, this situation is OK if either - the answer is in an unsigned zone, or there's a NSEC records. */ - if (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons)) - { - /* Empty DS without NSECS */ - if (qtype == T_DS) - return STAT_BOGUS; - - if ((rc = zone_status(name, qclass, keyname, now)) != STAT_SECURE) - { - if (class) - *class = qclass; /* Class for NEED_DS or NEED_KEY */ - return rc; - } - - return STAT_BOGUS; /* signed zone, no NSECs */ - } - } + if (secure == STAT_SECURE) + for (j = 0; j <targetidx; j++) + if ((p2 = targets[j])) + { + if (neganswer) + *neganswer = 1; + + if (!extract_name(header, plen, &p2, name, 1, 10)) + return STAT_BOGUS; /* bad packet */ + + /* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */ + + /* For anything other than a DS record, this situation is OK if either + the answer is in an unsigned zone, or there's a NSEC records. */ + if (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl)) + { + /* Empty DS without NSECS */ + if (qtype == T_DS) + return STAT_BOGUS; + + if ((rc = zone_status(name, qclass, keyname, now)) != STAT_SECURE) + { + if (class) + *class = qclass; /* Class for NEED_DS or NEED_KEY */ + return rc; + } + + return STAT_BOGUS; /* signed zone, no NSECs */ + } + } return secure; } @@ -2023,19 +2023,11 @@ int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen) } size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, - int type, union mysockaddr *addr, int edns_pktsz) + int type, int edns_pktsz) { unsigned char *p; - char *types = querystr("dnssec-query", type); size_t ret; - if (addr->sa.sa_family == AF_INET) - log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types); -#ifdef HAVE_IPV6 - else - log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, name, (struct all_addr *)&addr->in6.sin6_addr, types); -#endif - header->qdcount = htons(1); header->ancount = htons(0); header->nscount = htons(0); diff --git a/src/domain.c b/src/domain.c index 04d7cf8..3dc96ab 100644 --- a/src/domain.c +++ b/src/domain.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -18,21 +18,14 @@ static struct cond_domain *search_domain(struct in_addr addr, struct cond_domain *c); -#ifdef HAVE_IPV6 static struct cond_domain *search_domain6(struct in6_addr *addr, struct cond_domain *c); -#endif -int is_name_synthetic(int flags, char *name, struct all_addr *addr) +int is_name_synthetic(int flags, char *name, union all_addr *addr) { char *p; struct cond_domain *c = NULL; - int prot = AF_INET; - -#ifdef HAVE_IPV6 - if (flags & F_IPV6) - prot = AF_INET6; -#endif + int prot = (flags & F_IPV6) ? AF_INET6 : AF_INET; for (c = daemon->synth_domains; c; c = c->next) { @@ -80,11 +73,10 @@ int is_name_synthetic(int flags, char *name, struct all_addr *addr) if (!c->is6 && index <= ntohl(c->end.s_addr) - ntohl(c->start.s_addr)) { - addr->addr.addr4.s_addr = htonl(ntohl(c->start.s_addr) + index); + addr->addr4.s_addr = htonl(ntohl(c->start.s_addr) + index); found = 1; } - } -#ifdef HAVE_IPV6 + } else { u64 index = atoll(tail); @@ -93,12 +85,11 @@ int is_name_synthetic(int flags, char *name, struct all_addr *addr) index <= addr6part(&c->end6) - addr6part(&c->start6)) { u64 start = addr6part(&c->start6); - addr->addr.addr6 = c->start6; - setaddr6part(&addr->addr.addr6, start + index); + addr->addr6 = c->start6; + setaddr6part(&addr->addr6, start + index); found = 1; } } -#endif } } else @@ -111,10 +102,8 @@ int is_name_synthetic(int flags, char *name, struct all_addr *addr) if ((c >='0' && c <= '9') || c == '-') continue; -#ifdef HAVE_IPV6 if (prot == AF_INET6 && ((c >='A' && c <= 'F') || (c >='a' && c <= 'f'))) continue; -#endif break; } @@ -124,7 +113,6 @@ int is_name_synthetic(int flags, char *name, struct all_addr *addr) *p = 0; -#ifdef HAVE_IPV6 if (prot == AF_INET6 && strstr(tail, "--ffff-") == tail) { /* special hack for v4-mapped. */ @@ -134,7 +122,6 @@ int is_name_synthetic(int flags, char *name, struct all_addr *addr) *p = '.'; } else -#endif { /* swap . or : for - */ for (p = tail; *p; p++) @@ -142,10 +129,8 @@ int is_name_synthetic(int flags, char *name, struct all_addr *addr) { if (prot == AF_INET) *p = '.'; -#ifdef HAVE_IPV6 else *p = ':'; -#endif } } @@ -154,22 +139,20 @@ int is_name_synthetic(int flags, char *name, struct all_addr *addr) if (prot == AF_INET) { if (!c->is6 && - ntohl(addr->addr.addr4.s_addr) >= ntohl(c->start.s_addr) && - ntohl(addr->addr.addr4.s_addr) <= ntohl(c->end.s_addr)) + ntohl(addr->addr4.s_addr) >= ntohl(c->start.s_addr) && + ntohl(addr->addr4.s_addr) <= ntohl(c->end.s_addr)) found = 1; } -#ifdef HAVE_IPV6 else { - u64 addrpart = addr6part(&addr->addr.addr6); + u64 addrpart = addr6part(&addr->addr6); if (c->is6 && - is_same_net6(&addr->addr.addr6, &c->start6, 64) && + is_same_net6(&addr->addr6, &c->start6, 64) && addrpart >= addr6part(&c->start6) && addrpart <= addr6part(&c->end6)) found = 1; } -#endif } } @@ -190,18 +173,18 @@ int is_name_synthetic(int flags, char *name, struct all_addr *addr) } -int is_rev_synth(int flag, struct all_addr *addr, char *name) +int is_rev_synth(int flag, union all_addr *addr, char *name) { struct cond_domain *c; - if (flag & F_IPV4 && (c = search_domain(addr->addr.addr4, daemon->synth_domains))) + if (flag & F_IPV4 && (c = search_domain(addr->addr4, daemon->synth_domains))) { char *p; *name = 0; if (c->indexed) { - unsigned int index = ntohl(addr->addr.addr4.s_addr) - ntohl(c->start.s_addr); + unsigned int index = ntohl(addr->addr4.s_addr) - ntohl(c->start.s_addr); snprintf(name, MAXDNAME, "%s%u", c->prefix ? c->prefix : "", index); } else @@ -209,7 +192,7 @@ int is_rev_synth(int flag, struct all_addr *addr, char *name) if (c->prefix) strncpy(name, c->prefix, MAXDNAME - ADDRSTRLEN); - inet_ntop(AF_INET, &addr->addr.addr4, name + strlen(name), ADDRSTRLEN); + inet_ntop(AF_INET, &addr->addr4, name + strlen(name), ADDRSTRLEN); for (p = name; *p; p++) if (*p == '.') *p = '-'; @@ -221,15 +204,14 @@ int is_rev_synth(int flag, struct all_addr *addr, char *name) return 1; } -#ifdef HAVE_IPV6 - if (flag & F_IPV6 && (c = search_domain6(&addr->addr.addr6, daemon->synth_domains))) + if ((flag & F_IPV6) && (c = search_domain6(&addr->addr6, daemon->synth_domains))) { char *p; *name = 0; if (c->indexed) { - u64 index = addr6part(&addr->addr.addr6) - addr6part(&c->start6); + u64 index = addr6part(&addr->addr6) - addr6part(&c->start6); snprintf(name, MAXDNAME, "%s%llu", c->prefix ? c->prefix : "", index); } else @@ -237,14 +219,14 @@ int is_rev_synth(int flag, struct all_addr *addr, char *name) if (c->prefix) strncpy(name, c->prefix, MAXDNAME - ADDRSTRLEN); - inet_ntop(AF_INET6, &addr->addr.addr6, name + strlen(name), ADDRSTRLEN); + inet_ntop(AF_INET6, &addr->addr6, name + strlen(name), ADDRSTRLEN); /* IPv6 presentation address can start with ":", but valid domain names cannot start with "-" so prepend a zero in that case. */ if (!c->prefix && *name == ':') { *name = '0'; - inet_ntop(AF_INET6, &addr->addr.addr6, name+1, ADDRSTRLEN); + inet_ntop(AF_INET6, &addr->addr6, name+1, ADDRSTRLEN); } /* V4-mapped have periods.... */ @@ -259,7 +241,6 @@ int is_rev_synth(int flag, struct all_addr *addr, char *name) return 1; } -#endif return 0; } @@ -286,7 +267,7 @@ char *get_domain(struct in_addr addr) return daemon->domain_suffix; } -#ifdef HAVE_IPV6 + static struct cond_domain *search_domain6(struct in6_addr *addr, struct cond_domain *c) { u64 addrpart = addr6part(addr); @@ -310,4 +291,3 @@ char *get_domain6(struct in6_addr *addr) return daemon->domain_suffix; } -#endif diff --git a/src/dump.c b/src/dump.c new file mode 100644 index 0000000..9bd3a5f --- /dev/null +++ b/src/dump.c @@ -0,0 +1,211 @@ +/* dnsmasq is Copyright (c) 2000-2020 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 <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + +#ifdef HAVE_DUMPFILE + +static u32 packet_count; + +/* https://wiki.wireshark.org/Development/LibpcapFileFormat */ +struct pcap_hdr_s { + u32 magic_number; /* magic number */ + u16 version_major; /* major version number */ + u16 version_minor; /* minor version number */ + u32 thiszone; /* GMT to local correction */ + u32 sigfigs; /* accuracy of timestamps */ + u32 snaplen; /* max length of captured packets, in octets */ + u32 network; /* data link type */ +}; + +struct pcaprec_hdr_s { + u32 ts_sec; /* timestamp seconds */ + u32 ts_usec; /* timestamp microseconds */ + u32 incl_len; /* number of octets of packet saved in file */ + u32 orig_len; /* actual length of packet */ +}; + + +void dump_init(void) +{ + struct stat buf; + struct pcap_hdr_s header; + struct pcaprec_hdr_s pcap_header; + + packet_count = 0; + + if (stat(daemon->dump_file, &buf) == -1) + { + /* doesn't exist, create and add header */ + header.magic_number = 0xa1b2c3d4; + header.version_major = 2; + header.version_minor = 4; + header.thiszone = 0; + header.sigfigs = 0; + header.snaplen = daemon->edns_pktsz + 200; /* slop for IP/UDP headers */ + header.network = 101; /* DLT_RAW http://www.tcpdump.org/linktypes.html */ + + if (errno != ENOENT || + (daemon->dumpfd = creat(daemon->dump_file, S_IRUSR | S_IWUSR)) == -1 || + !read_write(daemon->dumpfd, (void *)&header, sizeof(header), 0)) + die(_("cannot create %s: %s"), daemon->dump_file, EC_FILE); + } + else if ((daemon->dumpfd = open(daemon->dump_file, O_APPEND | O_RDWR)) == -1 || + !read_write(daemon->dumpfd, (void *)&header, sizeof(header), 1)) + die(_("cannot access %s: %s"), daemon->dump_file, EC_FILE); + else if (header.magic_number != 0xa1b2c3d4) + die(_("bad header in %s"), daemon->dump_file, EC_FILE); + else + { + /* count existing records */ + while (read_write(daemon->dumpfd, (void *)&pcap_header, sizeof(pcap_header), 1)) + { + lseek(daemon->dumpfd, pcap_header.incl_len, SEEK_CUR); + packet_count++; + } + } +} + +void dump_packet(int mask, void *packet, size_t len, union mysockaddr *src, union mysockaddr *dst) +{ + struct ip ip; + struct ip6_hdr ip6; + int family; + struct udphdr { + u16 uh_sport; /* source port */ + u16 uh_dport; /* destination port */ + u16 uh_ulen; /* udp length */ + u16 uh_sum; /* udp checksum */ + } udp; + struct pcaprec_hdr_s pcap_header; + struct timeval time; + u32 i, sum; + void *iphdr; + size_t ipsz; + int rc; + + if (daemon->dumpfd == -1 || !(mask & daemon->dump_mask)) + return; + + /* So wireshark can Id the packet. */ + udp.uh_sport = udp.uh_dport = htons(NAMESERVER_PORT); + + if (src) + family = src->sa.sa_family; + else + family = dst->sa.sa_family; + + if (family == AF_INET6) + { + iphdr = &ip6; + ipsz = sizeof(ip6); + memset(&ip6, 0, sizeof(ip6)); + + ip6.ip6_vfc = 6 << 4; + ip6.ip6_plen = htons(sizeof(struct udphdr) + len); + ip6.ip6_nxt = IPPROTO_UDP; + ip6.ip6_hops = 64; + + if (src) + { + memcpy(&ip6.ip6_src, &src->in6.sin6_addr, IN6ADDRSZ); + udp.uh_sport = src->in6.sin6_port; + } + + if (dst) + { + memcpy(&ip6.ip6_dst, &dst->in6.sin6_addr, IN6ADDRSZ); + udp.uh_dport = dst->in6.sin6_port; + } + + /* start UDP checksum */ + for (sum = 0, i = 0; i < IN6ADDRSZ; i+=2) + { + sum += ip6.ip6_src.s6_addr[i] + (ip6.ip6_src.s6_addr[i+1] << 8) ; + sum += ip6.ip6_dst.s6_addr[i] + (ip6.ip6_dst.s6_addr[i+1] << 8) ; + + } + } + else + { + iphdr = &ip; + ipsz = sizeof(ip); + memset(&ip, 0, sizeof(ip)); + + ip.ip_v = IPVERSION; + ip.ip_hl = sizeof(struct ip) / 4; + ip.ip_len = htons(sizeof(struct ip) + sizeof(struct udphdr) + len); + ip.ip_ttl = IPDEFTTL; + ip.ip_p = IPPROTO_UDP; + + if (src) + { + ip.ip_src = src->in.sin_addr; + udp.uh_sport = src->in.sin_port; + } + + if (dst) + { + ip.ip_dst = dst->in.sin_addr; + udp.uh_dport = dst->in.sin_port; + } + + ip.ip_sum = 0; + for (sum = 0, i = 0; i < sizeof(struct ip) / 2; i++) + sum += ((u16 *)&ip)[i]; + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + ip.ip_sum = (sum == 0xffff) ? sum : ~sum; + + /* start UDP checksum */ + sum = ip.ip_src.s_addr & 0xffff; + sum += (ip.ip_src.s_addr >> 16) & 0xffff; + sum += ip.ip_dst.s_addr & 0xffff; + sum += (ip.ip_dst.s_addr >> 16) & 0xffff; + } + + if (len & 1) + ((unsigned char *)packet)[len] = 0; /* for checksum, in case length is odd. */ + + udp.uh_sum = 0; + udp.uh_ulen = htons(sizeof(struct udphdr) + len); + sum += htons(IPPROTO_UDP); + sum += htons(sizeof(struct udphdr) + len); + for (i = 0; i < sizeof(struct udphdr)/2; i++) + sum += ((u16 *)&udp)[i]; + for (i = 0; i < (len + 1) / 2; i++) + sum += ((u16 *)packet)[i]; + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + udp.uh_sum = (sum == 0xffff) ? sum : ~sum; + + rc = gettimeofday(&time, NULL); + pcap_header.ts_sec = time.tv_sec; + pcap_header.ts_usec = time.tv_usec; + pcap_header.incl_len = pcap_header.orig_len = ipsz + sizeof(udp) + len; + + if (rc == -1 || + !read_write(daemon->dumpfd, (void *)&pcap_header, sizeof(pcap_header), 0) || + !read_write(daemon->dumpfd, iphdr, ipsz, 0) || + !read_write(daemon->dumpfd, (void *)&udp, sizeof(udp), 0) || + !read_write(daemon->dumpfd, (void *)packet, len, 0)) + my_syslog(LOG_ERR, _("failed to write packet dump")); + else + my_syslog(LOG_INFO, _("dumping UDP packet %u mask 0x%04x"), ++packet_count, mask); + +} + +#endif diff --git a/src/edns0.c b/src/edns0.c index af33877..d75d3cc 100644 --- a/src/edns0.c +++ b/src/edns0.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -301,20 +301,14 @@ static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *lim struct subnet_opt { u16 family; - u8 source_netmask, scope_netmask; -#ifdef HAVE_IPV6 + u8 source_netmask, scope_netmask; u8 addr[IN6ADDRSZ]; -#else - u8 addr[INADDRSZ]; -#endif }; static void *get_addrp(union mysockaddr *addr, const short family) { -#ifdef HAVE_IPV6 if (family == AF_INET6) return &addr->in6.sin6_addr; -#endif return &addr->in.sin_addr; } @@ -330,7 +324,6 @@ static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source) opt->source_netmask = 0; opt->scope_netmask = 0; -#ifdef HAVE_IPV6 if (source->sa.sa_family == AF_INET6 && daemon->add_subnet6) { opt->source_netmask = daemon->add_subnet6->mask; @@ -342,7 +335,6 @@ static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source) else addrp = &source->in6.sin6_addr; } -#endif if (source->sa.sa_family == AF_INET && daemon->add_subnet4) { @@ -356,11 +348,7 @@ static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source) addrp = &source->in.sin_addr; } -#ifdef HAVE_IPV6 opt->family = htons(sa_family == AF_INET6 ? 2 : 1); -#else - opt->family = htons(1); -#endif len = 0; diff --git a/src/forward.c b/src/forward.c index cdd11d3..9c2b2c6 100644 --- a/src/forward.c +++ b/src/forward.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -26,7 +26,7 @@ static void free_frec(struct frec *f); /* Send a UDP packet with its source address set as "source" unless nowild is true, when we just send it with the kernel default */ int send_from(int fd, int nowild, char *packet, size_t len, - union mysockaddr *to, struct all_addr *source, + union mysockaddr *to, union all_addr *source, unsigned int iface) { struct msghdr msg; @@ -38,9 +38,7 @@ int send_from(int fd, int nowild, char *packet, size_t len, #elif defined(IP_SENDSRCADDR) char control[CMSG_SPACE(sizeof(struct in_addr))]; #endif -#ifdef HAVE_IPV6 char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; -#endif } control_u; iov[0].iov_base = packet; @@ -66,47 +64,49 @@ int send_from(int fd, int nowild, char *packet, size_t len, #if defined(HAVE_LINUX_NETWORK) struct in_pktinfo p; p.ipi_ifindex = 0; - p.ipi_spec_dst = source->addr.addr4; + p.ipi_spec_dst = source->addr4; + msg.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo)); memcpy(CMSG_DATA(cmptr), &p, sizeof(p)); - msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); cmptr->cmsg_level = IPPROTO_IP; cmptr->cmsg_type = IP_PKTINFO; #elif defined(IP_SENDSRCADDR) - memcpy(CMSG_DATA(cmptr), &(source->addr.addr4), sizeof(source->addr.addr4)); - msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); + msg.msg_controllen = CMSG_SPACE(sizeof(struct in_addr)); + memcpy(CMSG_DATA(cmptr), &(source->addr4), sizeof(source->addr4)); + cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); cmptr->cmsg_level = IPPROTO_IP; cmptr->cmsg_type = IP_SENDSRCADDR; #endif } else -#ifdef HAVE_IPV6 { struct in6_pktinfo p; p.ipi6_ifindex = iface; /* Need iface for IPv6 to handle link-local addrs */ - p.ipi6_addr = source->addr.addr6; + p.ipi6_addr = source->addr6; + msg.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)); memcpy(CMSG_DATA(cmptr), &p, sizeof(p)); - msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + cmptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); cmptr->cmsg_type = daemon->v6pktinfo; cmptr->cmsg_level = IPPROTO_IPV6; } -#else - (void)iface; /* eliminate warning */ -#endif } while (retry_send(sendmsg(fd, &msg, 0))); - /* If interface is still in DAD, EINVAL results - ignore that. */ - if (errno != 0 && errno != EINVAL) + if (errno != 0) { - my_syslog(LOG_ERR, _("failed to send packet: %s"), strerror(errno)); +#ifdef HAVE_LINUX_NETWORK + /* If interface is still in DAD, EINVAL results - ignore that. */ + if (errno != EINVAL) + my_syslog(LOG_ERR, _("failed to send packet: %s"), strerror(errno)); +#endif return 0; } return 1; } -static unsigned int search_servers(time_t now, struct all_addr **addrpp, unsigned int qtype, +static unsigned int search_servers(time_t now, union all_addr **addrpp, unsigned int qtype, char *qdomain, int *type, char **domain, int *norebind) { @@ -118,6 +118,7 @@ static unsigned int search_servers(time_t now, struct all_addr **addrpp, unsigne unsigned int matchlen = 0; struct server *serv; unsigned int flags = 0; + static union all_addr zero; for (serv = daemon->servers; serv; serv=serv->next) if (qtype == F_DNSSECOK && !(serv->flags & SERV_DO_DNSSEC)) @@ -127,19 +128,26 @@ static unsigned int search_servers(time_t now, struct all_addr **addrpp, unsigne { unsigned int sflag = serv->addr.sa.sa_family == AF_INET ? F_IPV4 : F_IPV6; *type = SERV_FOR_NODOTS; - if (serv->flags & SERV_NO_ADDR) + if ((serv->flags & SERV_NO_REBIND) && norebind) + *norebind = 1; + else if (serv->flags & SERV_NO_ADDR) flags = F_NXDOMAIN; - else if (serv->flags & SERV_LITERAL_ADDRESS) + else if (serv->flags & SERV_LITERAL_ADDRESS) { - if (sflag & qtype) + /* literal address = '#' -> return all-zero address for IPv4 and IPv6 */ + if ((serv->flags & SERV_USE_RESOLV) && (qtype & (F_IPV6 | F_IPV4))) + { + memset(&zero, 0, sizeof(zero)); + flags = qtype; + *addrpp = &zero; + } + else if (sflag & qtype) { flags = sflag; if (serv->addr.sa.sa_family == AF_INET) - *addrpp = (struct all_addr *)&serv->addr.in.sin_addr; -#ifdef HAVE_IPV6 + *addrpp = (union all_addr *)&serv->addr.in.sin_addr; else - *addrpp = (struct all_addr *)&serv->addr.in6.sin6_addr; -#endif + *addrpp = (union all_addr *)&serv->addr.in6.sin6_addr; } else if (!flags || (flags & F_NXDOMAIN)) flags = F_NOERR; @@ -184,15 +192,20 @@ static unsigned int search_servers(time_t now, struct all_addr **addrpp, unsigne flags = F_NXDOMAIN; else if (serv->flags & SERV_LITERAL_ADDRESS) { - if (sflag & qtype) + /* literal address = '#' -> return all-zero address for IPv4 and IPv6 */ + if ((serv->flags & SERV_USE_RESOLV) && (qtype & (F_IPV6 | F_IPV4))) + { + memset(&zero, 0, sizeof(zero)); + flags = qtype; + *addrpp = &zero; + } + else if (sflag & qtype) { flags = sflag; if (serv->addr.sa.sa_family == AF_INET) - *addrpp = (struct all_addr *)&serv->addr.in.sin_addr; -#ifdef HAVE_IPV6 + *addrpp = (union all_addr *)&serv->addr.in.sin_addr; else - *addrpp = (struct all_addr *)&serv->addr.in6.sin6_addr; -#endif + *addrpp = (union all_addr *)&serv->addr.in6.sin6_addr; } else if (!flags || (flags & F_NXDOMAIN)) flags = F_NOERR; @@ -214,12 +227,16 @@ static unsigned int search_servers(time_t now, struct all_addr **addrpp, unsigne if (flags) { - int logflags = 0; - - if (flags == F_NXDOMAIN || flags == F_NOERR) - logflags = F_NEG | qtype; - - log_query(logflags | flags | F_CONFIG | F_FORWARD, qdomain, *addrpp, NULL); + if (flags == F_NXDOMAIN || flags == F_NOERR) + log_query(flags | qtype | F_NEG | F_CONFIG | F_FORWARD, qdomain, NULL, NULL); + else + { + /* handle F_IPV4 and F_IPV6 set on ANY query to 0.0.0.0/:: domain. */ + if (flags & F_IPV4) + log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV6, qdomain, *addrpp, NULL); + if (flags & F_IPV6) + log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV4, qdomain, *addrpp, NULL); + } } else if ((*type) & SERV_USE_RESOLV) { @@ -230,13 +247,13 @@ static unsigned int search_servers(time_t now, struct all_addr **addrpp, unsigne } static int forward_query(int udpfd, union mysockaddr *udpaddr, - struct all_addr *dst_addr, unsigned int dst_iface, + union all_addr *dst_addr, unsigned int dst_iface, struct dns_header *header, size_t plen, time_t now, struct frec *forward, int ad_reqd, int do_bit) { char *domain = NULL; int type = SERV_DO_DNSSEC, norebind = 0; - struct all_addr *addrp = NULL; + union all_addr *addrp = NULL; unsigned int flags = 0; struct server *start = NULL; #ifdef HAVE_DNSSEC @@ -246,9 +263,9 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, unsigned int crc = questions_crc(header, plen, daemon->namebuff); void *hash = &crc; #endif - unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); - - (void)do_bit; + unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); + unsigned char *oph = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL); + (void)do_bit; /* may be no servers available. */ if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))) @@ -280,27 +297,24 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, PUTSHORT(SAFE_PKTSZ, pheader); if (forward->sentto->addr.sa.sa_family == AF_INET) - log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (struct all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec"); -#ifdef HAVE_IPV6 + log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (union all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec"); else - log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (struct all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec"); -#endif + log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (union all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec"); + if (forward->sentto->sfd) fd = forward->sentto->sfd->fd; else { -#ifdef HAVE_IPV6 if (forward->sentto->addr.sa.sa_family == AF_INET6) fd = forward->rfd6->fd; else -#endif fd = forward->rfd4->fd; } - while (retry_send( sendto(fd, (char *)header, plen, 0, - &forward->sentto->addr.sa, - sa_len(&forward->sentto->addr)))); + while (retry_send(sendto(fd, (char *)header, plen, 0, + &forward->sentto->addr.sa, + sa_len(&forward->sentto->addr)))); return 1; } @@ -334,7 +348,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, type &= ~SERV_DO_DNSSEC; if (daemon->servers && !flags) - forward = get_new_frec(now, NULL, 0); + forward = get_new_frec(now, NULL, NULL); /* table full - flags == 0, return REFUSED */ if (forward) @@ -399,7 +413,6 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, struct server *firstsentto = start; int subnet, forwarded = 0; size_t edns0_len; - unsigned char *oph = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL); unsigned char *pheader; /* If a query is retried, use the log_id for the retry when logging the answer. */ @@ -455,7 +468,6 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, fd = start->sfd->fd; else { -#ifdef HAVE_IPV6 if (start->addr.sa.sa_family == AF_INET6) { if (!forward->rfd6 && @@ -465,7 +477,6 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, fd = forward->rfd6->fd; } else -#endif { if (!forward->rfd4 && !(forward->rfd4 = allocate_rfd(AF_INET))) @@ -508,6 +519,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, if (errno == 0) { +#ifdef HAVE_DUMPFILE + dump_packet(DUMP_UP_QUERY, (void *)header, plen, NULL, &start->addr); +#endif + /* Keep info in case we want to re-send this packet */ daemon->srv_save = start; daemon->packet_len = plen; @@ -516,12 +531,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, strcpy(daemon->namebuff, "query"); if (start->addr.sa.sa_family == AF_INET) log_query(F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff, - (struct all_addr *)&start->addr.in.sin_addr, NULL); -#ifdef HAVE_IPV6 + (union all_addr *)&start->addr.in.sin_addr, NULL); else log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff, - (struct all_addr *)&start->addr.in6.sin6_addr, NULL); -#endif + (union all_addr *)&start->addr.in6.sin6_addr, NULL); start->queries++; forwarded = 1; forward->sentto = start; @@ -550,6 +563,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, if (udpfd != -1) { plen = setup_reply(header, plen, addrp, flags, daemon->local_ttl); + if (oph) + plen = add_pseudoheader(header, plen, ((unsigned char *) header) + PACKETSZ, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); send_from(udpfd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (char *)header, plen, udpaddr, dst_addr, dst_iface); } @@ -563,6 +578,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server unsigned char *pheader, *sizep; char **sets = 0; int munged = 0, is_sign; + unsigned int rcode = RCODE(header); size_t plen; (void)ad_reqd; @@ -593,6 +609,9 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign, NULL))) { + /* Get extended RCODE. */ + rcode |= sizep[2] << 4; + if (check_subnet && !check_source(header, plen, pheader, query_source)) { my_syslog(LOG_WARNING, _("discarding DNS reply: subnet option mismatch")); @@ -641,20 +660,29 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server if (!is_sign && !option_bool(OPT_DNSSEC_PROXY)) header->hb4 &= ~HB4_AD; - if (OPCODE(header) != QUERY || (RCODE(header) != NOERROR && RCODE(header) != NXDOMAIN)) + if (OPCODE(header) != QUERY) return resize_packet(header, n, pheader, plen); + + if (rcode != NOERROR && rcode != NXDOMAIN) + { + union all_addr a; + a.log.rcode = rcode; + log_query(F_UPSTREAM | F_RCODE, "error", &a, NULL); + + return resize_packet(header, n, pheader, plen); + } /* Complain loudly if the upstream server is non-recursive. */ - if (!(header->hb4 & HB4_RA) && RCODE(header) == NOERROR && + if (!(header->hb4 & HB4_RA) && rcode == NOERROR && server && !(server->flags & SERV_WARNED_RECURSIVE)) { - prettyprint_addr(&server->addr, daemon->namebuff); + (void)prettyprint_addr(&server->addr, daemon->namebuff); my_syslog(LOG_WARNING, _("nameserver %s refused to do a recursive query"), daemon->namebuff); if (!option_bool(OPT_LOG)) server->flags |= SERV_WARNED_RECURSIVE; } - if (daemon->bogus_addr && RCODE(header) != NXDOMAIN && + if (daemon->bogus_addr && rcode != NXDOMAIN && check_for_bogus_wildcard(header, n, daemon->namebuff, daemon->bogus_addr, now)) { munged = 1; @@ -666,7 +694,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server { int doctored = 0; - if (RCODE(header) == NXDOMAIN && + if (rcode == NXDOMAIN && extract_request(header, n, daemon->namebuff, NULL) && check_for_local_domain(daemon->namebuff, now)) { @@ -749,14 +777,11 @@ void reply_query(int fd, int family, time_t now) daemon->srv_save = NULL; /* Determine the address of the server replying so that we can mark that as good */ - serveraddr.sa.sa_family = family; -#ifdef HAVE_IPV6 - if (serveraddr.sa.sa_family == AF_INET6) + if ((serveraddr.sa.sa_family = family) == AF_INET6) serveraddr.in6.sin6_flowinfo = 0; -#endif header = (struct dns_header *)daemon->packet; - + if (n < (int)sizeof(struct dns_header) || !(header->hb3 & HB3_QR)) return; @@ -783,6 +808,11 @@ void reply_query(int fd, int family, time_t now) if (!(forward = lookup_frec(ntohs(header->id), hash))) return; +#ifdef HAVE_DUMPFILE + dump_packet((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_REPLY : DUMP_UP_REPLY, + (void *)header, n, &serveraddr, NULL); +#endif + /* log_query gets called indirectly all over the place, so pass these in global variables - sorry. */ daemon->log_display_id = forward->log_id; @@ -794,7 +824,7 @@ void reply_query(int fd, int family, time_t now) /* Note: if we send extra options in the EDNS0 header, we can't recreate the query from the reply. */ - if (RCODE(header) == REFUSED && + if ((RCODE(header) == REFUSED || RCODE(header) == SERVFAIL) && forward->forwardall == 0 && !(forward->flags & FREC_HAS_EXTRADATA)) /* for broken servers, attempt to send to another one. */ @@ -803,6 +833,70 @@ void reply_query(int fd, int family, time_t now) size_t plen; int is_sign; +#ifdef HAVE_DNSSEC + /* For DNSSEC originated queries, just retry the query to the same server. */ + if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) + { + struct server *start; + + blockdata_retrieve(forward->stash, forward->stash_len, (void *)header); + plen = forward->stash_len; + + forward->forwardall = 2; /* only retry once */ + start = forward->sentto; + + /* for non-domain specific servers, see if we can find another to try. */ + if ((forward->sentto->flags & SERV_TYPE) == 0) + while (1) + { + if (!(start = start->next)) + start = daemon->servers; + if (start == forward->sentto) + break; + + if ((start->flags & SERV_TYPE) == 0 && + (start->flags & SERV_DO_DNSSEC)) + break; + } + + + if (start->sfd) + fd = start->sfd->fd; + else + { + if (start->addr.sa.sa_family == AF_INET6) + { + /* may have changed family */ + if (!forward->rfd6) + forward->rfd6 = allocate_rfd(AF_INET6); + fd = forward->rfd6->fd; + } + else + { + /* may have changed family */ + if (!forward->rfd4) + forward->rfd4 = allocate_rfd(AF_INET); + fd = forward->rfd4->fd; + } + } + +#ifdef HAVE_DUMPFILE + dump_packet(DUMP_SEC_QUERY, (void *)header, (size_t)plen, NULL, &start->addr); +#endif + + while (retry_send(sendto(fd, (char *)header, plen, 0, + &start->addr.sa, + sa_len(&start->addr)))); + + if (start->addr.sa.sa_family == AF_INET) + log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (union all_addr *)&start->addr.in.sin_addr, "dnssec"); + else + log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (union all_addr *)&start->addr.in6.sin6_addr, "dnssec"); + + return; + } +#endif + /* In strict order mode, there must be a server later in the chain left to send to, otherwise without the forwardall mechanism, code further on will cycle around the list forwever if they @@ -861,12 +955,12 @@ void reply_query(int fd, int family, time_t now) /* We tried resending to this server with a smaller maximum size and got an answer. Make that permanent. To avoid reduxing the packet size for a single dropped packet, only do this when we get a truncated answer, or one larger than the safe size. */ - if (server && server->edns_pktsz > SAFE_PKTSZ && (forward->flags & FREC_TEST_PKTSZ) && + if (forward->sentto->edns_pktsz > SAFE_PKTSZ && (forward->flags & FREC_TEST_PKTSZ) && ((header->hb3 & HB3_TC) || n >= SAFE_PKTSZ)) { - server->edns_pktsz = SAFE_PKTSZ; - server->pktsz_reduced = now; - prettyprint_addr(&server->addr, daemon->addrbuff); + forward->sentto->edns_pktsz = SAFE_PKTSZ; + forward->sentto->pktsz_reduced = now; + (void)prettyprint_addr(&forward->sentto->addr, daemon->addrbuff); my_syslog(LOG_WARNING, _("reducing DNS packet size for nameserver %s to %d"), daemon->addrbuff, SAFE_PKTSZ); } @@ -875,8 +969,7 @@ void reply_query(int fd, int family, time_t now) we get a good reply from another server. Kill it when we've had replies from all to avoid filling the forwarding table when everything is broken */ - if (forward->forwardall == 0 || --forward->forwardall == 1 || - (RCODE(header) != REFUSED && RCODE(header) != SERVFAIL)) + if (forward->forwardall == 0 || --forward->forwardall == 1 || RCODE(header) != REFUSED) { int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; @@ -889,7 +982,7 @@ void reply_query(int fd, int family, time_t now) no_cache_dnssec = 1; #ifdef HAVE_DNSSEC - if (server && (server->flags & SERV_DO_DNSSEC) && + if ((forward->sentto->flags & SERV_DO_DNSSEC) && option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED)) { int status = 0; @@ -919,8 +1012,13 @@ void reply_query(int fd, int family, time_t now) status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); else status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, - option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC), - NULL, NULL); + !option_bool(OPT_DNSSEC_IGN_NS) && (forward->sentto->flags & SERV_DO_DNSSEC), + NULL, NULL, NULL); +#ifdef HAVE_DUMPFILE + if (status == STAT_BOGUS) + dump_packet((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_BOGUS : DUMP_BOGUS, + header, (size_t)n, &serveraddr, NULL); +#endif } /* Can't validate, as we're missing key data. Put this @@ -941,11 +1039,13 @@ void reply_query(int fd, int family, time_t now) /* Find the original query that started it all.... */ for (orig = forward; orig->dependent; orig = orig->dependent); - if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1))) + /* Make sure we don't expire and free the orig frec during the + allocation of a new one. */ + if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, orig))) status = STAT_ABANDONED; else { - int fd, type = SERV_DO_DNSSEC; + int querytype, fd, type = SERV_DO_DNSSEC; struct frec *next = new->next; char *domain; @@ -958,12 +1058,13 @@ void reply_query(int fd, int family, time_t now) servers for domains are involved. */ if (search_servers(now, NULL, F_DNSSECOK, daemon->keyname, &type, &domain, NULL) == 0) { - struct server *start = server, *new_server = NULL; + struct server *start, *new_server = NULL; + start = server = forward->sentto; while (1) { if (type == (start->flags & (SERV_TYPE | SERV_DO_DNSSEC)) && - (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) && + ((type & SERV_TYPE) != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) && !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))) { new_server = start; @@ -986,10 +1087,9 @@ void reply_query(int fd, int family, time_t now) new->sentto = server; new->rfd4 = NULL; -#ifdef HAVE_IPV6 new->rfd6 = NULL; -#endif - new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY); + new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA); + new->forwardall = 0; new->dependent = forward; /* to find query awaiting new one. */ forward->blocking_query = new; /* for garbage cleaning */ @@ -997,15 +1097,24 @@ void reply_query(int fd, int family, time_t now) if (status == STAT_NEED_KEY) { new->flags |= FREC_DNSKEY_QUERY; - nn = dnssec_generate_query(header, ((unsigned char *) header) + server->edns_pktsz, - daemon->keyname, forward->class, T_DNSKEY, &server->addr, server->edns_pktsz); + querytype = T_DNSKEY; } else { new->flags |= FREC_DS_QUERY; - nn = dnssec_generate_query(header,((unsigned char *) header) + server->edns_pktsz, - daemon->keyname, forward->class, T_DS, &server->addr, server->edns_pktsz); + querytype = T_DS; } + + nn = dnssec_generate_query(header,((unsigned char *) header) + server->edns_pktsz, + daemon->keyname, forward->class, querytype, server->edns_pktsz); + + if (server->addr.sa.sa_family == AF_INET) + log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, daemon->keyname, (union all_addr *)&(server->addr.in.sin_addr), + querystr("dnssec-query", querytype)); + else + log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, daemon->keyname, (union all_addr *)&(server->addr.in6.sin6_addr), + querystr("dnssec-query", querytype)); + if ((hash = hash_questions(header, nn, daemon->namebuff))) memcpy(new->hash, hash, HASH_SIZE); new->new_id = get_id(); @@ -1022,14 +1131,12 @@ void reply_query(int fd, int family, time_t now) else { fd = -1; -#ifdef HAVE_IPV6 if (server->addr.sa.sa_family == AF_INET6) { if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6))) fd = new->rfd6->fd; } else -#endif { if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET))) fd = new->rfd4->fd; @@ -1047,6 +1154,11 @@ void reply_query(int fd, int family, time_t now) setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); } #endif + +#ifdef HAVE_DUMPFILE + dump_packet(DUMP_SEC_QUERY, (void *)header, (size_t)nn, NULL, &server->addr); +#endif + while (retry_send(sendto(fd, (char *)header, nn, 0, &server->addr.sa, sa_len(&server->addr)))); @@ -1090,7 +1202,7 @@ void reply_query(int fd, int family, time_t now) if (status == STAT_BOGUS && extract_request(header, n, daemon->namebuff, NULL)) domain = daemon->namebuff; - log_query(F_KEYTAG | F_SECSTAT, domain, NULL, result); + log_query(F_SECSTAT, domain, NULL, result); } if (status == STAT_SECURE) @@ -1101,8 +1213,9 @@ void reply_query(int fd, int family, time_t now) bogusanswer = 1; } } -#endif - + +#endif + /* restore CD bit to the value in the query */ if (forward->flags & FREC_CHECKING_DISABLED) header->hb4 |= HB4_CD; @@ -1128,6 +1241,11 @@ void reply_query(int fd, int family, time_t now) nn = resize_packet(header, nn, NULL, 0); } #endif + +#ifdef HAVE_DUMPFILE + dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &forward->source); +#endif + send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, &forward->source, &forward->dest, forward->iface); } @@ -1142,7 +1260,7 @@ void receive_query(struct listener *listen, time_t now) union mysockaddr source_addr; unsigned char *pheader; unsigned short type, udp_size = PACKETSZ; /* default if no EDNS0 */ - struct all_addr dst_addr; + union all_addr dst_addr; struct in_addr netmask, dst_addr_4; size_t m; ssize_t n; @@ -1155,9 +1273,7 @@ void receive_query(struct listener *listen, time_t now) struct cmsghdr *cmptr; union { struct cmsghdr align; /* this ensures alignment */ -#ifdef HAVE_IPV6 char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; -#endif #if defined(HAVE_LINUX_NETWORK) char control[CMSG_SPACE(sizeof(struct in_pktinfo))]; #elif defined(IP_RECVDSTADDR) && defined(HAVE_SOLARIS_NETWORK) @@ -1168,26 +1284,23 @@ void receive_query(struct listener *listen, time_t now) CMSG_SPACE(sizeof(struct sockaddr_dl))]; #endif } control_u; -#ifdef HAVE_IPV6 + int family = listen->addr.sa.sa_family; /* Can always get recvd interface for IPv6 */ - int check_dst = !option_bool(OPT_NOWILD) || listen->family == AF_INET6; -#else - int check_dst = !option_bool(OPT_NOWILD); -#endif + int check_dst = !option_bool(OPT_NOWILD) || family == AF_INET6; /* packet buffer overwritten */ daemon->srv_save = NULL; - dst_addr_4.s_addr = dst_addr.addr.addr4.s_addr = 0; + dst_addr_4.s_addr = dst_addr.addr4.s_addr = 0; netmask.s_addr = 0; if (option_bool(OPT_NOWILD) && listen->iface) { auth_dns = listen->iface->dns_auth; - if (listen->family == AF_INET) + if (family == AF_INET) { - dst_addr_4 = dst_addr.addr.addr4 = listen->iface->addr.in.sin_addr; + dst_addr_4 = dst_addr.addr4 = listen->iface->addr.in.sin_addr; netmask = listen->iface->netmask; } } @@ -1215,16 +1328,15 @@ void receive_query(struct listener *listen, time_t now) information disclosure. */ memset(daemon->packet + n, 0, daemon->edns_pktsz - n); - source_addr.sa.sa_family = listen->family; + source_addr.sa.sa_family = family; - if (listen->family == AF_INET) + if (family == AF_INET) { /* Source-port == 0 is an error, we can't send back to that. http://www.ietf.org/mail-archive/web/dnsop/current/msg11441.html */ if (source_addr.in.sin_port == 0) return; } -#ifdef HAVE_IPV6 else { /* Source-port == 0 is an error, we can't send back to that. */ @@ -1232,29 +1344,27 @@ void receive_query(struct listener *listen, time_t now) return; source_addr.in6.sin6_flowinfo = 0; } -#endif /* We can be configured to only accept queries from at-most-one-hop-away addresses. */ if (option_bool(OPT_LOCAL_SERVICE)) { struct addrlist *addr; -#ifdef HAVE_IPV6 - if (listen->family == AF_INET6) + + if (family == AF_INET6) { for (addr = daemon->interface_addrs; addr; addr = addr->next) if ((addr->flags & ADDRLIST_IPV6) && - is_same_net6(&addr->addr.addr.addr6, &source_addr.in6.sin6_addr, addr->prefixlen)) + is_same_net6(&addr->addr.addr6, &source_addr.in6.sin6_addr, addr->prefixlen)) break; } else -#endif { struct in_addr netmask; for (addr = daemon->interface_addrs; addr; addr = addr->next) { netmask.s_addr = htonl(~(in_addr_t)0 << (32 - addr->prefixlen)); if (!(addr->flags & ADDRLIST_IPV6) && - is_same_net(addr->addr.addr.addr4, source_addr.in.sin_addr, netmask)) + is_same_net(addr->addr.addr4, source_addr.in.sin_addr, netmask)) break; } } @@ -1278,7 +1388,7 @@ void receive_query(struct listener *listen, time_t now) return; #if defined(HAVE_LINUX_NETWORK) - if (listen->family == AF_INET) + if (family == AF_INET) for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO) { @@ -1287,11 +1397,11 @@ void receive_query(struct listener *listen, time_t now) struct in_pktinfo *p; } p; p.c = CMSG_DATA(cmptr); - dst_addr_4 = dst_addr.addr.addr4 = p.p->ipi_spec_dst; + dst_addr_4 = dst_addr.addr4 = p.p->ipi_spec_dst; if_index = p.p->ipi_ifindex; } #elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF) - if (listen->family == AF_INET) + if (family == AF_INET) { for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) { @@ -1305,7 +1415,7 @@ void receive_query(struct listener *listen, time_t now) } p; p.c = CMSG_DATA(cmptr); if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVDSTADDR) - dst_addr_4 = dst_addr.addr.addr4 = *(p.a); + dst_addr_4 = dst_addr.addr4 = *(p.a); else if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF) #ifdef HAVE_SOLARIS_NETWORK if_index = *(p.i); @@ -1316,8 +1426,7 @@ void receive_query(struct listener *listen, time_t now) } #endif -#ifdef HAVE_IPV6 - if (listen->family == AF_INET6) + if (family == AF_INET6) { for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo) @@ -1328,27 +1437,26 @@ void receive_query(struct listener *listen, time_t now) } p; p.c = CMSG_DATA(cmptr); - dst_addr.addr.addr6 = p.p->ipi6_addr; + dst_addr.addr6 = p.p->ipi6_addr; if_index = p.p->ipi6_ifindex; } } -#endif /* enforce available interface configuration */ if (!indextoname(listen->fd, if_index, ifr.ifr_name)) return; - if (!iface_check(listen->family, &dst_addr, ifr.ifr_name, &auth_dns)) + if (!iface_check(family, &dst_addr, ifr.ifr_name, &auth_dns)) { if (!option_bool(OPT_CLEVERBIND)) enumerate_interfaces(0); - if (!loopback_exception(listen->fd, listen->family, &dst_addr, ifr.ifr_name) && - !label_exception(if_index, listen->family, &dst_addr)) + if (!loopback_exception(listen->fd, family, &dst_addr, ifr.ifr_name) && + !label_exception(if_index, family, &dst_addr)) return; } - if (listen->family == AF_INET && option_bool(OPT_LOCALISE)) + if (family == AF_INET && option_bool(OPT_LOCALISE)) { struct irec *iface; @@ -1381,7 +1489,11 @@ void receive_query(struct listener *listen, time_t now) pass these in global variables - sorry. */ daemon->log_display_id = ++daemon->log_id; daemon->log_source_addr = &source_addr; - + +#ifdef HAVE_DUMPFILE + dump_packet(DUMP_QUERY, daemon->packet, (size_t)n, &source_addr, NULL); +#endif + if (extract_request(header, (size_t)n, daemon->namebuff, &type)) { #ifdef HAVE_AUTH @@ -1389,14 +1501,12 @@ void receive_query(struct listener *listen, time_t now) #endif char *types = querystr(auth_dns ? "auth" : "query", type); - if (listen->family == AF_INET) + if (family == AF_INET) log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff, - (struct all_addr *)&source_addr.in.sin_addr, types); -#ifdef HAVE_IPV6 + (union all_addr *)&source_addr.in.sin_addr, types); else log_query(F_QUERY | F_IPV6 | F_FORWARD, daemon->namebuff, - (struct all_addr *)&source_addr.in6.sin6_addr, types); -#endif + (union all_addr *)&source_addr.in6.sin6_addr, types); #ifdef HAVE_AUTH /* find queries for zones we're authoritative for, and answer them directly */ @@ -1447,7 +1557,7 @@ void receive_query(struct listener *listen, time_t now) { send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (char *)header, m, &source_addr, &dst_addr, if_index); - daemon->auth_answer++; + daemon->metrics[METRIC_DNS_AUTH_ANSWERED]++; } } else @@ -1465,13 +1575,13 @@ void receive_query(struct listener *listen, time_t now) { send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (char *)header, m, &source_addr, &dst_addr, if_index); - daemon->local_answer++; + daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++; } else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index, header, (size_t)n, now, NULL, ad_reqd, do_bit)) - daemon->queries_forwarded++; + daemon->metrics[METRIC_DNS_QUERIES_FORWARDED]++; else - daemon->local_answer++; + daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++; } } @@ -1504,8 +1614,8 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si new_status = dnssec_validate_ds(now, header, n, name, keyname, class); else new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, - option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC), - NULL, NULL); + !option_bool(OPT_DNSSEC_IGN_NS) && (server->flags & SERV_DO_DNSSEC), + NULL, NULL, NULL); if (new_status != STAT_NEED_DS && new_status != STAT_NEED_KEY) break; @@ -1525,9 +1635,9 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si new_status = STAT_ABANDONED; break; } - + m = dnssec_generate_query(new_header, ((unsigned char *) new_header) + 65536, keyname, class, - new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr, server->edns_pktsz); + new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, server->edns_pktsz); *length = htons(m); @@ -1542,6 +1652,8 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si while (1) { + int data_sent = 0; + if (!firstsendto) firstsendto = server; else @@ -1560,32 +1672,46 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si (type == SERV_HAS_DOMAIN && !hostname_isequal(domain, server->domain)) || (server->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))) continue; - - retry: - /* may need to make new connection. */ - if (server->tcpfd == -1) - { - if ((server->tcpfd = socket(server->addr.sa.sa_family, SOCK_STREAM, 0)) == -1) - continue; /* No good, next server */ - + + retry: + /* may need to make new connection. */ + if (server->tcpfd == -1) + { + if ((server->tcpfd = socket(server->addr.sa.sa_family, SOCK_STREAM, 0)) == -1) + continue; /* No good, next server */ + #ifdef HAVE_CONNTRACK - /* Copy connection mark of incoming query to outgoing connection. */ - if (have_mark) - setsockopt(server->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); + /* Copy connection mark of incoming query to outgoing connection. */ + if (have_mark) + setsockopt(server->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); #endif - - if (!local_bind(server->tcpfd, &server->source_addr, server->interface, 0, 1) || - connect(server->tcpfd, &server->addr.sa, sa_len(&server->addr)) == -1) - { - close(server->tcpfd); - server->tcpfd = -1; - continue; /* No good, next server */ - } - - server->flags &= ~SERV_GOT_TCP; - } + + if (!local_bind(server->tcpfd, &server->source_addr, server->interface, 0, 1)) + { + close(server->tcpfd); + server->tcpfd = -1; + continue; /* No good, next server */ + } + +#ifdef MSG_FASTOPEN + while(retry_send(sendto(server->tcpfd, packet, m + sizeof(u16), + MSG_FASTOPEN, &server->addr.sa, sa_len(&server->addr)))); + + if (errno == 0) + data_sent = 1; +#endif + + if (!data_sent && connect(server->tcpfd, &server->addr.sa, sa_len(&server->addr)) == -1) + { + close(server->tcpfd); + server->tcpfd = -1; + continue; /* No good, next server */ + } + + server->flags &= ~SERV_GOT_TCP; + } - if (!read_write(server->tcpfd, packet, m + sizeof(u16), 0) || + if ((!data_sent && !read_write(server->tcpfd, packet, m + sizeof(u16), 0)) || !read_write(server->tcpfd, &c1, 1, 1) || !read_write(server->tcpfd, &c2, 1, 1) || !read_write(server->tcpfd, payload, (c1 << 8) | c2, 1)) @@ -1600,6 +1726,14 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si else continue; } + + + if (server->addr.sa.sa_family == AF_INET) + log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, keyname, (union all_addr *)&(server->addr.in.sin_addr), + querystr("dnssec-query", new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS)); + else + log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, keyname, (union all_addr *)&(server->addr.in6.sin6_addr), + querystr("dnssec-query", new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS)); server->flags |= SERV_GOT_TCP; @@ -1663,13 +1797,12 @@ unsigned char *tcp_request(int confd, time_t now, /* Get connection mark of incoming query to set on outgoing connections. */ if (option_bool(OPT_CONNTRACK)) { - struct all_addr local; -#ifdef HAVE_IPV6 + union all_addr local; + if (local_addr->sa.sa_family == AF_INET6) - local.addr.addr6 = local_addr->in6.sin6_addr; + local.addr6 = local_addr->in6.sin6_addr; else -#endif - local.addr.addr4 = local_addr->in.sin_addr; + local.addr4 = local_addr->in.sin_addr; have_mark = get_incoming_mark(&peer_addr, &local, 1, &mark); } @@ -1679,23 +1812,22 @@ unsigned char *tcp_request(int confd, time_t now, if (option_bool(OPT_LOCAL_SERVICE)) { struct addrlist *addr; -#ifdef HAVE_IPV6 + if (peer_addr.sa.sa_family == AF_INET6) { for (addr = daemon->interface_addrs; addr; addr = addr->next) if ((addr->flags & ADDRLIST_IPV6) && - is_same_net6(&addr->addr.addr.addr6, &peer_addr.in6.sin6_addr, addr->prefixlen)) + is_same_net6(&addr->addr.addr6, &peer_addr.in6.sin6_addr, addr->prefixlen)) break; } else -#endif { struct in_addr netmask; for (addr = daemon->interface_addrs; addr; addr = addr->next) { netmask.s_addr = htonl(~(in_addr_t)0 << (32 - addr->prefixlen)); if (!(addr->flags & ADDRLIST_IPV6) && - is_same_net(addr->addr.addr.addr4, peer_addr.in.sin_addr, netmask)) + is_same_net(addr->addr.addr4, peer_addr.in.sin_addr, netmask)) break; } } @@ -1742,12 +1874,10 @@ unsigned char *tcp_request(int confd, time_t now, if (peer_addr.sa.sa_family == AF_INET) log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff, - (struct all_addr *)&peer_addr.in.sin_addr, types); -#ifdef HAVE_IPV6 + (union all_addr *)&peer_addr.in.sin_addr, types); else log_query(F_QUERY | F_IPV6 | F_FORWARD, daemon->namebuff, - (struct all_addr *)&peer_addr.in6.sin6_addr, types); -#endif + (union all_addr *)&peer_addr.in6.sin6_addr, types); #ifdef HAVE_AUTH /* find queries for zones we're authoritative for, and answer them directly */ @@ -1803,7 +1933,7 @@ unsigned char *tcp_request(int confd, time_t now, if (m == 0) { unsigned int flags = 0; - struct all_addr *addrp = NULL; + union all_addr *addrp = NULL; int type = SERV_DO_DNSSEC; char *domain = NULL; unsigned char *oph = find_pseudoheader(header, size, NULL, NULL, NULL, NULL); @@ -1854,6 +1984,8 @@ unsigned char *tcp_request(int confd, time_t now, which can go to the same server, do so. */ while (1) { + int data_sent = 0; + if (!firstsendto) firstsendto = last_server; else @@ -1872,6 +2004,8 @@ unsigned char *tcp_request(int confd, time_t now, continue; retry: + *length = htons(size); + if (last_server->tcpfd == -1) { if ((last_server->tcpfd = socket(last_server->addr.sa.sa_family, SOCK_STREAM, 0)) == -1) @@ -1881,10 +2015,24 @@ unsigned char *tcp_request(int confd, time_t now, /* Copy connection mark of incoming query to outgoing connection. */ if (have_mark) setsockopt(last_server->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); -#endif +#endif - if ((!local_bind(last_server->tcpfd, &last_server->source_addr, last_server->interface, 0, 1) || - connect(last_server->tcpfd, &last_server->addr.sa, sa_len(&last_server->addr)) == -1)) + if ((!local_bind(last_server->tcpfd, &last_server->source_addr, last_server->interface, 0, 1))) + { + close(last_server->tcpfd); + last_server->tcpfd = -1; + continue; + } + +#ifdef MSG_FASTOPEN + while(retry_send(sendto(last_server->tcpfd, packet, size + sizeof(u16), + MSG_FASTOPEN, &last_server->addr.sa, sa_len(&last_server->addr)))); + + if (errno == 0) + data_sent = 1; +#endif + + if (!data_sent && connect(last_server->tcpfd, &last_server->addr.sa, sa_len(&last_server->addr)) == -1) { close(last_server->tcpfd); last_server->tcpfd = -1; @@ -1894,13 +2042,11 @@ unsigned char *tcp_request(int confd, time_t now, last_server->flags &= ~SERV_GOT_TCP; } - *length = htons(size); - /* get query name again for logging - may have been overwritten */ if (!(gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype))) strcpy(daemon->namebuff, "query"); - if (!read_write(last_server->tcpfd, packet, size + sizeof(u16), 0) || + if ((!data_sent && !read_write(last_server->tcpfd, packet, size + sizeof(u16), 0)) || !read_write(last_server->tcpfd, &c1, 1, 1) || !read_write(last_server->tcpfd, &c2, 1, 1) || !read_write(last_server->tcpfd, payload, (c1 << 8) | c2, 1)) @@ -1922,12 +2068,10 @@ unsigned char *tcp_request(int confd, time_t now, if (last_server->addr.sa.sa_family == AF_INET) log_query(F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff, - (struct all_addr *)&last_server->addr.in.sin_addr, NULL); -#ifdef HAVE_IPV6 + (union all_addr *)&last_server->addr.in.sin_addr, NULL); else log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff, - (struct all_addr *)&last_server->addr.in6.sin6_addr, NULL); -#endif + (union all_addr *)&last_server->addr.in6.sin6_addr, NULL); #ifdef HAVE_DNSSEC if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled && (last_server->flags & SERV_DO_DNSSEC)) @@ -1948,7 +2092,7 @@ unsigned char *tcp_request(int confd, time_t now, if (status == STAT_BOGUS && extract_request(header, m, daemon->namebuff, NULL)) domain = daemon->namebuff; - log_query(F_KEYTAG | F_SECSTAT, domain, NULL, result); + log_query(F_SECSTAT, domain, NULL, result); if (status == STAT_BOGUS) { @@ -1998,7 +2142,11 @@ unsigned char *tcp_request(int confd, time_t now, /* In case of local answer or no connections made. */ if (m == 0) - m = setup_reply(header, (unsigned int)size, addrp, flags, daemon->local_ttl); + { + m = setup_reply(header, (unsigned int)size, addrp, flags, daemon->local_ttl); + if (have_pseudoheader) + m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); + } } } @@ -2022,9 +2170,7 @@ static struct frec *allocate_frec(time_t now) f->sentto = NULL; f->rfd4 = NULL; f->flags = 0; -#ifdef HAVE_IPV6 f->rfd6 = NULL; -#endif #ifdef HAVE_DNSSEC f->dependent = NULL; f->blocking_query = NULL; @@ -2084,11 +2230,8 @@ static void free_frec(struct frec *f) f->rfd4 = NULL; f->sentto = NULL; f->flags = 0; - -#ifdef HAVE_IPV6 free_rfd(f->rfd6); f->rfd6 = NULL; -#endif #ifdef HAVE_DNSSEC if (f->stash) @@ -2111,9 +2254,10 @@ static void free_frec(struct frec *f) else return *wait zero if one available, or *wait is delay to when the oldest in-use record will expire. Impose an absolute limit of 4*TIMEOUT before we wipe things (for random sockets). - If force is set, always return a result, even if we have - to allocate above the limit. */ -struct frec *get_new_frec(time_t now, int *wait, int force) + If force is non-NULL, always return a result, even if we have + to allocate above the limit, and never free the record pointed + to by the force argument. */ +struct frec *get_new_frec(time_t now, int *wait, struct frec *force) { struct frec *f, *oldest, *target; int count; @@ -2130,7 +2274,7 @@ struct frec *get_new_frec(time_t now, int *wait, int force) /* Don't free DNSSEC sub-queries here, as we may end up with dangling references to them. They'll go when their "real" query is freed. */ - if (!f->dependent) + if (!f->dependent && f != force) #endif { if (difftime(now, f->time) >= 4*TIMEOUT) diff --git a/src/helper.c b/src/helper.c index c134071..7072cf4 100644 --- a/src/helper.c +++ b/src/helper.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -64,11 +64,10 @@ struct script_data #ifdef HAVE_TFTP off_t file_len; #endif -#ifdef HAVE_IPV6 struct in6_addr addr6; -#endif #ifdef HAVE_DHCP6 - int iaid, vendorclass_count; + int vendorclass_count; + unsigned int iaid; #endif unsigned char hwaddr[DHCP_CHADDR_MAX]; char interface[IF_NAMESIZE]; @@ -82,7 +81,8 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) pid_t pid; int i, pipefd[2]; struct sigaction sigact; - + unsigned char *alloc_buff = NULL; + /* create the pipe through which the main program sends us commands, then fork our process. */ if (pipe(pipefd) == -1 || !fix_fd(pipefd[1]) || (pid = fork()) == -1) @@ -131,12 +131,8 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) Don't close err_fd, in case the lua-init fails. Note that we have to do this before lua init so we don't close any lua fds. */ - for (max_fd--; max_fd >= 0; max_fd--) - if (max_fd != STDOUT_FILENO && max_fd != STDERR_FILENO && - max_fd != STDIN_FILENO && max_fd != pipefd[0] && - max_fd != event_fd && max_fd != err_fd) - close(max_fd); - + close_fds(max_fd, pipefd[0], event_fd, err_fd); + #ifdef HAVE_LUASCRIPT if (daemon->luascript) { @@ -188,11 +184,16 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) struct script_data data; char *p, *action_str, *hostname = NULL, *domain = NULL; unsigned char *buf = (unsigned char *)daemon->namebuff; - unsigned char *end, *extradata, *alloc_buff = NULL; + unsigned char *end, *extradata; int is6, err = 0; int pipeout[2]; - free(alloc_buff); + /* Free rarely-allocated memory from previous iteration. */ + if (alloc_buff) + { + free(alloc_buff); + alloc_buff = NULL; + } /* we read zero bytes when pipe closed: this is our signal to exit */ if (!read_write(pipefd[0], (unsigned char *)&data, sizeof(data), 1)) @@ -302,10 +303,8 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) if (!is6) inet_ntop(AF_INET, &data.addr, daemon->addrbuff, ADDRSTRLEN); -#ifdef HAVE_IPV6 else inet_ntop(AF_INET6, &data.addr6, daemon->addrbuff, ADDRSTRLEN); -#endif #ifdef HAVE_TFTP /* file length */ @@ -826,10 +825,8 @@ void queue_tftp(off_t file_len, char *filename, union mysockaddr *peer) if ((buf->flags = peer->sa.sa_family) == AF_INET) buf->addr = peer->in.sin_addr; -#ifdef HAVE_IPV6 else buf->addr6 = peer->in6.sin6_addr; -#endif memcpy((unsigned char *)(buf+1), filename, filename_len); @@ -837,7 +834,7 @@ void queue_tftp(off_t file_len, char *filename, union mysockaddr *peer) } #endif -void queue_arp(int action, unsigned char *mac, int maclen, int family, struct all_addr *addr) +void queue_arp(int action, unsigned char *mac, int maclen, int family, union all_addr *addr) { /* no script */ if (daemon->helperfd == -1) @@ -850,11 +847,9 @@ void queue_arp(int action, unsigned char *mac, int maclen, int family, struct al buf->hwaddr_len = maclen; buf->hwaddr_type = ARPHRD_ETHER; if ((buf->flags = family) == AF_INET) - buf->addr = addr->addr.addr4; -#ifdef HAVE_IPV6 + buf->addr = addr->addr4; else - buf->addr6 = addr->addr.addr6; -#endif + buf->addr6 = addr->addr6; memcpy(buf->hwaddr, mac, maclen); diff --git a/src/inotify.c b/src/inotify.c index 7107833..d5804cb 100644 --- a/src/inotify.c +++ b/src/inotify.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 diff --git a/src/ip6addr.h b/src/ip6addr.h index 6f6dff7..352b330 100644 --- a/src/ip6addr.h +++ b/src/ip6addr.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 diff --git a/src/ipset.c b/src/ipset.c index 3a5ecc5..0c014cb 100644 --- a/src/ipset.c +++ b/src/ipset.c @@ -22,9 +22,7 @@ #include <errno.h> #include <sys/types.h> #include <sys/socket.h> -#include <sys/utsname.h> #include <arpa/inet.h> -#include <linux/version.h> #include <linux/netlink.h> /* We want to be able to compile against old header files @@ -87,20 +85,7 @@ static inline void add_attr(struct nlmsghdr *nlh, uint16_t type, size_t len, con void ipset_init(void) { - struct utsname utsname; - int version; - char *split; - - if (uname(&utsname) < 0) - die(_("failed to find kernel version: %s"), NULL, EC_MISC); - - split = strtok(utsname.release, "."); - version = (split ? atoi(split) : 0); - split = strtok(NULL, "."); - version = version * 256 + (split ? atoi(split) : 0); - split = strtok(NULL, "."); - version = version * 256 + (split ? atoi(split) : 0); - old_kernel = (version < KERNEL_VERSION(2,6,32)); + old_kernel = (daemon->kernel_version < KERNEL_VERSION(2,6,32)); if (old_kernel && (ipset_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) != -1) return; @@ -114,19 +99,14 @@ void ipset_init(void) die (_("failed to create IPset control socket: %s"), NULL, EC_MISC); } -static int new_add_to_ipset(const char *setname, const struct all_addr *ipaddr, int af, int remove) +static int new_add_to_ipset(const char *setname, const union all_addr *ipaddr, int af, int remove) { struct nlmsghdr *nlh; struct my_nfgenmsg *nfg; struct my_nlattr *nested[2]; uint8_t proto; - int addrsz = INADDRSZ; + int addrsz = (af == AF_INET6) ? IN6ADDRSZ : INADDRSZ; -#ifdef HAVE_IPV6 - if (af == AF_INET6) - addrsz = IN6ADDRSZ; -#endif - if (strlen(setname) >= IPSET_MAXNAMELEN) { errno = ENAMETOOLONG; @@ -157,7 +137,7 @@ static int new_add_to_ipset(const char *setname, const struct all_addr *ipaddr, nested[1]->nla_type = NLA_F_NESTED | IPSET_ATTR_IP; add_attr(nlh, (af == AF_INET ? IPSET_ATTR_IPADDR_IPV4 : IPSET_ATTR_IPADDR_IPV6) | NLA_F_NET_BYTEORDER, - addrsz, &ipaddr->addr); + addrsz, ipaddr); nested[1]->nla_len = (void *)buffer + NL_ALIGN(nlh->nlmsg_len) - (void *)nested[1]; nested[0]->nla_len = (void *)buffer + NL_ALIGN(nlh->nlmsg_len) - (void *)nested[0]; @@ -168,7 +148,7 @@ static int new_add_to_ipset(const char *setname, const struct all_addr *ipaddr, } -static int old_add_to_ipset(const char *setname, const struct all_addr *ipaddr, int remove) +static int old_add_to_ipset(const char *setname, const union all_addr *ipaddr, int remove) { socklen_t size; struct ip_set_req_adt_get { @@ -200,7 +180,7 @@ static int old_add_to_ipset(const char *setname, const struct all_addr *ipaddr, return -1; req_adt.op = remove ? 0x102 : 0x101; req_adt.index = req_adt_get.set.index; - req_adt.ip = ntohl(ipaddr->addr.addr4.s_addr); + req_adt.ip = ntohl(ipaddr->addr4.s_addr); if (setsockopt(ipset_sock, SOL_IP, 83, &req_adt, sizeof(req_adt)) < 0) return -1; @@ -209,11 +189,10 @@ static int old_add_to_ipset(const char *setname, const struct all_addr *ipaddr, -int add_to_ipset(const char *setname, const struct all_addr *ipaddr, int flags, int remove) +int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove) { int ret = 0, af = AF_INET; -#ifdef HAVE_IPV6 if (flags & F_IPV6) { af = AF_INET6; @@ -224,7 +203,6 @@ int add_to_ipset(const char *setname, const struct all_addr *ipaddr, int flags, ret = -1; } } -#endif if (ret != -1) ret = old_kernel ? old_add_to_ipset(setname, ipaddr, remove) : new_add_to_ipset(setname, ipaddr, af, remove); diff --git a/src/lease.c b/src/lease.c index 5c33df7..23e6fe0 100644 --- a/src/lease.c +++ b/src/lease.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -24,7 +24,7 @@ static int dns_dirty, file_dirty, leases_left; static int read_leases(time_t now, FILE *leasestream) { unsigned long ei; - struct all_addr addr; + union all_addr addr; struct dhcp_lease *lease; int clid_len, hw_len, hw_type; int items; @@ -60,11 +60,16 @@ static int read_leases(time_t now, FILE *leasestream) if (fscanf(leasestream, " %64s %255s %764s", daemon->namebuff, daemon->dhcp_buff, daemon->packet) != 3) - return 0; - - if (inet_pton(AF_INET, daemon->namebuff, &addr.addr.addr4)) { - if ((lease = lease4_allocate(addr.addr.addr4))) + my_syslog(MS_DHCP | LOG_WARNING, _("ignoring invalid line in lease database: %s %s %s %s ..."), + daemon->dhcp_buff3, daemon->dhcp_buff2, + daemon->namebuff, daemon->dhcp_buff); + continue; + } + + if (inet_pton(AF_INET, daemon->namebuff, &addr.addr4)) + { + if ((lease = lease4_allocate(addr.addr4))) domain = get_domain(lease->addr); hw_len = parse_hex(daemon->dhcp_buff2, (unsigned char *)daemon->dhcp_buff2, DHCP_CHADDR_MAX, NULL, &hw_type); @@ -73,7 +78,7 @@ static int read_leases(time_t now, FILE *leasestream) hw_type = ARPHRD_ETHER; } #ifdef HAVE_DHCP6 - else if (inet_pton(AF_INET6, daemon->namebuff, &addr.addr.addr6)) + else if (inet_pton(AF_INET6, daemon->namebuff, &addr.addr6)) { char *s = daemon->dhcp_buff2; int lease_type = LEASE_NA; @@ -84,15 +89,20 @@ static int read_leases(time_t now, FILE *leasestream) s++; } - if ((lease = lease6_allocate(&addr.addr.addr6, lease_type))) + if ((lease = lease6_allocate(&addr.addr6, lease_type))) { lease_set_iaid(lease, strtoul(s, NULL, 10)); - domain = get_domain6((struct in6_addr *)lease->hwaddr); + domain = get_domain6(&lease->addr6); } } #endif else - return 0; + { + my_syslog(MS_DHCP | LOG_WARNING, _("ignoring invalid line in lease database, bad address: %s"), + daemon->namebuff); + continue; + } + if (!lease) die (_("too many stored leases"), NULL, EC_MISC); @@ -172,10 +182,8 @@ void lease_init(time_t now) if (leasestream) { if (!read_leases(now, leasestream)) - my_syslog(MS_DHCP | LOG_ERR, _("failed to parse lease database, invalid line: %s %s %s %s ..."), - daemon->dhcp_buff3, daemon->dhcp_buff2, - daemon->namebuff, daemon->dhcp_buff); - + my_syslog(MS_DHCP | LOG_ERR, _("failed to parse lease database cleanly")); + if (ferror(leasestream)) die(_("failed to read lease file %s: %s"), daemon->lease_file, EC_FILE); } @@ -222,7 +230,7 @@ void lease_update_from_configs(void) if (lease->flags & (LEASE_TA | LEASE_NA)) continue; else if ((config = find_config(daemon->dhcp_conf, NULL, lease->clid, lease->clid_len, - lease->hwaddr, lease->hwaddr_len, lease->hwaddr_type, NULL)) && + lease->hwaddr, lease->hwaddr_len, lease->hwaddr_type, NULL, NULL)) && (config->flags & CONFIG_NAME) && (!(config->flags & CONFIG_ADDR) || config->addr.s_addr == lease->addr.s_addr)) lease_set_hostname(lease, config->hostname, 1, get_domain(lease->addr), NULL); @@ -514,28 +522,28 @@ void lease_update_dns(int force) if (slaac->backoff == 0) { if (lease->fqdn) - cache_add_dhcp_entry(lease->fqdn, AF_INET6, (struct all_addr *)&slaac->addr, lease->expires); + cache_add_dhcp_entry(lease->fqdn, AF_INET6, (union 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 *)&slaac->addr, lease->expires); + cache_add_dhcp_entry(lease->hostname, AF_INET6, (union all_addr *)&slaac->addr, lease->expires); } } if (lease->fqdn) cache_add_dhcp_entry(lease->fqdn, prot, - prot == AF_INET ? (struct all_addr *)&lease->addr : (struct all_addr *)&lease->addr6, + prot == AF_INET ? (union all_addr *)&lease->addr : (union all_addr *)&lease->addr6, lease->expires); if (!option_bool(OPT_DHCP_FQDN) && lease->hostname) cache_add_dhcp_entry(lease->hostname, prot, - prot == AF_INET ? (struct all_addr *)&lease->addr : (struct all_addr *)&lease->addr6, + prot == AF_INET ? (union all_addr *)&lease->addr : (union all_addr *)&lease->addr6, lease->expires); #else if (lease->fqdn) - cache_add_dhcp_entry(lease->fqdn, prot, (struct all_addr *)&lease->addr, lease->expires); + cache_add_dhcp_entry(lease->fqdn, prot, (union all_addr *)&lease->addr, lease->expires); if (!option_bool(OPT_DHCP_FQDN) && lease->hostname) - cache_add_dhcp_entry(lease->hostname, prot, (struct all_addr *)&lease->addr, lease->expires); + cache_add_dhcp_entry(lease->hostname, prot, (union all_addr *)&lease->addr, lease->expires); #endif } @@ -550,12 +558,14 @@ void lease_prune(struct dhcp_lease *target, time_t now) for (lease = leases, up = &leases; lease; lease = tmp) { tmp = lease->next; - if ((lease->expires != 0 && difftime(now, lease->expires) > 0) || lease == target) + if ((lease->expires != 0 && difftime(now, lease->expires) >= 0) || lease == target) { file_dirty = 1; if (lease->hostname) dns_dirty = 1; - + + daemon->metrics[lease->addr.s_addr ? METRIC_LEASES_PRUNED_4 : METRIC_LEASES_PRUNED_6]++; + *up = lease->next; /* unlink */ /* Put on old_leases list 'till we @@ -625,7 +635,8 @@ struct dhcp_lease *lease_find_by_addr(struct in_addr addr) #ifdef HAVE_DHCP6 /* 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) + int lease_type, unsigned int iaid, + struct in6_addr *addr) { struct dhcp_lease *lease; @@ -657,7 +668,9 @@ void lease6_reset(void) } /* 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 *lease6_find_by_client(struct dhcp_lease *first, int lease_type, + unsigned char *clid, int clid_len, + unsigned int iaid) { struct dhcp_lease *lease; @@ -773,7 +786,10 @@ struct dhcp_lease *lease4_allocate(struct in_addr addr) { struct dhcp_lease *lease = lease_allocate(); if (lease) - lease->addr = addr; + { + lease->addr = addr; + daemon->metrics[METRIC_LEASES_ALLOCATED_4]++; + } return lease; } @@ -788,6 +804,8 @@ struct dhcp_lease *lease6_allocate(struct in6_addr *addrp, int lease_type) lease->addr6 = *addrp; lease->flags |= lease_type; lease->iaid = 0; + + daemon->metrics[METRIC_LEASES_ALLOCATED_6]++; } return lease; @@ -818,7 +836,7 @@ void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now) dns_dirty = 1; lease->expires = exp; #ifndef HAVE_BROKEN_RTC - lease->flags |= LEASE_AUX_CHANGED; + lease->flags |= LEASE_AUX_CHANGED | LEASE_EXP_CHANGED; file_dirty = 1; #endif } @@ -834,7 +852,7 @@ void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now) } #ifdef HAVE_DHCP6 -void lease_set_iaid(struct dhcp_lease *lease, int iaid) +void lease_set_iaid(struct dhcp_lease *lease, unsigned int iaid) { if (lease->iaid != iaid) { @@ -1118,7 +1136,8 @@ int do_script_run(time_t now) for (lease = leases; lease; lease = lease->next) if ((lease->flags & (LEASE_NEW | LEASE_CHANGED)) || - ((lease->flags & LEASE_AUX_CHANGED) && option_bool(OPT_LEASE_RO))) + ((lease->flags & LEASE_AUX_CHANGED) && option_bool(OPT_LEASE_RO)) || + ((lease->flags & LEASE_EXP_CHANGED) && option_bool(OPT_LEASE_RENEW))) { #ifdef HAVE_SCRIPT queue_script((lease->flags & LEASE_NEW) ? ACTION_ADD : ACTION_OLD, lease, @@ -1128,7 +1147,7 @@ int do_script_run(time_t now) emit_dbus_signal((lease->flags & LEASE_NEW) ? ACTION_ADD : ACTION_OLD, lease, lease->fqdn ? lease->fqdn : lease->hostname); #endif - lease->flags &= ~(LEASE_NEW | LEASE_CHANGED | LEASE_AUX_CHANGED); + lease->flags &= ~(LEASE_NEW | LEASE_CHANGED | LEASE_AUX_CHANGED | LEASE_EXP_CHANGED); /* this is used for the "add" call, then junked, since they're not in the database */ free(lease->extradata); @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -232,7 +232,7 @@ static void log_write(void) logaddr.sun_len = sizeof(logaddr) - sizeof(logaddr.sun_path) + strlen(_PATH_LOG) + 1; #endif logaddr.sun_family = AF_UNIX; - strncpy(logaddr.sun_path, _PATH_LOG, sizeof(logaddr.sun_path)); + safe_strncpy(logaddr.sun_path, _PATH_LOG, sizeof(logaddr.sun_path)); /* Got connection back? try again. */ if (connect(log_fd, (struct sockaddr *)&logaddr, sizeof(logaddr)) != -1) @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 diff --git a/src/metrics.c b/src/metrics.c new file mode 100644 index 0000000..79f20b0 --- /dev/null +++ b/src/metrics.c @@ -0,0 +1,44 @@ +/* dnsmasq is Copyright (c) 2000-2020 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 <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + +const char * metric_names[] = { + "dns_cache_inserted", + "dns_cache_live_freed", + "dns_queries_forwarded", + "dns_auth_answered", + "dns_local_answered", + "bootp", + "pxe", + "dhcp_ack", + "dhcp_decline", + "dhcp_discover", + "dhcp_inform", + "dhcp_nak", + "dhcp_offer", + "dhcp_release", + "dhcp_request", + "noanswer", + "leases_allocated_4", + "leases_pruned_4", + "leases_allocated_6", + "leases_pruned_6", +}; + +const char* get_metric_name(int i) { + return metric_names[i]; +} diff --git a/src/metrics.h b/src/metrics.h new file mode 100644 index 0000000..ccc92a8 --- /dev/null +++ b/src/metrics.h @@ -0,0 +1,43 @@ +/* dnsmasq is Copyright (c) 2000-2020 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 <http://www.gnu.org/licenses/>. +*/ + +/* If you modify this list, please keep the labels in metrics.c in sync. */ +enum { + METRIC_DNS_CACHE_INSERTED, + METRIC_DNS_CACHE_LIVE_FREED, + METRIC_DNS_QUERIES_FORWARDED, + METRIC_DNS_AUTH_ANSWERED, + METRIC_DNS_LOCAL_ANSWERED, + METRIC_BOOTP, + METRIC_PXE, + METRIC_DHCPACK, + METRIC_DHCPDECLINE, + METRIC_DHCPDISCOVER, + METRIC_DHCPINFORM, + METRIC_DHCPNAK, + METRIC_DHCPOFFER, + METRIC_DHCPRELEASE, + METRIC_DHCPREQUEST, + METRIC_NOANSWER, + METRIC_LEASES_ALLOCATED_4, + METRIC_LEASES_PRUNED_4, + METRIC_LEASES_ALLOCATED_6, + METRIC_LEASES_PRUNED_6, + + __METRIC_MAX, +}; + +const char* get_metric_name(int); diff --git a/src/netlink.c b/src/netlink.c index 05153f5..8e8431f 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -22,6 +22,15 @@ #include <linux/netlink.h> #include <linux/rtnetlink.h> +/* Blergh. Radv does this, so that's our excuse. */ +#ifndef SOL_NETLINK +#define SOL_NETLINK 270 +#endif + +#ifndef NETLINK_NO_ENOBUFS +#define NETLINK_NO_ENOBUFS 5 +#endif + /* linux 2.6.19 buggers up the headers, patch it up here. */ #ifndef IFA_RTA # define IFA_RTA(r) \ @@ -40,10 +49,11 @@ static u32 netlink_pid; static void nl_async(struct nlmsghdr *h); -void netlink_init(void) +char *netlink_init(void) { struct sockaddr_nl addr; socklen_t slen = sizeof(addr); + int opt = 1; addr.nl_family = AF_NETLINK; addr.nl_pad = 0; @@ -51,11 +61,10 @@ void netlink_init(void) addr.nl_groups = RTMGRP_IPV4_ROUTE; if (option_bool(OPT_CLEVERBIND)) addr.nl_groups |= RTMGRP_IPV4_IFADDR; -#ifdef HAVE_IPV6 addr.nl_groups |= RTMGRP_IPV6_ROUTE; if (option_bool(OPT_CLEVERBIND)) addr.nl_groups |= RTMGRP_IPV6_IFADDR; -#endif + #ifdef HAVE_DHCP6 if (daemon->doing_ra || daemon->doing_dhcp6) addr.nl_groups |= RTMGRP_IPV6_IFADDR; @@ -75,12 +84,19 @@ void netlink_init(void) if (daemon->netlinkfd == -1 || getsockname(daemon->netlinkfd, (struct sockaddr *)&addr, &slen) == -1) die(_("cannot create netlink socket: %s"), NULL, EC_MISC); - + + /* save pid assigned by bind() and retrieved by getsockname() */ netlink_pid = addr.nl_pid; iov.iov_len = 100; iov.iov_base = safe_malloc(iov.iov_len); + + if (daemon->kernel_version >= KERNEL_VERSION(2,6,30) && + setsockopt(daemon->netlinkfd, SOL_NETLINK, NETLINK_NO_ENOBUFS, &opt, sizeof(opt)) == -1) + return _("warning: failed to set NETLINK_NO_ENOBUFS on netlink socket"); + + return NULL; } static ssize_t netlink_recv(void) @@ -149,10 +165,10 @@ int iface_enumerate(int family, void *parm, int (*callback)()) struct rtgenmsg g; } req; + memset(&req, 0, sizeof(req)); + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; - addr.nl_pad = 0; - addr.nl_groups = 0; - addr.nl_pid = 0; /* address to kernel */ again: if (family == AF_UNSPEC) @@ -235,7 +251,6 @@ int iface_enumerate(int family, void *parm, int (*callback)()) if (!((*callback)(addr, ifa->ifa_index, label, netmask, broadcast, parm))) callback_ok = 0; } -#ifdef HAVE_IPV6 else if (ifa->ifa_family == AF_INET6) { struct in6_addr *addrp = NULL; @@ -270,7 +285,6 @@ int iface_enumerate(int family, void *parm, int (*callback)()) (int) preferred, (int)valid, parm))) callback_ok = 0; } -#endif } } else if (h->nlmsg_type == RTM_NEWNEIGH && family == AF_UNSPEC) @@ -363,7 +377,9 @@ static void nl_async(struct nlmsghdr *h) failing. */ struct rtmsg *rtm = NLMSG_DATA(h); - if (rtm->rtm_type == RTN_UNICAST && rtm->rtm_scope == RT_SCOPE_LINK) + if (rtm->rtm_type == RTN_UNICAST && rtm->rtm_scope == RT_SCOPE_LINK && + (rtm->rtm_table == RT_TABLE_MAIN || + rtm->rtm_table == RT_TABLE_LOCAL)) queue_event(EVENT_NEWROUTE); } else if (h->nlmsg_type == RTM_NEWADDR || h->nlmsg_type == RTM_DELADDR) diff --git a/src/network.c b/src/network.c index 0381513..c7d002b 100644 --- a/src/network.c +++ b/src/network.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -29,7 +29,7 @@ int indextoname(int fd, int index, char *name) if (ioctl(fd, SIOCGIFNAME, &ifr) == -1) return 0; - strncpy(name, ifr.ifr_name, IF_NAMESIZE); + safe_strncpy(name, ifr.ifr_name, IF_NAMESIZE); return 1; } @@ -82,12 +82,12 @@ int indextoname(int fd, int index, char *name) for (i = lifc.lifc_len / sizeof(struct lifreq); i; i--, lifrp++) { struct lifreq lifr; - strncpy(lifr.lifr_name, lifrp->lifr_name, IF_NAMESIZE); + safe_strncpy(lifr.lifr_name, lifrp->lifr_name, IF_NAMESIZE); if (ioctl(fd, SIOCGLIFINDEX, &lifr) < 0) return 0; if (lifr.lifr_index == index) { - strncpy(name, lifr.lifr_name, IF_NAMESIZE); + safe_strncpy(name, lifr.lifr_name, IF_NAMESIZE); return 1; } } @@ -109,7 +109,7 @@ int indextoname(int fd, int index, char *name) #endif -int iface_check(int family, struct all_addr *addr, char *name, int *auth) +int iface_check(int family, union all_addr *addr, char *name, int *auth) { struct iname *tmp; int ret = 1, match_addr = 0; @@ -135,14 +135,12 @@ int iface_check(int family, struct all_addr *addr, char *name, int *auth) if (tmp->addr.sa.sa_family == family) { if (family == AF_INET && - tmp->addr.in.sin_addr.s_addr == addr->addr.addr4.s_addr) + tmp->addr.in.sin_addr.s_addr == addr->addr4.s_addr) ret = match_addr = tmp->used = 1; -#ifdef HAVE_IPV6 else if (family == AF_INET6 && IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, - &addr->addr.addr6)) + &addr->addr6)) ret = match_addr = tmp->used = 1; -#endif } } @@ -160,13 +158,11 @@ int iface_check(int family, struct all_addr *addr, char *name, int *auth) break; } else if (addr && tmp->addr.sa.sa_family == AF_INET && family == AF_INET && - tmp->addr.in.sin_addr.s_addr == addr->addr.addr4.s_addr) + tmp->addr.in.sin_addr.s_addr == addr->addr4.s_addr) break; -#ifdef HAVE_IPV6 else if (addr && tmp->addr.sa.sa_family == AF_INET6 && family == AF_INET6 && - IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, &addr->addr.addr6)) + IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, &addr->addr6)) break; -#endif if (tmp && auth) { @@ -183,12 +179,12 @@ int iface_check(int family, struct all_addr *addr, char *name, int *auth) an interface other than the loopback. Accept packet if it arrived via a loopback interface, even when we're not accepting packets that way, as long as the destination address is one we're believing. Interface list must be up-to-date before calling. */ -int loopback_exception(int fd, int family, struct all_addr *addr, char *name) +int loopback_exception(int fd, int family, union all_addr *addr, char *name) { struct ifreq ifr; struct irec *iface; - strncpy(ifr.ifr_name, name, IF_NAMESIZE); + safe_strncpy(ifr.ifr_name, name, IF_NAMESIZE); if (ioctl(fd, SIOCGIFFLAGS, &ifr) != -1 && ifr.ifr_flags & IFF_LOOPBACK) { @@ -197,14 +193,11 @@ int loopback_exception(int fd, int family, struct all_addr *addr, char *name) { if (family == AF_INET) { - if (iface->addr.in.sin_addr.s_addr == addr->addr.addr4.s_addr) + if (iface->addr.in.sin_addr.s_addr == addr->addr4.s_addr) return 1; } -#ifdef HAVE_IPV6 - else if (IN6_ARE_ADDR_EQUAL(&iface->addr.in6.sin6_addr, &addr->addr.addr6)) + else if (IN6_ARE_ADDR_EQUAL(&iface->addr.in6.sin6_addr, &addr->addr6)) return 1; -#endif - } } return 0; @@ -214,7 +207,7 @@ int loopback_exception(int fd, int family, struct all_addr *addr, char *name) on the relevant address, but the name of the arrival interface, derived from the index won't match the config. Check that we found an interface address for the arrival interface: daemon->interfaces must be up-to-date. */ -int label_exception(int index, int family, struct all_addr *addr) +int label_exception(int index, int family, union all_addr *addr) { struct irec *iface; @@ -224,7 +217,7 @@ int label_exception(int index, int family, struct all_addr *addr) for (iface = daemon->interfaces; iface; iface = iface->next) if (iface->index == index && iface->addr.sa.sa_family == AF_INET && - iface->addr.in.sin_addr.s_addr == addr->addr.addr4.s_addr) + iface->addr.in.sin_addr.s_addr == addr->addr4.s_addr) return 1; return 0; @@ -289,22 +282,18 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, if (addr->sa.sa_family == AF_INET) { - al->addr.addr.addr4 = addr->in.sin_addr; + al->addr.addr4 = addr->in.sin_addr; al->flags = 0; } -#ifdef HAVE_IPV6 else { - al->addr.addr.addr6 = addr->in6.sin6_addr; + al->addr.addr6 = addr->in6.sin6_addr; al->flags = ADDRLIST_IPV6; } -#endif } } -#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; @@ -332,12 +321,11 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, al->next = zone->subnet; zone->subnet = al; al->prefixlen = prefixlen; - al->addr.addr.addr4 = addr->in.sin_addr; + al->addr.addr4 = addr->in.sin_addr; al->flags = 0; } } -#ifdef HAVE_IPV6 if (addr->sa.sa_family == AF_INET6 && (name->flags & AUTH6)) { if (param->spare) @@ -353,12 +341,10 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, al->next = zone->subnet; zone->subnet = al; al->prefixlen = prefixlen; - al->addr.addr.addr6 = addr->in6.sin6_addr; + al->addr.addr6 = addr->in6.sin6_addr; al->flags = ADDRLIST_IPV6; } } -#endif - } #endif @@ -383,20 +369,18 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, if (addr->sa.sa_family == AF_INET) { - al->addr.addr.addr4 = addr->in.sin_addr; + al->addr.addr4 = addr->in.sin_addr; al->flags = 0; } -#ifdef HAVE_IPV6 else { - al->addr.addr.addr6 = addr->in6.sin6_addr; + al->addr.addr6 = addr->in6.sin6_addr; al->flags = ADDRLIST_IPV6; /* Privacy addresses and addresses still undergoing DAD and deprecated addresses don't appear in forward queries, but will in reverse ones. */ if (!(iface_flags & IFACE_PERMANENT) || (iface_flags & (IFACE_DEPRECATED | IFACE_TENTATIVE))) al->flags |= ADDRLIST_REVONLY; } -#endif } } } @@ -404,10 +388,11 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, /* 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)) + if (sockaddr_isequal(&iface->addr, addr) && iface->index == if_index) { iface->dad = !!(iface_flags & IFACE_TENTATIVE); iface->found = 1; /* for garbage collection */ + iface->netmask = netmask; return 1; } @@ -435,14 +420,12 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, } if (addr->sa.sa_family == AF_INET && - !iface_check(AF_INET, (struct all_addr *)&addr->in.sin_addr, label, &auth_dns)) + !iface_check(AF_INET, (union all_addr *)&addr->in.sin_addr, label, &auth_dns)) return 1; -#ifdef HAVE_IPV6 if (addr->sa.sa_family == AF_INET6 && - !iface_check(AF_INET6, (struct all_addr *)&addr->in6.sin6_addr, label, &auth_dns)) + !iface_check(AF_INET6, (union all_addr *)&addr->in6.sin6_addr, label, &auth_dns)) return 1; -#endif #ifdef HAVE_DHCP /* No DHCP where we're doing auth DNS. */ @@ -501,7 +484,6 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, return 0; } -#ifdef HAVE_IPV6 static int iface_allowed_v6(struct in6_addr *local, int prefix, int scope, int if_index, int flags, int preferred, int valid, void *vparam) @@ -529,7 +511,6 @@ static int iface_allowed_v6(struct in6_addr *local, int prefix, return iface_allowed((struct iface_param *)vparam, if_index, NULL, &addr, netmask, prefix, flags); } -#endif static int iface_allowed_v4(struct in_addr local, int if_index, char *label, struct in_addr netmask, struct in_addr broadcast, void *vparam) @@ -552,7 +533,82 @@ static int iface_allowed_v4(struct in_addr local, int if_index, char *label, return iface_allowed((struct iface_param *)vparam, if_index, label, &addr, netmask, prefix, 0); } - + +/* + * Clean old interfaces no longer found. + */ +static void clean_interfaces() +{ + struct irec *iface; + struct irec **up = &daemon->interfaces; + + for (iface = *up; iface; iface = *up) + { + if (!iface->found && !iface->done) + { + *up = iface->next; + free(iface->name); + free(iface); + } + else + { + up = &iface->next; + } + } +} + +/** Release listener if no other interface needs it. + * + * @return 1 if released, 0 if still required + */ +static int release_listener(struct listener *l) +{ + if (l->used > 1) + { + struct irec *iface; + for (iface = daemon->interfaces; iface; iface = iface->next) + if (iface->done && sockaddr_isequal(&l->addr, &iface->addr)) + { + if (iface->found) + { + /* update listener to point to active interface instead */ + if (!l->iface->found) + l->iface = iface; + } + else + { + l->used--; + iface->done = 0; + } + } + + /* Someone is still using this listener, skip its deletion */ + if (l->used > 0) + return 0; + } + + if (l->iface->done) + { + int port; + + port = prettyprint_addr(&l->iface->addr, daemon->addrbuff); + my_syslog(LOG_DEBUG, _("stopped listening on %s(#%d): %s port %d"), + l->iface->name, l->iface->index, daemon->addrbuff, port); + /* In case it ever returns */ + l->iface->done = 0; + } + + if (l->fd != -1) + close(l->fd); + if (l->tcpfd != -1) + close(l->tcpfd); + if (l->tftpfd != -1) + close(l->tftpfd); + + free(l); + return 1; +} + int enumerate_interfaces(int reset) { static struct addrlist *spare = NULL; @@ -633,9 +689,7 @@ int enumerate_interfaces(int reset) param.spare = spare; -#ifdef HAVE_IPV6 ret = iface_enumerate(AF_INET6, ¶m, iface_allowed_v6); -#endif if (ret) ret = iface_enumerate(AF_INET, ¶m, iface_allowed_v4); @@ -652,6 +706,7 @@ int enumerate_interfaces(int reset) in OPT_CLEVERBIND mode, that at listener will just disappear after a call to enumerate_interfaces, this is checked OK on all calls. */ struct listener *l, *tmp, **up; + int freed = 0; for (up = &daemon->listeners, l = daemon->listeners; l; l = tmp) { @@ -659,25 +714,17 @@ int enumerate_interfaces(int reset) if (!l->iface || l->iface->found) up = &l->next; - else + else if (release_listener(l)) { - *up = l->next; - - /* In case it ever returns */ - l->iface->done = 0; - - if (l->fd != -1) - close(l->fd); - if (l->tcpfd != -1) - close(l->tcpfd); - if (l->tftpfd != -1) - close(l->tftpfd); - - free(l); + *up = tmp; + freed = 1; } } + + if (freed) + clean_interfaces(); } - + errno = errsave; spare = param.spare; @@ -740,16 +787,19 @@ static int make_sock(union mysockaddr *addr, int type, int dienow) if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1 || !fix_fd(fd)) goto err; -#ifdef HAVE_IPV6 if (family == AF_INET6 && setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) == -1) goto err; -#endif if ((rc = bind(fd, (struct sockaddr *)addr, sa_len(addr))) == -1) goto err; if (type == SOCK_STREAM) { +#ifdef TCP_FASTOPEN + int qlen = 5; + setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &qlen, sizeof(qlen)); +#endif + if (listen(fd, TCP_BACKLOG) == -1) goto err; } @@ -767,15 +817,12 @@ static int make_sock(union mysockaddr *addr, int type, int dienow) #endif } } -#ifdef HAVE_IPV6 else if (!set_ipv6pktinfo(fd)) goto err; -#endif return fd; } -#ifdef HAVE_IPV6 int set_ipv6pktinfo(int fd) { int opt = 1; @@ -802,12 +849,13 @@ int set_ipv6pktinfo(int fd) return 0; } -#endif /* Find the interface on which a TCP connection arrived, if possible, or zero otherwise. */ int tcp_interface(int fd, int af) { + (void)fd; /* suppress potential unused warning */ + (void)af; /* suppress potential unused warning */ int if_index = 0; #ifdef HAVE_LINUX_NETWORK @@ -842,7 +890,6 @@ int tcp_interface(int fd, int af) } } } -#ifdef HAVE_IPV6 else { /* Only the RFC-2292 API has the ability to find the interface for TCP connections, @@ -874,7 +921,6 @@ int tcp_interface(int fd, int af) } } } -#endif /* IPV6 */ #endif /* Linux */ return if_index; @@ -904,7 +950,6 @@ static struct listener *create_listeners(union mysockaddr *addr, int do_tftp, in tftpfd = make_sock(addr, SOCK_DGRAM, dienow); addr->in.sin_port = save; } -# ifdef HAVE_IPV6 else { short save = addr->in6.sin6_port; @@ -912,7 +957,6 @@ static struct listener *create_listeners(union mysockaddr *addr, int do_tftp, in tftpfd = make_sock(addr, SOCK_DGRAM, dienow); addr->in6.sin6_port = save; } -# endif } #endif @@ -920,10 +964,11 @@ static struct listener *create_listeners(union mysockaddr *addr, int do_tftp, in { l = safe_malloc(sizeof(struct listener)); l->next = NULL; - l->family = addr->sa.sa_family; l->fd = fd; l->tcpfd = tcpfd; - l->tftpfd = tftpfd; + l->tftpfd = tftpfd; + l->addr = *addr; + l->used = 1; l->iface = NULL; } @@ -945,11 +990,10 @@ void create_wildcard_listeners(void) l = create_listeners(&addr, !!option_bool(OPT_TFTP), 1); -#ifdef HAVE_IPV6 memset(&addr, 0, sizeof(addr)); -# ifdef HAVE_SOCKADDR_SA_LEN +#ifdef HAVE_SOCKADDR_SA_LEN addr.in6.sin6_len = sizeof(addr.in6); -# endif +#endif addr.in6.sin6_family = AF_INET6; addr.in6.sin6_addr = in6addr_any; addr.in6.sin6_port = htons(daemon->port); @@ -959,25 +1003,52 @@ void create_wildcard_listeners(void) l->next = l6; else l = l6; -#endif daemon->listeners = l; } +static struct listener *find_listener(union mysockaddr *addr) +{ + struct listener *l; + for (l = daemon->listeners; l; l = l->next) + if (sockaddr_isequal(&l->addr, addr)) + return l; + return NULL; +} + void create_bound_listeners(int dienow) { struct listener *new; struct irec *iface; struct iname *if_tmp; + struct listener *existing; for (iface = daemon->interfaces; iface; iface = iface->next) - if (!iface->done && !iface->dad && iface->found && - (new = create_listeners(&iface->addr, iface->tftp_ok, dienow))) + if (!iface->done && !iface->dad && iface->found) { - new->iface = iface; - new->next = daemon->listeners; - daemon->listeners = new; - iface->done = 1; + existing = find_listener(&iface->addr); + if (existing) + { + iface->done = 1; + existing->used++; /* increase usage counter */ + } + else if ((new = create_listeners(&iface->addr, iface->tftp_ok, dienow))) + { + new->iface = iface; + new->next = daemon->listeners; + daemon->listeners = new; + iface->done = 1; + + /* Don't log the initial set of listen addresses created + at startup, since this is happening before the logging + system is initialised and the sign-on printed. */ + if (!dienow) + { + int port = prettyprint_addr(&iface->addr, daemon->addrbuff); + my_syslog(LOG_DEBUG, _("listening on %s(#%d): %s port %d"), + iface->name, iface->index, daemon->addrbuff, port); + } + } } /* Check for --listen-address options that haven't been used because there's @@ -997,6 +1068,12 @@ void create_bound_listeners(int dienow) { new->next = daemon->listeners; daemon->listeners = new; + + if (!dienow) + { + int port = prettyprint_addr(&if_tmp->addr, daemon->addrbuff); + my_syslog(LOG_DEBUG, _("listening on %s port %d"), daemon->addrbuff, port); + } } } @@ -1159,7 +1236,6 @@ int random_sock(int family) addr.in.sin_len = sizeof(struct sockaddr_in); #endif } -#ifdef HAVE_IPV6 else { addr.in6.sin6_addr = in6addr_any; @@ -1168,7 +1244,6 @@ int random_sock(int family) addr.in6.sin6_len = sizeof(struct sockaddr_in6); #endif } -#endif if (bind(fd, (struct sockaddr *)&addr, sa_len(&addr)) == 0) return fd; @@ -1193,10 +1268,8 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind { if (addr_copy.sa.sa_family == AF_INET) addr_copy.in.sin_port = 0; -#ifdef HAVE_IPV6 else addr_copy.in6.sin6_port = 0; -#endif } if (bind(fd, (struct sockaddr *)&addr_copy, sa_len(&addr_copy)) == -1) @@ -1211,7 +1284,7 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind return setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex_opt, sizeof(ifindex_opt)) == 0; } #endif -#if defined(HAVE_IPV6) && defined (IPV6_UNICAST_IF) +#if defined (IPV6_UNICAST_IF) if (addr_copy.sa.sa_family == AF_INET6) { uint32_t ifindex_opt = htonl(ifindex); @@ -1220,6 +1293,7 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind #endif } + (void)intname; /* suppress potential unused warning */ #if defined(SO_BINDTODEVICE) if (intname[0] != 0 && setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, intname, IF_NAMESIZE) == -1) @@ -1234,7 +1308,8 @@ static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname) struct serverfd *sfd; unsigned int ifindex = 0; int errsave; - + int opt = 1; + /* when using random ports, servers which would otherwise use the INADDR_ANY/port0 socket have sfd set to NULL */ if (!daemon->osport && intname[0] == 0) @@ -1246,12 +1321,10 @@ static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname) addr->in.sin_port == htons(0)) return NULL; -#ifdef HAVE_IPV6 if (addr->sa.sa_family == AF_INET6 && memcmp(&addr->in6.sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0 && addr->in6.sin6_port == htons(0)) return NULL; -#endif } if (intname && strlen(intname) != 0) @@ -1274,20 +1347,22 @@ static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname) free(sfd); return NULL; } - - if (!local_bind(sfd->fd, addr, intname, ifindex, 0) || !fix_fd(sfd->fd)) + + if ((addr->sa.sa_family == AF_INET6 && setsockopt(sfd->fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) == -1) || + !local_bind(sfd->fd, addr, intname, ifindex, 0) || !fix_fd(sfd->fd)) { - errsave = errno; /* save error from bind. */ + errsave = errno; /* save error from bind/setsockopt. */ close(sfd->fd); free(sfd); errno = errsave; return NULL; } - strcpy(sfd->interface, intname); + safe_strncpy(sfd->interface, intname, sizeof(sfd->interface)); sfd->source_addr = *addr; sfd->next = daemon->sfds; sfd->ifindex = ifindex; + sfd->preallocated = 0; daemon->sfds = sfd; return sfd; @@ -1298,6 +1373,7 @@ static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname) void pre_allocate_sfds(void) { struct server *srv; + struct serverfd *sfd; if (daemon->query_port != 0) { @@ -1309,8 +1385,9 @@ void pre_allocate_sfds(void) #ifdef HAVE_SOCKADDR_SA_LEN addr.in.sin_len = sizeof(struct sockaddr_in); #endif - allocate_sfd(&addr, ""); -#ifdef HAVE_IPV6 + if ((sfd = allocate_sfd(&addr, ""))) + sfd->preallocated = 1; + memset(&addr, 0, sizeof(addr)); addr.in6.sin6_family = AF_INET6; addr.in6.sin6_addr = in6addr_any; @@ -1318,8 +1395,8 @@ void pre_allocate_sfds(void) #ifdef HAVE_SOCKADDR_SA_LEN addr.in6.sin6_len = sizeof(struct sockaddr_in6); #endif - allocate_sfd(&addr, ""); -#endif + if ((sfd = allocate_sfd(&addr, ""))) + sfd->preallocated = 1; } for (srv = daemon->servers; srv; srv = srv->next) @@ -1328,7 +1405,7 @@ void pre_allocate_sfds(void) errno != 0 && option_bool(OPT_NOWILD)) { - prettyprint_addr(&srv->source_addr, daemon->namebuff); + (void)prettyprint_addr(&srv->source_addr, daemon->namebuff); if (srv->interface[0] != 0) { strcat(daemon->namebuff, " "); @@ -1452,7 +1529,7 @@ void add_update_server(int flags, serv->flags |= SERV_HAS_DOMAIN; if (interface) - strcpy(serv->interface, interface); + safe_strncpy(serv->interface, interface, sizeof(serv->interface)); if (addr) serv->addr = *addr; if (source_addr) @@ -1471,9 +1548,10 @@ void check_servers(void) /* interface may be new since startup */ if (!option_bool(OPT_NOWILD)) enumerate_interfaces(0); - + + /* don't garbage collect pre-allocated sfds. */ for (sfd = daemon->sfds; sfd; sfd = sfd->next) - sfd->used = 0; + sfd->used = sfd->preallocated; for (count = 0, serv = daemon->servers; serv; serv = serv->next) { @@ -1569,7 +1647,7 @@ void check_servers(void) { count--; if (++locals <= LOCALS_LOGGED) - my_syslog(LOG_INFO, _("using local addresses only for %s %s"), s1, s2); + my_syslog(LOG_INFO, _("using only locally-known addresses for %s %s"), s1, s2); } else if (serv->flags & SERV_USE_RESOLV) my_syslog(LOG_INFO, _("using standard nameservers for %s %s"), s1, s2); @@ -1651,7 +1729,6 @@ int reload_servers(char *fname) source_addr.in.sin_addr.s_addr = INADDR_ANY; source_addr.in.sin_port = htons(daemon->query_port); } -#ifdef HAVE_IPV6 else { int scope_index = 0; @@ -1679,10 +1756,6 @@ int reload_servers(char *fname) else continue; } -#else /* IPV6 */ - else - continue; -#endif add_update_server(SERV_FROM_RESOLV, &addr, &source_addr, NULL, NULL); gotone = 1; diff --git a/src/option.c b/src/option.c index d358d99..dbe5f90 100644 --- a/src/option.c +++ b/src/option.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -129,9 +129,6 @@ struct myoption { #define LOPT_AUTHPEER 318 #define LOPT_IPSET 319 #define LOPT_SYNTH 320 -#ifdef OPTION6_PREFIX_CLASS -#define LOPT_PREF_CLSS 321 -#endif #define LOPT_RELAY 323 #define LOPT_RA_PARAM 324 #define LOPT_ADD_SBNET 325 @@ -160,6 +157,16 @@ struct myoption { #define LOPT_DHCPTTL 348 #define LOPT_TFTP_MTU 349 #define LOPT_REPLY_DELAY 350 +#define LOPT_RAPID_COMMIT 351 +#define LOPT_DUMPFILE 352 +#define LOPT_DUMPMASK 353 +#define LOPT_UBUS 354 +#define LOPT_NAME_MATCH 355 +#define LOPT_CAA 356 +#define LOPT_SHARED_NET 357 +#define LOPT_IGNORE_CLID 358 +#define LOPT_SINGLE_PORT 359 +#define LOPT_SCRIPT_TIME 360 #ifdef HAVE_GETOPT_LONG static const struct option opts[] = @@ -228,8 +235,10 @@ static const struct myoption opts[] = { "srv-host", 1, 0, 'W' }, { "localise-queries", 0, 0, 'y' }, { "txt-record", 1, 0, 'Y' }, + { "caa-record", 1, 0 , LOPT_CAA }, { "dns-rr", 1, 0, LOPT_RR }, { "enable-dbus", 2, 0, '1' }, + { "enable-ubus", 2, 0, LOPT_UBUS }, { "bootp-dynamic", 2, 0, '3' }, { "dhcp-mac", 1, 0, '4' }, { "no-ping", 0, 0, '5' }, @@ -237,6 +246,7 @@ static const struct myoption opts[] = { "conf-dir", 1, 0, '7' }, { "log-facility", 1, 0 ,'8' }, { "leasefile-ro", 0, 0, '9' }, + { "script-on-renewal", 0, 0, LOPT_SCRIPT_TIME}, { "dns-forward-max", 1, 0, '0' }, { "clear-on-reload", 0, 0, LOPT_RELOAD }, { "dhcp-ignore-names", 2, 0, LOPT_NO_NAMES }, @@ -248,9 +258,11 @@ static const struct myoption opts[] = { "tftp-max", 1, 0, LOPT_TFTP_MAX }, { "tftp-mtu", 1, 0, LOPT_TFTP_MTU }, { "tftp-lowercase", 0, 0, LOPT_TFTP_LC }, + { "tftp-single-port", 0, 0, LOPT_SINGLE_PORT }, { "ptr-record", 1, 0, LOPT_PTR }, { "naptr-record", 1, 0, LOPT_NAPTR }, { "bridge-interface", 1, 0 , LOPT_BRIDGE }, + { "shared-network", 1, 0, LOPT_SHARED_NET }, { "dhcp-option-force", 1, 0, LOPT_FORCE }, { "tftp-no-blocksize", 0, 0, LOPT_NOBLOCK }, { "log-dhcp", 0, 0, LOPT_LOG_OPTS }, @@ -268,7 +280,8 @@ static const struct myoption opts[] = { "stop-dns-rebind", 0, 0, LOPT_REBIND }, { "rebind-domain-ok", 1, 0, LOPT_NO_REBIND }, { "all-servers", 0, 0, LOPT_NOLAST }, - { "dhcp-match", 1, 0, LOPT_MATCH }, + { "dhcp-match", 1, 0, LOPT_MATCH }, + { "dhcp-name-match", 1, 0, LOPT_NAME_MATCH }, { "dhcp-broadcast", 2, 0, LOPT_BROADCAST }, { "neg-ttl", 1, 0, LOPT_NEGTTL }, { "max-ttl", 1, 0, LOPT_MAXTTL }, @@ -310,12 +323,9 @@ static const struct myoption opts[] = { "dnssec", 0, 0, LOPT_SEC_VALID }, { "trust-anchor", 1, 0, LOPT_TRUST_ANCHOR }, { "dnssec-debug", 0, 0, LOPT_DNSSEC_DEBUG }, - { "dnssec-check-unsigned", 0, 0, LOPT_DNSSEC_CHECK }, + { "dnssec-check-unsigned", 2, 0, LOPT_DNSSEC_CHECK }, { "dnssec-no-timecheck", 0, 0, LOPT_DNSSEC_TIME }, { "dnssec-timestamp", 1, 0, LOPT_DNSSEC_STAMP }, -#ifdef OPTION6_PREFIX_CLASS - { "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS }, -#endif { "dhcp-relay", 1, 0, LOPT_RELAY }, { "ra-param", 1, 0, LOPT_RA_PARAM }, { "quiet-dhcp", 0, 0, LOPT_QUIET_DHCP }, @@ -325,6 +335,10 @@ static const struct myoption opts[] = { "script-arp", 0, 0, LOPT_SCRIPT_ARP }, { "dhcp-ttl", 1, 0 , LOPT_DHCPTTL }, { "dhcp-reply-delay", 1, 0, LOPT_REPLY_DELAY }, + { "dhcp-rapid-commit", 0, 0, LOPT_RAPID_COMMIT }, + { "dumpfile", 1, 0, LOPT_DUMPFILE }, + { "dumpmask", 1, 0, LOPT_DUMPMASK }, + { "dhcp-ignore-clid", 0, 0, LOPT_IGNORE_CLID }, { NULL, 0, 0, 0 } }; @@ -414,10 +428,12 @@ static struct { { 'z', OPT_NOWILD, NULL, gettext_noop("Bind only to interfaces in use."), NULL }, { 'Z', OPT_ETHERS, NULL, gettext_noop("Read DHCP static host information from %s."), ETHERSFILE }, { '1', ARG_ONE, "[=<busname>]", gettext_noop("Enable the DBus interface for setting upstream servers, etc."), NULL }, + { LOPT_UBUS, ARG_ONE, "[=<busname>]", gettext_noop("Enable the UBus interface."), NULL }, { '2', ARG_DUP, "<interface>", gettext_noop("Do not provide DHCP on this interface, only provide DNS."), NULL }, { '3', ARG_DUP, "[=tag:<tag>]...", gettext_noop("Enable dynamic address allocation for bootp."), NULL }, { '4', ARG_DUP, "set:<tag>,<mac address>", gettext_noop("Map MAC address (with wildcards) to option set."), NULL }, { LOPT_BRIDGE, ARG_DUP, "<iface>,<alias>..", gettext_noop("Treat DHCP requests on aliases as arriving from interface."), NULL }, + { LOPT_SHARED_NET, ARG_DUP, "<iface>|<addr>,<addr>", gettext_noop("Specify extra networks sharing a broadcast domain for DHCP"), NULL}, { '5', OPT_NO_PING, NULL, gettext_noop("Disable ICMP echo address checking in the DHCP server."), NULL }, { '6', ARG_ONE, "<path>", gettext_noop("Shell script to run on DHCP lease creation and destruction."), NULL }, { LOPT_LUASCRIPT, ARG_DUP, "path", gettext_noop("Lua script to run on DHCP lease creation and destruction."), NULL }, @@ -440,6 +456,7 @@ static struct { { LOPT_NOBLOCK, OPT_TFTP_NOBLOCK, NULL, gettext_noop("Disable the TFTP blocksize extension."), NULL }, { LOPT_TFTP_LC, OPT_TFTP_LC, NULL, gettext_noop("Convert TFTP filenames to lowercase"), NULL }, { LOPT_TFTPPORTS, ARG_ONE, "<start>,<end>", gettext_noop("Ephemeral port range for use by TFTP transfers."), NULL }, + { LOPT_SINGLE_PORT, OPT_SINGLE_PORT, NULL, gettext_noop("Use only one port for TFTP server."), NULL }, { LOPT_LOG_OPTS, OPT_LOG_OPTS, NULL, gettext_noop("Extra logging for DHCP."), NULL }, { LOPT_MAX_LOGS, ARG_ONE, "[=<integer>]", gettext_noop("Enable async. logging; optionally set queue length."), NULL }, { LOPT_REBIND, OPT_NO_REBIND, NULL, gettext_noop("Stop DNS rebinding. Filter private IP ranges when resolving."), NULL }, @@ -447,6 +464,7 @@ static struct { { LOPT_NO_REBIND, ARG_DUP, "/<domain>/", gettext_noop("Inhibit DNS-rebind protection on this domain."), NULL }, { LOPT_NOLAST, OPT_ALL_SERVERS, NULL, gettext_noop("Always perform DNS queries to all servers."), NULL }, { LOPT_MATCH, ARG_DUP, "set:<tag>,<optspec>", gettext_noop("Set tag if client includes matching option in request."), NULL }, + { LOPT_NAME_MATCH, ARG_DUP, "set:<tag>,<string>[*]", gettext_noop("Set tag if client provides given name."), NULL }, { LOPT_ALTPORT, ARG_ONE, "[=<ports>]", gettext_noop("Use alternative ports for DHCP."), NULL }, { LOPT_NAPTR, ARG_DUP, "<name>,<naptr>", gettext_noop("Specify NAPTR DNS record."), NULL }, { LOPT_MINPORT, ARG_ONE, "<port>", gettext_noop("Specify lowest port available for DNS query transmission."), NULL }, @@ -464,11 +482,13 @@ static struct { { LOPT_CPE_ID, ARG_ONE, "<text>", gettext_noop("Add client identification to forwarded DNS queries."), NULL }, { LOPT_DNSSEC, OPT_DNSSEC_PROXY, NULL, gettext_noop("Proxy DNSSEC validation results from upstream nameservers."), NULL }, { LOPT_INCR_ADDR, OPT_CONSEC_ADDR, NULL, gettext_noop("Attempt to allocate sequential IP addresses to DHCP clients."), NULL }, + { LOPT_IGNORE_CLID, OPT_IGNORE_CLID, NULL, gettext_noop("Ignore client identifier option sent by DHCP clients."), NULL }, { LOPT_CONNTRACK, OPT_CONNTRACK, NULL, gettext_noop("Copy connection-track mark from queries to upstream connections."), NULL }, { LOPT_FQDN, OPT_FQDN_UPDATE, NULL, gettext_noop("Allow DHCP clients to do their own DDNS updates."), NULL }, { LOPT_RA, OPT_RA, NULL, gettext_noop("Send router-advertisements for interfaces doing DHCPv6"), NULL }, { LOPT_DUID, ARG_ONE, "<enterprise>,<duid>", gettext_noop("Specify DUID_EN-type DHCPv6 server DUID"), NULL }, { LOPT_HOST_REC, ARG_DUP, "<name>,<address>[,<ttl>]", gettext_noop("Specify host (A/AAAA and PTR) records"), NULL }, + { LOPT_CAA, ARG_DUP, "<name>,<flags>,<tag>,<value>", gettext_noop("Specify certification authority authorization record"), NULL }, { LOPT_RR, ARG_DUP, "<name>,<RR-number>,[<data>]", gettext_noop("Specify arbitrary DNS resource record"), NULL }, { LOPT_CLVERBIND, OPT_CLEVERBIND, NULL, gettext_noop("Bind to interfaces in use - check for new interfaces"), NULL }, { LOPT_AUTHSERV, ARG_ONE, "<NS>,<interface>", gettext_noop("Export local names to global DNS"), NULL }, @@ -482,12 +502,9 @@ static struct { { LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL }, { LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>],...", gettext_noop("Specify trust anchor key digest."), NULL }, { LOPT_DNSSEC_DEBUG, OPT_DNSSEC_DEBUG, NULL, gettext_noop("Disable upstream checking for DNSSEC debugging."), NULL }, - { LOPT_DNSSEC_CHECK, OPT_DNSSEC_NO_SIGN, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL }, + { LOPT_DNSSEC_CHECK, ARG_DUP, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL }, { LOPT_DNSSEC_TIME, OPT_DNSSEC_TIME, NULL, gettext_noop("Don't check DNSSEC signature timestamps until first cache-reload"), NULL }, { LOPT_DNSSEC_STAMP, ARG_ONE, "<path>", gettext_noop("Timestamp file to verify system clock for DNSSEC"), NULL }, -#ifdef OPTION6_PREFIX_CLASS - { LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL }, -#endif { LOPT_RA_PARAM, ARG_DUP, "<iface>,[mtu:<value>|<interface>|off,][<prio>,]<intval>[,<lifetime>]", gettext_noop("Set MTU, priority, resend-interval and router-lifetime"), NULL }, { LOPT_QUIET_DHCP, OPT_QUIET_DHCP, NULL, gettext_noop("Do not log routine DHCP."), NULL }, { LOPT_QUIET_DHCP6, OPT_QUIET_DHCP6, NULL, gettext_noop("Do not log routine DHCPv6."), NULL }, @@ -497,6 +514,10 @@ static struct { { LOPT_IGNORE_ADDR, ARG_DUP, "<ipaddr>", gettext_noop("Ignore DNS responses containing ipaddr."), NULL }, { LOPT_DHCPTTL, ARG_ONE, "<ttl>", gettext_noop("Set TTL in DNS responses with DHCP-derived addresses."), NULL }, { LOPT_REPLY_DELAY, ARG_ONE, "<integer>", gettext_noop("Delay DHCP replies for at least number of seconds."), NULL }, + { LOPT_RAPID_COMMIT, OPT_RAPID_COMMIT, NULL, gettext_noop("Enables DHCPv4 Rapid Commit option."), NULL }, + { LOPT_DUMPFILE, ARG_ONE, "<path>", gettext_noop("Path to debug packet dump file"), NULL }, + { LOPT_DUMPMASK, ARG_ONE, "<hex>", gettext_noop("Mask which packets to dump"), NULL }, + { LOPT_SCRIPT_TIME, OPT_LEASE_RENEW, NULL, gettext_noop("Call dhcp-script when lease expiry changes."), NULL }, { 0, 0, NULL, NULL, NULL } }; @@ -559,14 +580,15 @@ static void *opt_malloc(size_t size) return ret; } -static char *opt_string_alloc(char *cp) +static char *opt_string_alloc(const char *cp) { char *ret = NULL; + size_t len; - if (cp && strlen(cp) != 0) + if (cp && (len = strlen(cp)) != 0) { - ret = opt_malloc(strlen(cp)+1); - strcpy(ret, cp); + ret = opt_malloc(len+1); + memcpy(ret, cp, len+1); /* restore hidden metachars */ unhide_metas(ret); @@ -741,15 +763,15 @@ static void do_usage(void) } #define ret_err(x) do { strcpy(errstr, (x)); return 0; } while (0) +#define ret_err_free(x,m) do { strcpy(errstr, (x)); free((m)); return 0; } while (0) +#define goto_err(x) do { strcpy(errstr, (x)); goto on_error; } while (0) static char *parse_mysockaddr(char *arg, union mysockaddr *addr) { if (inet_pton(AF_INET, arg, &addr->in.sin_addr) > 0) addr->sa.sa_family = AF_INET; -#ifdef HAVE_IPV6 else if (inet_pton(AF_INET6, arg, &addr->in6.sin6_addr) > 0) addr->sa.sa_family = AF_INET6; -#endif else return _("bad address"); @@ -761,10 +783,8 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a int source_port = 0, serv_port = NAMESERVER_PORT; char *portno, *source; char *interface_opt = NULL; -#ifdef HAVE_IPV6 int scope_index = 0; char *scope_id; -#endif if (!arg || strlen(arg) == 0) { @@ -782,9 +802,7 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a !atoi_check16(portno, &serv_port)) return _("bad port"); -#ifdef HAVE_IPV6 scope_id = split_chr(arg, '%'); -#endif if (source) { interface_opt = split_chr(source, '@'); @@ -792,7 +810,7 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a if (interface_opt) { #if defined(SO_BINDTODEVICE) - strncpy(interface, interface_opt, IF_NAMESIZE - 1); + safe_strncpy(interface, interface_opt, IF_NAMESIZE); #else return _("interface binding not supported"); #endif @@ -821,14 +839,13 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a return _("interface can only be specified once"); source_addr->in.sin_addr.s_addr = INADDR_ANY; - strncpy(interface, source, IF_NAMESIZE - 1); + safe_strncpy(interface, source, IF_NAMESIZE); #else return _("interface binding not supported"); #endif } } } -#ifdef HAVE_IPV6 else if (inet_pton(AF_INET6, arg, &addr->in6.sin6_addr) > 0) { if (scope_id && (scope_index = if_nametoindex(scope_id)) == 0) @@ -856,14 +873,13 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a return _("interface can only be specified once"); source_addr->in6.sin6_addr = in6addr_any; - strncpy(interface, source, IF_NAMESIZE - 1); + safe_strncpy(interface, source, IF_NAMESIZE); #else return _("interface binding not supported"); #endif } } } -#endif else return _("bad address"); @@ -894,6 +910,8 @@ static struct server *add_rev4(struct in_addr addr, int msize) p += sprintf(p, "%d.", (a >> 24) & 0xff); break; default: + free(serv->domain); + free(serv); return NULL; } @@ -948,6 +966,116 @@ static char *set_prefix(char *arg) return arg; } +static struct dhcp_netid *dhcp_netid_create(const char *net, struct dhcp_netid *next) +{ + struct dhcp_netid *tt; + tt = opt_malloc(sizeof (struct dhcp_netid)); + tt->net = opt_string_alloc(net); + tt->next = next; + return tt; +} + +static void dhcp_netid_free(struct dhcp_netid *nid) +{ + while (nid) + { + struct dhcp_netid *tmp = nid; + nid = nid->next; + free(tmp->net); + free(tmp); + } +} + +/* Parse one or more tag:s before parameters. + * Moves arg to the end of tags. */ +static struct dhcp_netid * dhcp_tags(char **arg) +{ + struct dhcp_netid *id = NULL; + + while (is_tag_prefix(*arg)) + { + char *comma = split(*arg); + id = dhcp_netid_create((*arg)+4, id); + *arg = comma; + }; + if (!*arg) + { + dhcp_netid_free(id); + id = NULL; + } + return id; +} + +static void dhcp_netid_list_free(struct dhcp_netid_list *netid) +{ + while (netid) + { + struct dhcp_netid_list *tmplist = netid; + netid = netid->next; + dhcp_netid_free(tmplist->list); + free(tmplist); + } +} + +static void dhcp_config_free(struct dhcp_config *config) +{ + if (config) + { + struct hwaddr_config *hwaddr = config->hwaddr; + + while (hwaddr) + { + struct hwaddr_config *tmp = hwaddr; + hwaddr = hwaddr->next; + free(tmp); + } + + dhcp_netid_list_free(config->netid); + dhcp_netid_free(config->filter); + + if (config->flags & CONFIG_CLID) + free(config->clid); + +#ifdef HAVE_DHCP6 + if (config->flags & CONFIG_ADDR6) + { + struct addrlist *addr, *tmp; + + for (addr = config->addr6; addr; addr = tmp) + { + tmp = addr->next; + free(addr); + } + } +#endif + + free(config); + } +} + +static void dhcp_context_free(struct dhcp_context *ctx) +{ + if (ctx) + { + dhcp_netid_free(ctx->filter); + free(ctx->netid.net); +#ifdef HAVE_DHCP6 + free(ctx->template_interface); +#endif + free(ctx); + } +} + +static void dhcp_opt_free(struct dhcp_opt *opt) +{ + if (opt->flags & DHOPT_VENDOR) + free(opt->u.vendor_class); + dhcp_netid_free(opt->netid); + free(opt->val); + free(opt); +} + + /* This is too insanely large to keep in-line in the switch */ static int parse_dhcp_opt(char *errstr, char *arg, int flags) { @@ -955,7 +1083,6 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) char lenchar = 0, *cp; int addrs, digs, is_addr, is_addr6, is_hex, is_dec, is_string, dots; char *comma = NULL; - struct dhcp_netid *np = NULL; u16 opt_len = 0; int is6 = 0; int option_ok = 0; @@ -1042,14 +1169,9 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) } else { - new->netid = opt_malloc(sizeof (struct dhcp_netid)); /* allow optional "net:" or "tag:" for consistency */ - if (is_tag_prefix(arg)) - new->netid->net = opt_string_alloc(arg+4); - else - new->netid->net = opt_string_alloc(set_prefix(arg)); - new->netid->next = np; - np = new->netid; + const char *name = (is_tag_prefix(arg)) ? arg+4 : set_prefix(arg); + new->netid = dhcp_netid_create(name, new->netid); } arg = comma; @@ -1059,7 +1181,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) if (is6) { if (new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE)) - ret_err(_("unsupported encapsulation for IPv6 option")); + goto_err(_("unsupported encapsulation for IPv6 option")); if (opt_len == 0 && !(new->flags & DHOPT_RFC3925)) @@ -1073,13 +1195,13 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) /* option may be missing with rfc3925 match */ if (!option_ok) - ret_err(_("bad dhcp-option")); + goto_err(_("bad dhcp-option")); if (comma) { /* characterise the value */ char c; - int found_dig = 0; + int found_dig = 0, found_colon = 0; is_addr = is_addr6 = is_hex = is_dec = is_string = 1; addrs = digs = 1; dots = 0; @@ -1093,6 +1215,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) { digs++; is_dec = is_addr = 0; + found_colon = 1; } else if (c == '/') { @@ -1102,7 +1225,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) } else if (c == '.') { - is_addr6 = is_dec = is_hex = 0; + is_dec = is_hex = 0; dots++; } else if (c == '-') @@ -1134,6 +1257,15 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) if (!found_dig) is_dec = is_addr = 0; + + if (!found_colon) + is_addr6 = 0; + +#ifdef HAVE_DHCP6 + /* NTP server option takes hex, addresses or FQDN */ + if (is6 && new->opt == OPTION6_NTP_SERVER && !is_hex) + opt_len |= is_addr6 ? OT_ADDR_LIST : OT_RFC1035_NAME; +#endif /* We know that some options take addresses */ if (opt_len & OT_ADDR_LIST) @@ -1141,10 +1273,10 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) is_string = is_dec = is_hex = 0; if (!is6 && (!is_addr || dots == 0)) - ret_err(_("bad IP address")); + goto_err(_("bad IP address")); if (is6 && !is_addr6) - ret_err(_("bad IPv6 address")); + goto_err(_("bad IPv6 address")); } /* or names */ else if (opt_len & (OT_NAME | OT_RFC1035_NAME | OT_CSTRING)) @@ -1237,7 +1369,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) comma = split(cp); slash = split_chr(cp, '/'); if (!inet_pton(AF_INET, cp, &in)) - ret_err(_("bad IPv4 address")); + goto_err(_("bad IPv4 address")); if (!slash) { memcpy(op, &in, INADDRSZ); @@ -1282,8 +1414,8 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) op += IN6ADDRSZ; continue; } - - ret_err(_("bad IPv6 address")); + + goto_err(_("bad IPv6 address")); } new->len = op - new->val; } @@ -1310,7 +1442,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) if (strcmp (arg, ".") != 0) { if (!(dom = canonicalise_opt(arg))) - ret_err(_("bad domain in dhcp-option")); + goto_err(_("bad domain in dhcp-option")); domlen = strlen(dom) + 2; } @@ -1357,7 +1489,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) } /* RFC 3361, enc byte is zero for names */ - if (new->opt == OPTION_SIP_SERVER) + if (new->opt == OPTION_SIP_SERVER && m) m[0] = 0; new->len = (int) len + header_size; new->val = m; @@ -1395,8 +1527,9 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) } else if (comma && (opt_len & OT_RFC1035_NAME)) { - unsigned char *p = NULL, *newp, *end; + unsigned char *p = NULL, *q, *newp, *end; int len = 0; + int header_size = (is6 && new->opt == OPTION6_NTP_SERVER) ? 4 : 0; arg = comma; comma = split(arg); @@ -1404,9 +1537,9 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) { char *dom = canonicalise_opt(arg); if (!dom) - ret_err(_("bad domain in dhcp-option")); + goto_err(_("bad domain in dhcp-option")); - newp = opt_malloc(len + strlen(dom) + 2); + newp = opt_malloc(len + header_size + strlen(dom) + 2); if (p) { @@ -1415,8 +1548,14 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) } p = newp; - end = do_rfc1035_name(p + len, dom, NULL); + q = p + len; + end = do_rfc1035_name(q + header_size, dom, NULL); *end++ = 0; + if (is6 && new->opt == OPTION6_NTP_SERVER) + { + PUTSHORT(NTP_SUBOPTION_SRV_FQDN, q); + PUTSHORT(end - q - 2, q); + } len = end - p; free(dom); @@ -1442,14 +1581,14 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) ((new->len > 255) || (new->len > 253 && (new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE))) || (new->len > 250 && (new->flags & DHOPT_RFC3925)))) - ret_err(_("dhcp-option too long")); + goto_err(_("dhcp-option too long")); if (flags == DHOPT_MATCH) { if ((new->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR)) || !new->netid || new->netid->next) - ret_err(_("illegal dhcp-match")); + goto_err(_("illegal dhcp-match")); if (is6) { @@ -1474,24 +1613,31 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) } return 1; +on_error: + dhcp_opt_free(new); + return 0; } #endif void set_option_bool(unsigned int opt) { - if (opt < 32) - daemon->options |= 1u << opt; - else - daemon->options2 |= 1u << (opt - 32); + option_var(opt) |= option_val(opt); } void reset_option_bool(unsigned int opt) { - if (opt < 32) - daemon->options &= ~(1u << opt); - else - daemon->options2 &= ~(1u << (opt - 32)); + option_var(opt) &= ~(option_val(opt)); +} + +static void server_list_free(struct server *list) +{ + while (list) + { + struct server *tmp = list; + list = list->next; + free(tmp); + } } static int one_opt(int option, char *arg, char *errstr, char *gen_err, int command_line, int servers_only) @@ -1552,9 +1698,9 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma struct dirent *ent; char *directory, *path; struct list { - char *suffix; + char *name; struct list *next; - } *ignore_suffix = NULL, *match_suffix = NULL, *li; + } *ignore_suffix = NULL, *match_suffix = NULL, *files = NULL, *li; comma = split(arg); if (!(directory = opt_string_alloc(arg))) @@ -1576,7 +1722,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma li->next = match_suffix; match_suffix = li; /* Have to copy: buffer is overwritten */ - li->suffix = opt_string_alloc(arg+1); + li->name = opt_string_alloc(arg+1); } } else @@ -1584,7 +1730,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma li->next = ignore_suffix; ignore_suffix = li; /* Have to copy: buffer is overwritten */ - li->suffix = opt_string_alloc(arg); + li->name = opt_string_alloc(arg); } } } @@ -1609,9 +1755,9 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma for (li = match_suffix; li; li = li->next) { /* check for required suffices */ - size_t ls = strlen(li->suffix); + size_t ls = strlen(li->name); if (len > ls && - strcmp(li->suffix, &ent->d_name[len - ls]) == 0) + strcmp(li->name, &ent->d_name[len - ls]) == 0) break; } if (!li) @@ -1621,9 +1767,9 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma for (li = ignore_suffix; li; li = li->next) { /* check for proscribed suffices */ - size_t ls = strlen(li->suffix); + size_t ls = strlen(li->name); if (len > ls && - strcmp(li->suffix, &ent->d_name[len - ls]) == 0) + strcmp(li->name, &ent->d_name[len - ls]) == 0) break; } if (li) @@ -1640,25 +1786,44 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma /* only reg files allowed. */ if (S_ISREG(buf.st_mode)) - one_file(path, 0); - - free(path); + { + /* sort files into order. */ + struct list **up, *new = opt_malloc(sizeof(struct list)); + new->name = path; + + for (up = &files, li = files; li; up = &li->next, li = li->next) + if (strcmp(li->name, path) >=0) + break; + + new->next = li; + *up = new; + } + } - + + for (li = files; li; li = li->next) + one_file(li->name, 0); + closedir(dir_stream); free(directory); for(; ignore_suffix; ignore_suffix = li) { li = ignore_suffix->next; - free(ignore_suffix->suffix); + free(ignore_suffix->name); free(ignore_suffix); } for(; match_suffix; match_suffix = li) { li = match_suffix->next; - free(match_suffix->suffix); + free(match_suffix->name); free(match_suffix); } + for(; files; files = li) + { + li = files->next; + free(files->name); + free(files); + } break; } @@ -1675,13 +1840,13 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma /* has subnet+len */ err = parse_mysockaddr(arg, &new->addr); if (err) - ret_err(err); + ret_err_free(err, new); if (!atoi_check(end, &new->mask)) - ret_err(gen_err); + ret_err_free(gen_err, new); new->addr_used = 1; } else if (!atoi_check(arg, &new->mask)) - ret_err(gen_err); + ret_err_free(gen_err, new); daemon->add_subnet4 = new; @@ -1693,15 +1858,15 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma /* has subnet+len */ err = parse_mysockaddr(comma, &new->addr); if (err) - ret_err(err); + ret_err_free(err, new); if (!atoi_check(end, &new->mask)) - ret_err(gen_err); + ret_err_free(gen_err, new); new->addr_used = 1; } else { if (!atoi_check(comma, &new->mask)) - ret_err(gen_err); + ret_err_free(gen_err, new); } daemon->add_subnet6 = new; @@ -1716,7 +1881,15 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma else daemon->dbus_name = DNSMASQ_SERVICE; break; - + + case LOPT_UBUS: /* --enable-ubus */ + set_option_bool(OPT_UBUS); + if (arg) + daemon->ubus_name = opt_string_alloc(arg); + else + daemon->ubus_name = DNSMASQ_UBUS_NAME; + break; + case '8': /* --log-facility */ /* may be a filename */ if (strchr(arg, '/') || strcmp (arg, "-") == 0) @@ -1737,7 +1910,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma #endif } break; - + case 'x': /* --pid-file */ daemon->runfile = opt_string_alloc(arg); break; @@ -1808,6 +1981,14 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma ret_err(_("bad MX target")); break; + case LOPT_DUMPFILE: /* --dumpfile */ + daemon->dump_file = opt_string_alloc(arg); + break; + + case LOPT_DUMPMASK: /* --dumpmask */ + daemon->dump_mask = strtol(arg, NULL, 0); + break; + #ifdef HAVE_DHCP case 'l': /* --dhcp-leasefile */ daemon->lease_file = opt_string_alloc(arg); @@ -1816,9 +1997,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma /* Sorry about the gross pre-processor abuse */ case '6': /* --dhcp-script */ case LOPT_LUASCRIPT: /* --dhcp-luascript */ -# if defined(NO_FORK) - ret_err(_("cannot run scripts under uClinux")); -# elif !defined(HAVE_SCRIPT) +# if !defined(HAVE_SCRIPT) ret_err(_("recompile with HAVE_SCRIPT defined to enable lease-change scripts")); # else if (option == LOPT_LUASCRIPT) @@ -1875,47 +2054,42 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma break; } - -#ifdef HAVE_AUTH case LOPT_AUTHSERV: /* --auth-server */ - if (!(comma = split(arg))) - ret_err(gen_err); + comma = split(arg); daemon->authserver = opt_string_alloc(arg); - arg = comma; - do { - struct iname *new = opt_malloc(sizeof(struct iname)); - comma = split(arg); - new->name = NULL; - unhide_metas(arg); - if (inet_pton(AF_INET, arg, &new->addr.in.sin_addr) > 0) - new->addr.sa.sa_family = AF_INET; -#ifdef HAVE_IPV6 - else if (inet_pton(AF_INET6, arg, &new->addr.in6.sin6_addr) > 0) - new->addr.sa.sa_family = AF_INET6; -#endif - else - { - char *fam = split_chr(arg, '/'); - new->name = opt_string_alloc(arg); - new->addr.sa.sa_family = 0; - if (fam) - { - if (strcmp(fam, "4") == 0) - new->addr.sa.sa_family = AF_INET; -#ifdef HAVE_IPV6 - else if (strcmp(fam, "6") == 0) - new->addr.sa.sa_family = AF_INET6; -#endif - else - ret_err(gen_err); - } - } - new->next = daemon->authinterface; - daemon->authinterface = new; - - arg = comma; - } while (arg); + + while ((arg = comma)) + { + struct iname *new = opt_malloc(sizeof(struct iname)); + comma = split(arg); + new->name = NULL; + unhide_metas(arg); + if (inet_pton(AF_INET, arg, &new->addr.in.sin_addr) > 0) + new->addr.sa.sa_family = AF_INET; + else if (inet_pton(AF_INET6, arg, &new->addr.in6.sin6_addr) > 0) + new->addr.sa.sa_family = AF_INET6; + else + { + char *fam = split_chr(arg, '/'); + new->name = opt_string_alloc(arg); + new->addr.sa.sa_family = 0; + if (fam) + { + if (strcmp(fam, "4") == 0) + new->addr.sa.sa_family = AF_INET; + else if (strcmp(fam, "6") == 0) + new->addr.sa.sa_family = AF_INET6; + else + { + free(new->name); + ret_err_free(gen_err, new); + } + } + } + new->next = daemon->authinterface; + daemon->authinterface = new; + }; break; @@ -1954,7 +2128,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma int is_exclude = 0; char *prefix; struct addrlist *subnet = NULL; - struct all_addr addr; + union all_addr addr; comma = split(arg); prefix = split_chr(arg, '/'); @@ -1968,20 +2142,18 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma arg = arg+8; } - if (inet_pton(AF_INET, arg, &addr.addr.addr4)) + if (inet_pton(AF_INET, arg, &addr.addr4)) { subnet = opt_malloc(sizeof(struct addrlist)); subnet->prefixlen = (prefixlen == 0) ? 24 : prefixlen; subnet->flags = ADDRLIST_LITERAL; } -#ifdef HAVE_IPV6 - else if (inet_pton(AF_INET6, arg, &addr.addr.addr6)) + else if (inet_pton(AF_INET6, arg, &addr.addr6)) { subnet = opt_malloc(sizeof(struct addrlist)); subnet->prefixlen = (prefixlen == 0) ? 64 : prefixlen; subnet->flags = ADDRLIST_LITERAL | ADDRLIST_IPV6; } -#endif else { struct auth_name_list *name = opt_malloc(sizeof(struct auth_name_list)); @@ -1993,10 +2165,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma { if (prefixlen == 4) name->flags &= ~AUTH6; -#ifdef HAVE_IPV6 else if (prefixlen == 6) name->flags &= ~AUTH4; -#endif else ret_err(gen_err); } @@ -2051,7 +2221,6 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } break; -#endif case 's': /* --domain */ case LOPT_SYNTH: /* --synth-domain */ @@ -2080,7 +2249,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma arg = split(netpart); if (!atoi_check(netpart, &msize)) - ret_err(gen_err); + ret_err_free(gen_err, new); else if (inet_pton(AF_INET, comma, &new->start)) { int mask = (1 << (32 - msize)) - 1; @@ -2093,18 +2262,18 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma { if (!(new->prefix = canonicalise_opt(arg)) || strlen(new->prefix) > MAXLABEL - INET_ADDRSTRLEN) - ret_err(_("bad prefix")); + ret_err_free(_("bad prefix"), new); } else if (strcmp(arg, "local") != 0 || (msize != 8 && msize != 16 && msize != 24)) - ret_err(gen_err); + ret_err_free(gen_err, new); else { /* generate the equivalent of local=/xxx.yyy.zzz.in-addr.arpa/ */ struct server *serv = add_rev4(new->start, msize); if (!serv) - ret_err(_("bad prefix")); + ret_err_free(_("bad prefix"), new); serv->flags |= SERV_NO_ADDR; @@ -2118,7 +2287,6 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } } } -#ifdef HAVE_IPV6 else if (inet_pton(AF_INET6, comma, &new->start6)) { u64 mask = (1LLU << (128 - msize)) - 1LLU; @@ -2134,17 +2302,17 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma setaddr6part(&new->end6, addrpart | mask); if (msize < 64) - ret_err(gen_err); + ret_err_free(gen_err, new); else if (arg) { if (option != 's') { if (!(new->prefix = canonicalise_opt(arg)) || strlen(new->prefix) > MAXLABEL - INET6_ADDRSTRLEN) - ret_err(_("bad prefix")); + ret_err_free(_("bad prefix"), new); } else if (strcmp(arg, "local") != 0 || ((msize & 4) != 0)) - ret_err(gen_err); + ret_err_free(gen_err, new); else { /* generate the equivalent of @@ -2162,9 +2330,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } } } -#endif else - ret_err(gen_err); + ret_err_free(gen_err, new); } else { @@ -2178,26 +2345,24 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma if (!arg) new->end.s_addr = new->start.s_addr; else if (!inet_pton(AF_INET, arg, &new->end)) - ret_err(gen_err); + ret_err_free(gen_err, new); } -#ifdef HAVE_IPV6 else if (inet_pton(AF_INET6, comma, &new->start6)) { new->is6 = 1; if (!arg) memcpy(&new->end6, &new->start6, IN6ADDRSZ); else if (!inet_pton(AF_INET6, arg, &new->end6)) - ret_err(gen_err); + ret_err_free(gen_err, new); } -#endif else - ret_err(gen_err); + ret_err_free(gen_err, new); if (option != 's' && prefstr) { if (!(new->prefix = canonicalise_opt(prefstr)) || strlen(new->prefix) > MAXLABEL - INET_ADDRSTRLEN) - ret_err(_("bad prefix")); + ret_err_free(_("bad prefix"), new); } } @@ -2212,7 +2377,9 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma char *star; new->next = daemon->synth_domains; daemon->synth_domains = new; - if ((star = strrchr(new->prefix, '*')) && *(star+1) == 0) + if (new->prefix && + (star = strrchr(new->prefix, '*')) + && *(star+1) == 0) { *star = 0; new->indexed = 1; @@ -2346,7 +2513,6 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma new->addr.in.sin_len = sizeof(new->addr.in); #endif } -#ifdef HAVE_IPV6 else if (arg && inet_pton(AF_INET6, arg, &new->addr.in6.sin6_addr) > 0) { new->addr.sa.sa_family = AF_INET6; @@ -2357,9 +2523,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma new->addr.in6.sin6_len = sizeof(new->addr.in6); #endif } -#endif else - ret_err(gen_err); + ret_err_free(gen_err, new); new->used = 0; if (option == 'a') @@ -2400,7 +2565,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma if (strcmp(arg, "#") == 0) domain = ""; else if (strlen (arg) != 0 && !(domain = canonicalise_opt(arg))) - option = '?'; + ret_err(gen_err); serv = opt_malloc(sizeof(struct server)); memset(serv, 0, sizeof(struct server)); serv->next = newlist; @@ -2430,7 +2595,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma { newlist->flags |= SERV_LITERAL_ADDRESS; if (!(newlist->flags & SERV_TYPE)) - ret_err(gen_err); + { + server_list_free(newlist); + ret_err(gen_err); + } } else if (option == LOPT_NO_REBIND) newlist->flags |= SERV_NO_REBIND; @@ -2442,22 +2610,21 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } else if (strcmp(arg, "#") == 0) - { - newlist->flags |= SERV_USE_RESOLV; /* treat in ordinary way */ - if (newlist->flags & SERV_LITERAL_ADDRESS) - ret_err(gen_err); - } + newlist->flags |= SERV_USE_RESOLV; /* treat in ordinary way */ else { char *err = parse_server(arg, &newlist->addr, &newlist->source_addr, newlist->interface, &newlist->flags); if (err) - ret_err(err); + { + server_list_free(newlist); + ret_err(err); + } } serv = newlist; while (serv->next) { - serv->next->flags = serv->flags; + serv->next->flags |= serv->flags & ~(SERV_HAS_DOMAIN | SERV_FOR_NODOTS); serv->next->addr = serv->addr; serv->next->source_addr = serv->source_addr; strcpy(serv->next->interface, serv->interface); @@ -2474,24 +2641,25 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma int size; struct server *serv; struct in_addr addr4; -#ifdef HAVE_IPV6 struct in6_addr addr6; -#endif unhide_metas(arg); - if (!arg || !(comma=split(arg)) || !(string = split_chr(arg, '/')) || !atoi_check(string, &size)) + if (!arg) ret_err(gen_err); + + comma=split(arg); + if (!(string = split_chr(arg, '/')) || !atoi_check(string, &size)) + ret_err(gen_err); + if (inet_pton(AF_INET, arg, &addr4)) { serv = add_rev4(addr4, size); if (!serv) ret_err(_("bad prefix")); } -#ifdef HAVE_IPV6 else if (inet_pton(AF_INET6, arg, &addr6)) serv = add_rev6(&addr6, size); -#endif else ret_err(gen_err); @@ -2532,7 +2700,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma if (strcmp(arg, "#") == 0 || !*arg) domain = ""; else if (strlen(arg) != 0 && !(domain = canonicalise_opt(arg))) - option = '?'; + ret_err(gen_err); ipsets->next = opt_malloc(sizeof(struct ipsets)); ipsets = ipsets->next; memset(ipsets, 0, sizeof(struct ipsets)); @@ -2547,13 +2715,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma memset(ipsets, 0, sizeof(struct ipsets)); ipsets->domain = ""; } + if (!arg || !*arg) - { - option = '?'; - break; - } - size = 2; - for (end = arg; *end; ++end) + ret_err(gen_err); + + for (size = 2, end = arg; *end; ++end) if (*end == ',') ++size; @@ -2586,8 +2752,14 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma if (size < 0) size = 0; - else if (size > 10000) - size = 10000; + + /* Note that for very large cache sizes, the malloc() + will overflow. For the size of the cache record + at the time this was noted, the value of "very large" + was 46684428. Limit to an order of magnitude less than + that to be safe from changes to the cache record. */ + if (size > 5000000) + size = 5000000; daemon->cachesize = size; } @@ -2774,6 +2946,44 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } #ifdef HAVE_DHCP + case LOPT_SHARED_NET: /* --shared-network */ + { + struct shared_network *new = opt_malloc(sizeof(struct shared_network)); + +#ifdef HAVE_DHCP6 + new->shared_addr.s_addr = 0; +#endif + new->if_index = 0; + + if (!(comma = split(arg))) + { + snerr: + free(new); + ret_err(_("bad shared-network")); + } + + if (inet_pton(AF_INET, comma, &new->shared_addr)) + { + if (!inet_pton(AF_INET, arg, &new->match_addr) && + !(new->if_index = if_nametoindex(arg))) + goto snerr; + } +#ifdef HAVE_DHCP6 + else if (inet_pton(AF_INET6, comma, &new->shared_addr6)) + { + if (!inet_pton(AF_INET6, arg, &new->match_addr6) && + !(new->if_index = if_nametoindex(arg))) + goto snerr; + } +#endif + else + goto snerr; + + new->next = daemon->shared_networks; + daemon->shared_networks = new; + break; + } + case 'F': /* --dhcp-range */ { int k, leasepos = 2; @@ -2781,13 +2991,6 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma struct dhcp_context *new = opt_malloc(sizeof(struct dhcp_context)); memset (new, 0, sizeof(*new)); - new->lease_time = DEFLEASE; - - if (!arg) - { - option = '?'; - break; - } while(1) { @@ -2801,21 +3004,19 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma { if (is_tag_prefix(arg)) { - struct dhcp_netid *tt = opt_malloc(sizeof (struct dhcp_netid)); - tt->net = opt_string_alloc(arg+4); - tt->next = new->filter; /* ignore empty tag */ - if (tt->net) - new->filter = tt; + if (arg[4]) + new->filter = dhcp_netid_create(arg+4, new->filter); } else { if (new->netid.net) - ret_err(_("only one tag allowed")); - else if (strstr(arg, "set:") == arg) - new->netid.net = opt_string_alloc(arg+4); + { + dhcp_context_free(new); + ret_err(_("only one tag allowed")); + } else - new->netid.net = opt_string_alloc(arg); + new->netid.net = opt_string_alloc(set_prefix(arg)); } arg = comma; } @@ -2831,11 +3032,15 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma break; if (k < 2) - ret_err(_("bad dhcp-range")); + { + dhcp_context_free(new); + ret_err(_("bad dhcp-range")); + } if (inet_pton(AF_INET, a[0], &new->start)) { new->next = daemon->dhcp; + new->lease_time = DEFLEASE; daemon->dhcp = new; new->end = new->start; if (strcmp(a[1], "static") == 0) @@ -2843,7 +3048,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma else if (strcmp(a[1], "proxy") == 0) new->flags |= CONTEXT_PROXY; else if (!inet_pton(AF_INET, a[1], &new->end)) - ret_err(_("bad dhcp-range")); + { + dhcp_context_free(new); + ret_err(_("bad dhcp-range")); + } if (ntohl(new->start.s_addr) > ntohl(new->end.s_addr)) { @@ -2858,7 +3066,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma new->flags |= CONTEXT_NETMASK; leasepos = 3; if (!is_same_net(new->start, new->end, new->netmask)) - ret_err(_("inconsistent DHCP range")); + { + dhcp_context_free(new); + ret_err(_("inconsistent DHCP range")); + } if (k >= 4 && strchr(a[3], '.') && @@ -2872,9 +3083,12 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma #ifdef HAVE_DHCP6 else if (inet_pton(AF_INET6, a[0], &new->start6)) { + const char *err = NULL; + new->flags |= CONTEXT_V6; new->prefix = 64; /* default */ new->end6 = new->start6; + new->lease_time = DEFLEASE6; new->next = daemon->dhcp6; daemon->dhcp6 = new; @@ -2917,19 +3131,24 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } } - if (new->prefix != 64) + if (new->prefix > 64) { if (new->flags & CONTEXT_RA) - ret_err(_("prefix length must be exactly 64 for RA subnets")); + err=(_("prefix length must be exactly 64 for RA subnets")); else if (new->flags & CONTEXT_TEMPLATE) - ret_err(_("prefix length must be exactly 64 for subnet constructors")); + err=(_("prefix length must be exactly 64 for subnet constructors")); } - - if (new->prefix < 64) - ret_err(_("prefix length must be at least 64")); + else if (new->prefix < 64) + err=(_("prefix length must be at least 64")); - if (!is_same_net6(&new->start6, &new->end6, new->prefix)) - ret_err(_("inconsistent DHCPv6 range")); + if (!err && !is_same_net6(&new->start6, &new->end6, new->prefix)) + err=(_("inconsistent DHCPv6 range")); + + if (err) + { + dhcp_context_free(new); + ret_err(err); + } /* dhcp-range=:: enables DHCP stateless on any interface */ if (IN6_IS_ADDR_UNSPECIFIED(&new->start6) && !(new->flags & CONTEXT_TEMPLATE)) @@ -2940,7 +3159,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma struct in6_addr zero; memset(&zero, 0, sizeof(zero)); if (!is_same_net6(&zero, &new->start6, new->prefix)) - ret_err(_("prefix must be zero with \"constructor:\" argument")); + { + dhcp_context_free(new); + ret_err(_("prefix must be zero with \"constructor:\" argument")); + } } if (addr6part(&new->start6) > addr6part(&new->end6)) @@ -2952,15 +3174,24 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } #endif else - ret_err(_("bad dhcp-range")); + { + dhcp_context_free(new); + ret_err(_("bad dhcp-range")); + } if (leasepos < k) { if (leasepos != k-1) - ret_err(_("bad dhcp-range")); + { + dhcp_context_free(new); + ret_err(_("bad dhcp-range")); + } if (strcmp(a[leasepos], "infinite") == 0) - new->lease_time = 0xffffffff; + { + new->lease_time = 0xffffffff; + new->flags |= CONTEXT_SETLEASE; + } else if (strcmp(a[leasepos], "deprecated") == 0) new->flags |= CONTEXT_DEPRECATE; else @@ -2996,9 +3227,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma break; if (*cp || (leasepos+1 < k)) - ret_err(_("bad dhcp-range")); + ret_err_free(_("bad dhcp-range"), new); new->lease_time = atoi(a[leasepos]) * fac; + new->flags |= CONTEXT_SETLEASE; /* Leases of a minute or less confuse some clients, notably Apple's */ if (new->lease_time < 120) @@ -3006,14 +3238,13 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } } } + break; } case LOPT_BANK: case 'G': /* --dhcp-host */ { - int j, k = 0; - char *a[7] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL }; struct dhcp_config *new; struct in_addr in; @@ -3023,185 +3254,223 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma new->flags = (option == LOPT_BANK) ? CONFIG_BANK : 0; new->hwaddr = NULL; new->netid = NULL; - - if ((a[0] = arg)) - for (k = 1; k < 7; k++) - if (!(a[k] = split(a[k-1]))) - break; - - for (j = 0; j < k; j++) - if (strchr(a[j], ':')) /* ethernet address, netid or binary CLID */ - { - char *arg = a[j]; - - if ((arg[0] == 'i' || arg[0] == 'I') && - (arg[1] == 'd' || arg[1] == 'D') && - arg[2] == ':') - { - if (arg[3] == '*') - new->flags |= CONFIG_NOCLID; - else - { - int len; - arg += 3; /* dump id: */ - if (strchr(arg, ':')) - len = parse_hex(arg, (unsigned char *)arg, -1, NULL, NULL); - else - { - unhide_metas(arg); - len = (int) strlen(arg); - } - - if (len == -1) - ret_err(_("bad hex constant")); - else if ((new->clid = opt_malloc(len))) - { - new->flags |= CONFIG_CLID; - new->clid_len = len; - memcpy(new->clid, arg, len); - } - } - } - /* dhcp-host has strange backwards-compat needs. */ - else if (strstr(arg, "net:") == arg || strstr(arg, "set:") == arg) - { - struct dhcp_netid *newtag = opt_malloc(sizeof(struct dhcp_netid)); - struct dhcp_netid_list *newlist = opt_malloc(sizeof(struct dhcp_netid_list)); - newtag->net = opt_malloc(strlen(arg + 4) + 1); - newlist->next = new->netid; - new->netid = newlist; - newlist->list = newtag; - strcpy(newtag->net, arg+4); - unhide_metas(newtag->net); - } - else if (strstr(arg, "tag:") == arg) - ret_err(_("cannot match tags in --dhcp-host")); + new->filter = NULL; + new->clid = NULL; #ifdef HAVE_DHCP6 - else if (arg[0] == '[' && arg[strlen(arg)-1] == ']') - { - arg[strlen(arg)-1] = 0; - arg++; - - if (!inet_pton(AF_INET6, arg, &new->addr6)) - ret_err(_("bad IPv6 address")); + new->addr6 = NULL; +#endif - for (i= 0; i < 8; i++) - if (new->addr6.s6_addr[i] != 0) - break; + while (arg) + { + comma = split(arg); + if (strchr(arg, ':')) /* ethernet address, netid or binary CLID */ + { + if ((arg[0] == 'i' || arg[0] == 'I') && + (arg[1] == 'd' || arg[1] == 'D') && + arg[2] == ':') + { + if (arg[3] == '*') + new->flags |= CONFIG_NOCLID; + else + { + int len; + arg += 3; /* dump id: */ + if (strchr(arg, ':')) + len = parse_hex(arg, (unsigned char *)arg, -1, NULL, NULL); + else + { + unhide_metas(arg); + len = (int) strlen(arg); + } + + if (len == -1) + { + dhcp_config_free(new); + ret_err(_("bad hex constant")); + } + else if ((new->clid = opt_malloc(len))) + { + new->flags |= CONFIG_CLID; + new->clid_len = len; + memcpy(new->clid, arg, len); + } + } + } + /* dhcp-host has strange backwards-compat needs. */ + else if (strstr(arg, "net:") == arg || strstr(arg, "set:") == arg) + { + struct dhcp_netid_list *newlist = opt_malloc(sizeof(struct dhcp_netid_list)); + newlist->next = new->netid; + new->netid = newlist; + newlist->list = dhcp_netid_create(arg+4, NULL); + } + else if (strstr(arg, "tag:") == arg) + new->filter = dhcp_netid_create(arg+4, new->filter); + +#ifdef HAVE_DHCP6 + else if (arg[0] == '[' && arg[strlen(arg)-1] == ']') + { + char *pref; + struct in6_addr in6; + struct addrlist *new_addr; + + arg[strlen(arg)-1] = 0; + arg++; + pref = split_chr(arg, '/'); + + if (!inet_pton(AF_INET6, arg, &in6)) + { + dhcp_config_free(new); + ret_err(_("bad IPv6 address")); + } - /* set WILDCARD if network part all zeros */ - if (i == 8) - new->flags |= CONFIG_WILDCARD; + new_addr = opt_malloc(sizeof(struct addrlist)); + new_addr->next = new->addr6; + new_addr->flags = 0; + new_addr->addr.addr6 = in6; + new->addr6 = new_addr; + + if (pref) + { + u64 addrpart = addr6part(&in6); + + if (!atoi_check(pref, &new_addr->prefixlen) || + new_addr->prefixlen > 128 || + ((((u64)1<<(128-new_addr->prefixlen))-1) & addrpart) != 0) + { + dhcp_config_free(new); + ret_err(_("bad IPv6 prefix")); + } + + new_addr->flags |= ADDRLIST_PREFIX; + } - new->flags |= CONFIG_ADDR6; - } + for (i= 0; i < 8; i++) + if (in6.s6_addr[i] != 0) + break; + + /* set WILDCARD if network part all zeros */ + if (i == 8) + new_addr->flags |= ADDRLIST_WILDCARD; + + new->flags |= CONFIG_ADDR6; + } #endif - else - { - struct hwaddr_config *newhw = opt_malloc(sizeof(struct hwaddr_config)); - if ((newhw->hwaddr_len = parse_hex(a[j], newhw->hwaddr, DHCP_CHADDR_MAX, - &newhw->wildcard_mask, &newhw->hwaddr_type)) == -1) - ret_err(_("bad hex constant")); - else - { - - newhw->next = new->hwaddr; - new->hwaddr = newhw; - } - } - } - else if (strchr(a[j], '.') && (inet_pton(AF_INET, a[j], &in) > 0)) - { - struct dhcp_config *configs; - - new->addr = in; - new->flags |= CONFIG_ADDR; - - /* If the same IP appears in more than one host config, then DISCOVER - for one of the hosts will get the address, but REQUEST will be NAKed, - since the address is reserved by the other one -> protocol loop. */ - for (configs = daemon->dhcp_conf; configs; configs = configs->next) - if ((configs->flags & CONFIG_ADDR) && configs->addr.s_addr == in.s_addr) + else { - sprintf(errstr, _("duplicate dhcp-host IP address %s"), inet_ntoa(in)); - return 0; - } - } - else - { - char *cp, *lastp = NULL, last = 0; - int fac = 1, isdig = 0; - - if (strlen(a[j]) > 1) - { - lastp = a[j] + strlen(a[j]) - 1; - last = *lastp; - switch (last) + struct hwaddr_config *newhw = opt_malloc(sizeof(struct hwaddr_config)); + if ((newhw->hwaddr_len = parse_hex(arg, newhw->hwaddr, DHCP_CHADDR_MAX, + &newhw->wildcard_mask, &newhw->hwaddr_type)) == -1) + { + free(newhw); + dhcp_config_free(new); + ret_err(_("bad hex constant")); + } + else + { + newhw->next = new->hwaddr; + new->hwaddr = newhw; + } + } + } + else if (strchr(arg, '.') && (inet_pton(AF_INET, arg, &in) > 0)) + { + struct dhcp_config *configs; + + new->addr = in; + new->flags |= CONFIG_ADDR; + + /* If the same IP appears in more than one host config, then DISCOVER + for one of the hosts will get the address, but REQUEST will be NAKed, + since the address is reserved by the other one -> protocol loop. */ + for (configs = daemon->dhcp_conf; configs; configs = configs->next) + if ((configs->flags & CONFIG_ADDR) && configs->addr.s_addr == in.s_addr) { - case 'w': - case 'W': - fac *= 7; - /* fall through */ - case 'd': - case 'D': - fac *= 24; - /* fall through */ - case 'h': - case 'H': - fac *= 60; - /* fall through */ - case 'm': - case 'M': - fac *= 60; - /* fall through */ - case 's': - case 'S': - *lastp = 0; - } - } - - for (cp = a[j]; *cp; cp++) - if (isdigit((unsigned char)*cp)) - isdig = 1; - else if (*cp != ' ') - break; + sprintf(errstr, _("duplicate dhcp-host IP address %s"), inet_ntoa(in)); + return 0; + } + } + else + { + char *cp, *lastp = NULL, last = 0; + int fac = 1, isdig = 0; + + if (strlen(arg) > 1) + { + lastp = arg + strlen(arg) - 1; + last = *lastp; + switch (last) + { + case 'w': + case 'W': + fac *= 7; + /* fall through */ + case 'd': + case 'D': + fac *= 24; + /* fall through */ + case 'h': + case 'H': + fac *= 60; + /* fall through */ + case 'm': + case 'M': + fac *= 60; + /* fall through */ + case 's': + case 'S': + *lastp = 0; + } + } + + for (cp = arg; *cp; cp++) + if (isdigit((unsigned char)*cp)) + isdig = 1; + else if (*cp != ' ') + break; + + if (*cp) + { + if (lastp) + *lastp = last; + if (strcmp(arg, "infinite") == 0) + { + new->lease_time = 0xffffffff; + new->flags |= CONFIG_TIME; + } + else if (strcmp(arg, "ignore") == 0) + new->flags |= CONFIG_DISABLE; + else + { + if (!(new->hostname = canonicalise_opt(arg)) || + !legal_hostname(new->hostname)) + { + dhcp_config_free(new); + ret_err(_("bad DHCP host name")); + } + + new->flags |= CONFIG_NAME; + new->domain = strip_hostname(new->hostname); + } + } + else if (isdig) + { + new->lease_time = atoi(arg) * fac; + /* Leases of a minute or less confuse + some clients, notably Apple's */ + if (new->lease_time < 120) + new->lease_time = 120; + new->flags |= CONFIG_TIME; + } + } + + arg = comma; + } - if (*cp) - { - if (lastp) - *lastp = last; - if (strcmp(a[j], "infinite") == 0) - { - new->lease_time = 0xffffffff; - new->flags |= CONFIG_TIME; - } - else if (strcmp(a[j], "ignore") == 0) - new->flags |= CONFIG_DISABLE; - else - { - if (!(new->hostname = canonicalise_opt(a[j])) || - !legal_hostname(new->hostname)) - ret_err(_("bad DHCP host name")); - - new->flags |= CONFIG_NAME; - new->domain = strip_hostname(new->hostname); - } - } - else if (isdig) - { - new->lease_time = atoi(a[j]) * fac; - /* Leases of a minute or less confuse - some clients, notably Apple's */ - if (new->lease_time < 120) - new->lease_time = 120; - new->flags |= CONFIG_TIME; - } - } - daemon->dhcp_conf = new; break; } - + case LOPT_TAG_IF: /* --tag-if */ { struct tag_if *new = opt_malloc(sizeof(struct tag_if)); @@ -3234,10 +3503,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } else { - struct dhcp_netid *newtag = opt_malloc(sizeof(struct dhcp_netid)); - newtag->net = opt_malloc(len - 3); - strcpy(newtag->net, arg+4); - unhide_metas(newtag->net); + struct dhcp_netid *newtag = dhcp_netid_create(arg+4, NULL); if (strstr(arg, "set:") == arg) { @@ -3254,7 +3520,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma else { new->set = NULL; - free(newtag); + dhcp_netid_free(newtag); break; } } @@ -3263,7 +3529,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } if (!new->set) - ret_err(_("bad tag-if")); + { + dhcp_netid_free(new->tag); + dhcp_netid_list_free(new->set); + ret_err_free(_("bad tag-if"), new); + } break; } @@ -3277,22 +3547,41 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma option == LOPT_FORCE ? DHOPT_FORCE : (option == LOPT_MATCH ? DHOPT_MATCH : (option == LOPT_OPTS ? DHOPT_BANK : 0))); - - case 'M': /* --dhcp-boot */ + + case LOPT_NAME_MATCH: /* --dhcp-name-match */ { - struct dhcp_netid *id = NULL; - while (is_tag_prefix(arg)) + struct dhcp_match_name *new = opt_malloc(sizeof(struct dhcp_match_name)); + struct dhcp_netid *id = opt_malloc(sizeof(struct dhcp_netid)); + ssize_t len; + + if (!(comma = split(arg)) || (len = strlen(comma)) == 0) + ret_err(gen_err); + + new->wildcard = 0; + new->netid = id; + id->net = opt_string_alloc(set_prefix(arg)); + + if (comma[len-1] == '*') { - struct dhcp_netid *newid = opt_malloc(sizeof(struct dhcp_netid)); - newid->next = id; - id = newid; - comma = split(arg); - newid->net = opt_string_alloc(arg+4); - arg = comma; - }; + comma[len-1] = 0; + new->wildcard = 1; + } + new->name = opt_string_alloc(comma); + + new->next = daemon->dhcp_name_match; + daemon->dhcp_name_match = new; + + break; + } + + case 'M': /* --dhcp-boot */ + { + struct dhcp_netid *id = dhcp_tags(&arg); if (!arg) - ret_err(gen_err); + { + ret_err(gen_err); + } else { char *dhcp_file, *dhcp_sname = NULL, *tftp_sname = NULL; @@ -3338,19 +3627,12 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma case LOPT_REPLY_DELAY: /* --dhcp-reply-delay */ { - struct dhcp_netid *id = NULL; - while (is_tag_prefix(arg)) - { - struct dhcp_netid *newid = opt_malloc(sizeof(struct dhcp_netid)); - newid->next = id; - id = newid; - comma = split(arg); - newid->net = opt_string_alloc(arg+4); - arg = comma; - }; + struct dhcp_netid *id = dhcp_tags(&arg); if (!arg) - ret_err(gen_err); + { + ret_err(gen_err); + } else { struct delay_config *new; @@ -3375,19 +3657,13 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma new->netid = NULL; new->opt = 10; /* PXE_MENU_PROMPT */ - - while (is_tag_prefix(arg)) - { - struct dhcp_netid *nn = opt_malloc(sizeof (struct dhcp_netid)); - comma = split(arg); - nn->next = new->netid; - new->netid = nn; - nn->net = opt_string_alloc(arg+4); - arg = comma; - } + new->netid = dhcp_tags(&arg); if (!arg) - ret_err(gen_err); + { + dhcp_opt_free(new); + ret_err(gen_err); + } else { comma = split(arg); @@ -3423,17 +3699,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma new->netid = NULL; new->sname = NULL; new->server.s_addr = 0; + new->netid = dhcp_tags(&arg); - while (is_tag_prefix(arg)) - { - struct dhcp_netid *nn = opt_malloc(sizeof (struct dhcp_netid)); - comma = split(arg); - nn->next = new->netid; - new->netid = nn; - nn->net = opt_string_alloc(arg+4); - arg = comma; - } - if (arg && (comma = split(arg))) { for (i = 0; CSA[i]; i++) @@ -3510,7 +3777,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma unhide_metas(comma); new->hwaddr_len = parse_hex(comma, new->hwaddr, DHCP_CHADDR_MAX, &new->mask, &new->hwaddr_type); if (new->hwaddr_len == -1) - ret_err(gen_err); + { + free(new->netid.net); + ret_err_free(gen_err, new); + } else { new->next = daemon->dhcp_macs; @@ -3520,24 +3790,6 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } break; -#ifdef OPTION6_PREFIX_CLASS - case LOPT_PREF_CLSS: /* --dhcp-prefix-class */ - { - struct prefix_class *new = opt_malloc(sizeof(struct prefix_class)); - - if (!(comma = split(arg)) || - !atoi_check16(comma, &new->class)) - ret_err(gen_err); - - new->tag.net = opt_string_alloc(set_prefix(arg)); - new->next = daemon->prefix_classes; - daemon->prefix_classes = new; - - break; - } -#endif - - case 'U': /* --dhcp-vendorclass */ case 'j': /* --dhcp-userclass */ case LOPT_CIRCUIT: /* --dhcp-circuitid */ @@ -3549,7 +3801,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma struct dhcp_vendor *new = opt_malloc(sizeof(struct dhcp_vendor)); if (!(comma = split(arg))) - ret_err(gen_err); + ret_err_free(gen_err, new); new->netid.net = opt_string_alloc(set_prefix(arg)); /* check for hex string - must digits may include : must not have nothing else, @@ -3559,7 +3811,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma if ((comma = split(arg))) { if (option != 'U' || strstr(arg, "enterprise:") != arg) - ret_err(gen_err); + { + free(new->netid.net); + ret_err_free(gen_err, new); + } else new->enterprise = atoi(arg+11); } @@ -3661,14 +3916,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } while (arg) { - struct dhcp_netid *member = opt_malloc(sizeof(struct dhcp_netid)); comma = split(arg); - member->next = list; - list = member; - if (is_tag_prefix(arg)) - member->net = opt_string_alloc(arg+4); - else - member->net = opt_string_alloc(arg); + list = dhcp_netid_create(is_tag_prefix(arg) ? arg+4 :arg, list); arg = comma; } @@ -3682,7 +3931,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma struct addr_list *new = opt_malloc(sizeof(struct addr_list)); comma = split(arg); if (!(inet_pton(AF_INET, arg, &new->addr) > 0)) - ret_err(_("bad dhcp-proxy address")); + ret_err_free(_("bad dhcp-proxy address"), new); new->next = daemon->override_relays; daemon->override_relays = new; arg = comma; @@ -3708,7 +3957,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } #endif else - ret_err(_("Bad dhcp-relay")); + { + free(new->interface); + ret_err_free(_("Bad dhcp-relay"), new); + } break; } @@ -3748,8 +4000,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma arg = split(comma); if (!atoi_check(comma, &new->interval) || (arg && !atoi_check(arg, &new->lifetime))) + { err: - ret_err(_("bad RA-params")); + free(new->name); + ret_err_free(_("bad RA-params"), new); + } new->next = daemon->ra_interfaces; daemon->ra_interfaces = new; @@ -3790,17 +4045,15 @@ err: if ((k < 2) || (!(inet_pton(AF_INET, a[0], &new->in) > 0)) || - (!(inet_pton(AF_INET, a[1], &new->out) > 0))) - option = '?'; - - if (k == 3 && !inet_pton(AF_INET, a[2], &new->mask)) - option = '?'; + (!(inet_pton(AF_INET, a[1], &new->out) > 0)) || + (k == 3 && !inet_pton(AF_INET, a[2], &new->mask))) + ret_err(_("missing address in alias")); if (dash && (!(inet_pton(AF_INET, dash, &new->end) > 0) || !is_same_net(new->in, new->end, new->mask) || ntohl(new->in.s_addr) > ntohl(new->end.s_addr))) - ret_err(_("invalid alias range")); + ret_err_free(_("invalid alias range"), new); break; } @@ -3830,12 +4083,10 @@ err: { if (strcmp(arg, "4") == 0) new->family = AF_INET; -#ifdef HAVE_IPV6 else if (strcmp(arg, "6") == 0) new->family = AF_INET6; -#endif else - ret_err(gen_err); + ret_err_free(gen_err, new); } new->intr = opt_string_alloc(comma); break; @@ -3867,11 +4118,19 @@ err: alias = canonicalise_opt(arg); if (!alias || !target) - ret_err(_("bad CNAME")); + { + free(target); + free(alias); + ret_err(_("bad CNAME")); + } for (new = daemon->cnames; new; new = new->next) if (hostname_isequal(new->alias, alias)) - ret_err(_("duplicate CNAME")); + { + free(target); + free(alias); + ret_err(_("duplicate CNAME")); + } new = opt_malloc(sizeof(struct cname)); new->next = daemon->cnames; daemon->cnames = new; @@ -3894,7 +4153,11 @@ err: if (!(dom = canonicalise_opt(arg)) || (comma && !(target = canonicalise_opt(comma)))) - ret_err(_("bad PTR record")); + { + free(dom); + free(target); + ret_err(_("bad PTR record")); + } else { new = opt_malloc(sizeof(struct ptr_record)); @@ -3912,7 +4175,7 @@ err: int k = 0; struct naptr *new; int order, pref; - char *name, *replace = NULL; + char *name=NULL, *replace = NULL; if ((a[0] = arg)) for (k = 1; k < 7; k++) @@ -3925,7 +4188,11 @@ err: !atoi_check16(a[1], &order) || !atoi_check16(a[2], &pref) || (k == 7 && !(replace = canonicalise_opt(a[6])))) - ret_err(_("bad NAPTR record")); + { + free(name); + free(replace); + ret_err(_("bad NAPTR record")); + } else { new = opt_malloc(sizeof(struct naptr)); @@ -3947,26 +4214,30 @@ err: struct txt_record *new; size_t len = 0; char *data; - int val; + int class; comma = split(arg); data = split(comma); new = opt_malloc(sizeof(struct txt_record)); - new->next = daemon->rr; - daemon->rr = new; + new->name = NULL; - if (!atoi_check(comma, &val) || + if (!atoi_check(comma, &class) || !(new->name = canonicalise_opt(arg)) || (data && (len = parse_hex(data, (unsigned char *)data, -1, NULL, NULL)) == -1U)) - ret_err(_("bad RR record")); - - new->class = val; + { + free(new->name); + ret_err_free(_("bad RR record"), new); + } + new->len = 0; + new->class = class; + new->next = daemon->rr; + daemon->rr = new; if (data) { - new->txt=opt_malloc(len); + new->txt = opt_malloc(len); new->len = len; memcpy(new->txt, data, len); } @@ -3974,6 +4245,37 @@ err: break; } + case LOPT_CAA: /* --caa-record */ + { + struct txt_record *new; + char *tag, *value; + int flags; + + comma = split(arg); + tag = split(comma); + value = split(tag); + + new = opt_malloc(sizeof(struct txt_record)); + new->next = daemon->rr; + daemon->rr = new; + + if (!atoi_check(comma, &flags) || !tag || !value || !(new->name = canonicalise_opt(arg))) + ret_err(_("bad CAA record")); + + unhide_metas(tag); + unhide_metas(value); + + new->len = strlen(tag) + strlen(value) + 2; + new->txt = opt_malloc(new->len); + new->txt[0] = flags; + new->txt[1] = strlen(tag); + memcpy(&new->txt[2], tag, strlen(tag)); + memcpy(&new->txt[2 + strlen(tag)], value, strlen(value)); + new->class = T_CAA; + + break; + } + case 'Y': /* --txt-record */ { struct txt_record *new; @@ -3983,14 +4285,14 @@ err: comma = split(arg); new = opt_malloc(sizeof(struct txt_record)); - new->next = daemon->txt; - daemon->txt = new; new->class = C_IN; new->stat = 0; if (!(new->name = canonicalise_opt(arg))) - ret_err(_("bad TXT record")); + ret_err_free(_("bad TXT record"), new); + new->next = daemon->txt; + daemon->txt = new; len = comma ? strlen(comma) : 0; len += (len/255) + 1; /* room for extra counts */ new->txt = p = opt_malloc(len); @@ -4037,24 +4339,32 @@ err: arg = comma; comma = split(arg); if (!(target = canonicalise_opt(arg))) - ret_err(_("bad SRV target")); + ret_err_free(_("bad SRV target"), name); if (comma) { arg = comma; comma = split(arg); if (!atoi_check16(arg, &port)) - ret_err(_("invalid port number")); + { + free(name); + ret_err_free(_("invalid port number"), target); + } if (comma) { arg = comma; comma = split(arg); if (!atoi_check16(arg, &priority)) - ret_err(_("invalid priority")); - + { + free(name); + ret_err_free(_("invalid priority"), target); + } if (comma && !atoi_check16(comma, &weight)) - ret_err(_("invalid weight")); + { + free(name); + ret_err_free(_("invalid weight"), target); + } } } } @@ -4073,16 +4383,19 @@ err: case LOPT_HOST_REC: /* --host-record */ { - struct host_record *new = opt_malloc(sizeof(struct host_record)); - memset(new, 0, sizeof(struct host_record)); - new->ttl = -1; + struct host_record *new; if (!arg || !(comma = split(arg))) ret_err(_("Bad host-record")); + new = opt_malloc(sizeof(struct host_record)); + memset(new, 0, sizeof(struct host_record)); + new->ttl = -1; + new->flags = 0; + while (arg) { - struct all_addr addr; + union all_addr addr; char *dig; for (dig = arg; *dig != 0; dig++) @@ -4090,20 +4403,33 @@ err: break; if (*dig == 0) new->ttl = atoi(arg); - else if (inet_pton(AF_INET, arg, &addr)) - new->addr = addr.addr.addr4; -#ifdef HAVE_IPV6 - else if (inet_pton(AF_INET6, arg, &addr)) - new->addr6 = addr.addr.addr6; -#endif + else if (inet_pton(AF_INET, arg, &addr.addr4)) + { + new->addr = addr.addr4; + new->flags |= HR_4; + } + else if (inet_pton(AF_INET6, arg, &addr.addr6)) + { + new->addr6 = addr.addr6; + new->flags |= HR_6; + } else { int nomem; char *canon = canonicalise(arg, &nomem); - struct name_list *nl = opt_malloc(sizeof(struct name_list)); + struct name_list *nl; if (!canon) - ret_err(_("Bad name in host-record")); + { + struct name_list *tmp = new->names, *next; + for (tmp = new->names; tmp; tmp = next) + { + next = tmp->next; + free(tmp); + } + ret_err_free(_("Bad name in host-record"), new); + } + nl = opt_malloc(sizeof(struct name_list)); nl->name = canon; /* keep order, so that PTR record goes to first name */ nl->next = NULL; @@ -4132,17 +4458,28 @@ err: } #ifdef HAVE_DNSSEC - case LOPT_DNSSEC_STAMP: + case LOPT_DNSSEC_STAMP: /* --dnssec-timestamp */ daemon->timestamp_file = opt_string_alloc(arg); break; - case LOPT_TRUST_ANCHOR: + case LOPT_DNSSEC_CHECK: /* --dnssec-check-unsigned */ + if (arg) + { + if (strcmp(arg, "no") == 0) + set_option_bool(OPT_DNSSEC_IGN_NS); + else + ret_err(_("bad value for dnssec-check-unsigned")); + } + break; + + case LOPT_TRUST_ANCHOR: /* --trust-anchor */ { struct ds_config *new = opt_malloc(sizeof(struct ds_config)); char *cp, *cp1, *keyhex, *digest, *algo = NULL; int len; new->class = C_IN; + new->name = NULL; if ((comma = split(arg)) && (algo = split(comma))) { @@ -4167,7 +4504,7 @@ err: !atoi_check8(algo, &new->algo) || !atoi_check8(digest, &new->digest_type) || !(new->name = canonicalise_opt(arg))) - ret_err(_("bad trust anchor")); + ret_err_free(_("bad trust anchor"), new); /* Upper bound on length */ len = (2*strlen(keyhex))+1; @@ -4181,7 +4518,10 @@ err: else cp++; if ((new->digestlen = parse_hex(keyhex, (unsigned char *)new->digest, len, NULL, NULL)) == -1) - ret_err(_("bad HEX in trust anchor")); + { + free(new->name); + ret_err_free(_("bad HEX in trust anchor"), new); + } new->next = daemon->ds; daemon->ds = new; @@ -4321,7 +4661,7 @@ static void read_file(char *file, FILE *f, int hard_opt) if (errmess) strcpy(daemon->namebuff, errmess); - if (errmess || !one_opt(option, arg, buff, _("error"), 0, hard_opt == LOPT_REV_SERV)) + if (errmess || !one_opt(option, arg, daemon->namebuff, _("error"), 0, hard_opt == LOPT_REV_SERV)) { sprintf(daemon->namebuff + strlen(daemon->namebuff), _(" at line %d of %s"), lineno, file); if (hard_opt != 0) @@ -4650,8 +4990,8 @@ void read_opts(int argc, char **argv, char *compile_opts) size_t argbuf_size = MAXDNAME; char *argbuf = opt_malloc(argbuf_size); char *buff = opt_malloc(MAXDNAME); - int option, conffile_opt = '7', testmode = 0; - char *arg, *conffile = CONFFILE; + int option, testmode = 0; + char *arg, *conffile = NULL; opterr = 0; @@ -4725,8 +5065,7 @@ void read_opts(int argc, char **argv, char *compile_opts) argbuf_size = strlen(optarg) + 1; argbuf = opt_malloc(argbuf_size); } - strncpy(argbuf, optarg, argbuf_size); - argbuf[argbuf_size-1] = 0; + safe_strncpy(argbuf, optarg, argbuf_size); arg = argbuf; } else @@ -4761,8 +5100,14 @@ void read_opts(int argc, char **argv, char *compile_opts) } else if (option == 'C') { - conffile_opt = 0; /* file must exist */ - conffile = opt_string_alloc(arg); + if (!conffile) + conffile = opt_string_alloc(arg); + else + { + char *extra = opt_string_alloc(arg); + one_file(extra, 0); + free(extra); + } } else { @@ -4779,10 +5124,11 @@ void read_opts(int argc, char **argv, char *compile_opts) if (conffile) { - one_file(conffile, conffile_opt); - if (conffile_opt == 0) - free(conffile); + one_file(conffile, 0); + free(conffile); } + else + one_file(CONFFILE, '7'); /* port might not be known when the address is parsed - fill in here */ if (daemon->servers) @@ -4793,10 +5139,8 @@ void read_opts(int argc, char **argv, char *compile_opts) { if (tmp->source_addr.sa.sa_family == AF_INET) tmp->source_addr.in.sin_port = htons(daemon->query_port); -#ifdef HAVE_IPV6 else if (tmp->source_addr.sa.sa_family == AF_INET6) tmp->source_addr.in6.sin6_port = htons(daemon->query_port); -#endif } } @@ -4816,7 +5160,7 @@ void read_opts(int argc, char **argv, char *compile_opts) #define NOLOOP 1 #define TESTLOOP 2 - /* Fill in TTL for CNAMES noe we have local_ttl. + /* Fill in TTL for CNAMES now we have local_ttl. Also prepare to do loop detection. */ for (cn = daemon->cnames; cn; cn = cn->next) { @@ -4857,10 +5201,8 @@ void read_opts(int argc, char **argv, char *compile_opts) for(tmp = daemon->if_addrs; tmp; tmp = tmp->next) if (tmp->addr.sa.sa_family == AF_INET) tmp->addr.in.sin_port = htons(daemon->port); -#ifdef HAVE_IPV6 else if (tmp->addr.sa.sa_family == AF_INET6) tmp->addr.in6.sin6_port = htons(daemon->port); -#endif /* IPv6 */ } /* create default, if not specified */ diff --git a/src/outpacket.c b/src/outpacket.c index d20bd33..50513dc 100644 --- a/src/outpacket.c +++ b/src/outpacket.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 diff --git a/src/radv-protocol.h b/src/radv-protocol.h index 2ca9ec7..edc1532 100644 --- a/src/radv-protocol.h +++ b/src/radv-protocol.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -288,7 +288,10 @@ static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_ad context->netid.next = &context->netid; } - if (!iface_enumerate(AF_INET6, &parm, add_prefixes)) + /* If no link-local address then we can't advertise since source address of + advertisement must be link local address: RFC 4861 para 6.1.2. */ + if (!iface_enumerate(AF_INET6, &parm, add_prefixes) || + parm.link_pref_time == 0) return; /* Find smallest preferred time within address classes, @@ -412,7 +415,7 @@ static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_ad if (mtu == 0) { char *mtu_name = ra_param ? ra_param->mtu_name : NULL; - sprintf(daemon->namebuff, "/proc/sys/net/ipv6/conf/%s/mtu", mtu_name ? : iface_name); + sprintf(daemon->namebuff, "/proc/sys/net/ipv6/conf/%s/mtu", mtu_name ? mtu_name : iface_name); if ((f = fopen(daemon->namebuff, "r"))) { if (fgets(daemon->namebuff, MAXDNAME, f)) @@ -623,8 +626,11 @@ static int add_prefixes(struct in6_addr *local, int prefix, real_prefix = context->prefix; } - /* find floor time, don't reduce below 3 * RA interval. */ - if (time > context->lease_time) + /* find floor time, don't reduce below 3 * RA interval. + If the lease time has been left as default, don't + use that as a floor. */ + if ((context->flags & CONTEXT_SETLEASE) && + time > context->lease_time) { time = context->lease_time; if (time < ((unsigned int)(3 * param->adv_interval))) @@ -888,11 +894,21 @@ static int iface_search(struct in6_addr *local, int prefix, { struct search_param *param = vparam; struct dhcp_context *context; - + struct iname *tmp; + (void)scope; (void)preferred; (void)valid; - + + /* ignore interfaces we're not doing DHCP on. */ + if (!indextoname(daemon->icmp6fd, if_index, param->name) || + !iface_check(AF_LOCAL, NULL, param->name, NULL)) + return 1; + + for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) + if (tmp->name && wildcard_match(tmp->name, param->name)) + return 1; + for (context = daemon->dhcp6; context; context = context->next) if (!(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) && prefix <= context->prefix && @@ -904,17 +920,9 @@ static int iface_search(struct in6_addr *local, int prefix, /* found an interface that's overdue for RA determine new timeout value and arrange for RA to be sent unless interface is still doing DAD.*/ - if (!(flags & IFACE_TENTATIVE)) param->iface = if_index; - /* should never fail */ - if (!indextoname(daemon->icmp6fd, if_index, param->name)) - { - param->iface = 0; - return 0; - } - new_timeout(context, param->name, param->now); /* zero timers for other contexts on the same subnet, so they don't timeout diff --git a/src/rfc1035.c b/src/rfc1035.c index b078b59..fefe63d 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -143,7 +143,7 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, /* Max size of input string (for IPv6) is 75 chars.) */ #define MAXARPANAME 75 -int in_arpa_name_2_addr(char *namein, struct all_addr *addrp) +int in_arpa_name_2_addr(char *namein, union all_addr *addrp) { int j; char name[MAXARPANAME+1], *cp1; @@ -153,7 +153,7 @@ int in_arpa_name_2_addr(char *namein, struct all_addr *addrp) if (strlen(namein) > MAXARPANAME) return 0; - memset(addrp, 0, sizeof(struct all_addr)); + memset(addrp, 0, sizeof(union all_addr)); /* turn name into a series of asciiz strings */ /* j counts no. of labels */ @@ -198,7 +198,6 @@ int in_arpa_name_2_addr(char *namein, struct all_addr *addrp) return F_IPV4; } -#ifdef HAVE_IPV6 else if (hostname_isequal(penchunk, "ip6") && (hostname_isequal(lastchunk, "int") || hostname_isequal(lastchunk, "arpa"))) { @@ -235,7 +234,7 @@ int in_arpa_name_2_addr(char *namein, struct all_addr *addrp) if (*(cp1+1) || !isxdigit((unsigned char)*cp1)) return 0; - for (j = sizeof(struct all_addr)-1; j>0; j--) + for (j = sizeof(struct in6_addr)-1; j>0; j--) addr[j] = (addr[j] >> 4) | (addr[j-1] << 4); addr[0] = (addr[0] >> 4) | (strtol(cp1, NULL, 16) << 4); } @@ -243,7 +242,6 @@ int in_arpa_name_2_addr(char *namein, struct all_addr *addrp) return F_IPV6; } } -#endif return 0; } @@ -426,7 +424,6 @@ int private_net(struct in_addr addr, int ban_localhost) ((ip_addr & 0xFFFFFFFF) == 0xFFFFFFFF) /* 255.255.255.255/32 (broadcast)*/ ; } -#ifdef HAVE_IPV6 static int private_net6(struct in6_addr *a) { return @@ -436,8 +433,6 @@ static int private_net6(struct in6_addr *a) ((unsigned char *)a)[0] == 0xfd || /* RFC 6303 4.4 */ ((u32 *)a)[0] == htonl(0x20010db8); /* RFC 6303 4.6 */ } -#endif - static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header *header, size_t qlen, char *name, int *doctored) { @@ -590,7 +585,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t unsigned char *p, *p1, *endrr, *namep; int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0; unsigned long ttl = 0; - struct all_addr addr; + union all_addr addr; #ifdef HAVE_IPSET char **ipsets_cur; #else @@ -613,7 +608,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t #ifdef HAVE_DNSSEC if (option_bool(OPT_DNSSEC_VALID)) for (i = 0; i < ntohs(header->ancount); i++) - if (daemon->rr_status[i]) + if (daemon->rr_status[i] != 0) return 0; #endif } @@ -686,13 +681,16 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t if (!extract_name(header, qlen, &p1, name, 1, 0)) return 0; #ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[j]) + if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[j] != 0) { /* validated RR anywhere in CNAME chain, don't cache. */ if (cname_short || aqtype == T_CNAME) return 0; secflag = F_DNSSECOK; + /* limit TTL based on signature. */ + if (daemon->rr_status[j] < cttl) + cttl = daemon->rr_status[j]; } #endif @@ -706,7 +704,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t goto cname_loop; } - cache_insert(name, &addr, now, cttl, name_encoding | secflag | F_REVERSE); + cache_insert(name, &addr, C_IN, now, cttl, name_encoding | secflag | F_REVERSE); found = 1; } @@ -724,28 +722,28 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t ttl = find_soa(header, qlen, NULL, doctored); } if (ttl) - cache_insert(NULL, &addr, now, ttl, name_encoding | F_REVERSE | F_NEG | flags | (secure ? F_DNSSECOK : 0)); + cache_insert(NULL, &addr, C_IN, now, ttl, name_encoding | F_REVERSE | F_NEG | flags | (secure ? F_DNSSECOK : 0)); } } else { /* everything other than PTR */ struct crec *newc; - int addrlen; + int addrlen = 0; if (qtype == T_A) { addrlen = INADDRSZ; flags |= F_IPV4; } -#ifdef HAVE_IPV6 else if (qtype == T_AAAA) { addrlen = IN6ADDRSZ; flags |= F_IPV6; } -#endif - else + else if (qtype == T_SRV) + flags |= F_SRV; + else continue; cname_loop1: @@ -773,21 +771,27 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == qtype)) { #ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[j]) + if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[j] != 0) + { secflag = F_DNSSECOK; + + /* limit TTl based on sig. */ + if (daemon->rr_status[j] < attl) + attl = daemon->rr_status[j]; + } #endif if (aqtype == T_CNAME) { if (!cname_count--) return 0; /* looped CNAMES */ - newc = cache_insert(name, NULL, now, attl, F_CNAME | F_FORWARD | secflag); - if (newc) + + if ((newc = cache_insert(name, NULL, C_IN, now, attl, F_CNAME | F_FORWARD | secflag))) { newc->addr.cname.target.cache = NULL; - /* anything other than zero, to avoid being mistaken for CNAME to interface-name */ - newc->addr.cname.uid = 1; + newc->addr.cname.is_name_ptr = 0; if (cpp) { + next_uid(newc); cpp->addr.cname.target.cache = newc; cpp->addr.cname.uid = newc->uid; } @@ -797,53 +801,90 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t if (attl < cttl) cttl = attl; + namep = p1; if (!extract_name(header, qlen, &p1, name, 1, 0)) return 0; + goto cname_loop1; } else if (!(flags & F_NXDOMAIN)) { found = 1; - /* copy address into aligned storage */ - if (!CHECK_LEN(header, p1, qlen, addrlen)) - return 0; /* bad packet */ - memcpy(&addr, p1, addrlen); - - /* check for returned address in private space */ - if (check_rebind) + if (flags & F_SRV) { - if ((flags & F_IPV4) && - private_net(addr.addr.addr4, !option_bool(OPT_LOCAL_REBIND))) - return 1; - -#ifdef HAVE_IPV6 - if ((flags & F_IPV6) && - IN6_IS_ADDR_V4MAPPED(&addr.addr.addr6)) + unsigned char *tmp = namep; + + if (!CHECK_LEN(header, p1, qlen, 6)) + return 0; /* bad packet */ + GETSHORT(addr.srv.priority, p1); + GETSHORT(addr.srv.weight, p1); + GETSHORT(addr.srv.srvport, p1); + if (!extract_name(header, qlen, &p1, name, 1, 0)) + return 0; + addr.srv.targetlen = strlen(name) + 1; /* include terminating zero */ + if (!(addr.srv.target = blockdata_alloc(name, addr.srv.targetlen))) + return 0; + + /* we overwrote the original name, so get it back here. */ + if (!extract_name(header, qlen, &tmp, name, 1, 0)) + return 0; + } + else + { + /* copy address into aligned storage */ + if (!CHECK_LEN(header, p1, qlen, addrlen)) + return 0; /* bad packet */ + memcpy(&addr, p1, addrlen); + + /* check for returned address in private space */ + if (check_rebind) { - struct in_addr v4; - v4.s_addr = ((const uint32_t *) (&addr.addr.addr6))[3]; - if (private_net(v4, !option_bool(OPT_LOCAL_REBIND))) + if ((flags & F_IPV4) && + private_net(addr.addr4, !option_bool(OPT_LOCAL_REBIND))) return 1; + + /* Block IPv4-mapped IPv6 addresses in private IPv4 address space */ + if (flags & F_IPV6) + { + if (IN6_IS_ADDR_V4MAPPED(&addr.addr6)) + { + struct in_addr v4; + v4.s_addr = ((const uint32_t *) (&addr.addr6))[3]; + if (private_net(v4, !option_bool(OPT_LOCAL_REBIND))) + return 1; + } + + /* Check for link-local (LL) and site-local (ULA) IPv6 addresses */ + if (IN6_IS_ADDR_LINKLOCAL(&addr.addr6) || + IN6_IS_ADDR_SITELOCAL(&addr.addr6)) + return 1; + + /* Check for the IPv6 loopback address (::1) when + option rebind-localhost-ok is NOT set */ + if (!option_bool(OPT_LOCAL_REBIND) && + IN6_IS_ADDR_LOOPBACK(&addr.addr6)) + return 1; + } } -#endif - } - + #ifdef HAVE_IPSET - if (ipsets && (flags & (F_IPV4 | F_IPV6))) - { - ipsets_cur = ipsets; - while (*ipsets_cur) + if (ipsets && (flags & (F_IPV4 | F_IPV6))) { - log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, name, &addr, *ipsets_cur); - add_to_ipset(*ipsets_cur++, &addr, flags, 0); + ipsets_cur = ipsets; + while (*ipsets_cur) + { + log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, name, &addr, *ipsets_cur); + add_to_ipset(*ipsets_cur++, &addr, flags, 0); + } } - } #endif + } - newc = cache_insert(name, &addr, now, attl, flags | F_FORWARD | secflag); + newc = cache_insert(name, &addr, C_IN, now, attl, flags | F_FORWARD | secflag); if (newc && cpp) { + next_uid(newc); cpp->addr.cname.target.cache = newc; cpp->addr.cname.uid = newc->uid; } @@ -867,9 +908,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t pointing at this, inherit its TTL */ if (ttl || cpp) { - newc = cache_insert(name, NULL, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0)); + newc = cache_insert(name, NULL, C_IN, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0)); if (newc && cpp) { + next_uid(newc); cpp->addr.cname.target.cache = newc; cpp->addr.cname.uid = newc->uid; } @@ -922,23 +964,29 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, if (qtype == T_ANY) return F_IPV4 | F_IPV6; } + + /* F_DNSSECOK as agument to search_servers() inhibits forwarding + to servers for domains without a trust anchor. This make the + behaviour for DS and DNSKEY queries we forward the same + as for DS and DNSKEY queries we originate. */ + if (qtype == T_DS || qtype == T_DNSKEY) + return F_DNSSECOK; return F_QUERY; } - size_t setup_reply(struct dns_header *header, size_t qlen, - struct all_addr *addrp, unsigned int flags, unsigned long ttl) + union all_addr *addrp, unsigned int flags, unsigned long ttl) { unsigned char *p; - + if (!(p = skip_questions(header, qlen))) return 0; /* clear authoritative and truncated flags, set QR flag */ - header->hb3 = (header->hb3 & ~(HB3_AA | HB3_TC)) | HB3_QR; - /* set RA flag */ - header->hb4 |= HB4_RA; + header->hb3 = (header->hb3 & ~(HB3_AA | HB3_TC )) | HB3_QR; + /* clear AD flag, set RA flag */ + header->hb4 = (header->hb4 & ~HB4_AD) | HB4_RA; header->nscount = htons(0); header->arcount = htons(0); @@ -948,66 +996,73 @@ size_t setup_reply(struct dns_header *header, size_t qlen, else if (flags == F_NXDOMAIN) SET_RCODE(header, NXDOMAIN); else if (flags == F_SERVFAIL) - SET_RCODE(header, SERVFAIL); - else if (flags == F_IPV4) - { /* we know the address */ - SET_RCODE(header, NOERROR); - header->ancount = htons(1); - header->hb3 |= HB3_AA; - add_resource_record(header, NULL, NULL, sizeof(struct dns_header), &p, ttl, NULL, T_A, C_IN, "4", addrp); + { + union all_addr a; + a.log.rcode = SERVFAIL; + log_query(F_CONFIG | F_RCODE, "error", &a, NULL); + SET_RCODE(header, SERVFAIL); } -#ifdef HAVE_IPV6 - else if (flags == F_IPV6) + else if (flags & ( F_IPV4 | F_IPV6)) { - SET_RCODE(header, NOERROR); - header->ancount = htons(1); - header->hb3 |= HB3_AA; - add_resource_record(header, NULL, NULL, sizeof(struct dns_header), &p, ttl, NULL, T_AAAA, C_IN, "6", addrp); + if (flags & F_IPV4) + { /* we know the address */ + SET_RCODE(header, NOERROR); + header->ancount = htons(1); + header->hb3 |= HB3_AA; + add_resource_record(header, NULL, NULL, sizeof(struct dns_header), &p, ttl, NULL, T_A, C_IN, "4", addrp); + } + + if (flags & F_IPV6) + { + SET_RCODE(header, NOERROR); + header->ancount = htons(ntohs(header->ancount) + 1); + header->hb3 |= HB3_AA; + add_resource_record(header, NULL, NULL, sizeof(struct dns_header), &p, ttl, NULL, T_AAAA, C_IN, "6", addrp); + } } -#endif else /* nowhere to forward to */ - SET_RCODE(header, REFUSED); - + { + union all_addr a; + a.log.rcode = REFUSED; + log_query(F_CONFIG | F_RCODE, "error", &a, NULL); + SET_RCODE(header, REFUSED); + } + return p - (unsigned char *)header; } /* check if name matches local names ie from /etc/hosts or DHCP or local mx names. */ int check_for_local_domain(char *name, time_t now) { - struct crec *crecp; struct mx_srv_record *mx; struct txt_record *txt; struct interface_name *intr; struct ptr_record *ptr; struct naptr *naptr; - /* Note: the call to cache_find_by_name is intended to find any record which matches - ie A, AAAA, CNAME. */ - - if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME | F_NO_RR)) && - (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) - return 1; - for (naptr = daemon->naptr; naptr; naptr = naptr->next) - if (hostname_isequal(name, naptr->name)) + if (hostname_issubdomain(name, naptr->name)) return 1; for (mx = daemon->mxnames; mx; mx = mx->next) - if (hostname_isequal(name, mx->name)) + if (hostname_issubdomain(name, mx->name)) return 1; for (txt = daemon->txt; txt; txt = txt->next) - if (hostname_isequal(name, txt->name)) + if (hostname_issubdomain(name, txt->name)) return 1; for (intr = daemon->int_names; intr; intr = intr->next) - if (hostname_isequal(name, intr->name)) + if (hostname_issubdomain(name, intr->name)) return 1; for (ptr = daemon->ptr; ptr; ptr = ptr->next) - if (hostname_isequal(name, ptr->name)) + if (hostname_issubdomain(name, ptr->name)) return 1; - + + if (cache_find_non_terminal(name, now)) + return 1; + return 0; } @@ -1047,7 +1102,7 @@ int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, /* Found a bogus address. Insert that info here, since there no SOA record to get the ttl from in the normal processing */ cache_start_insert(); - cache_insert(name, NULL, now, ttl, F_IPV4 | F_FORWARD | F_NEG | F_NXDOMAIN); + cache_insert(name, NULL, C_IN, now, ttl, F_IPV4 | F_FORWARD | F_NEG | F_NXDOMAIN); cache_end_insert(); return 1; @@ -1153,14 +1208,12 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int for (; *format; format++) switch (*format) { -#ifdef HAVE_IPV6 case '6': CHECK_LIMIT(IN6ADDRSZ); sval = va_arg(ap, char *); memcpy(p, sval, IN6ADDRSZ); p += IN6ADDRSZ; break; -#endif case '4': CHECK_LIMIT(INADDRSZ); @@ -1262,7 +1315,11 @@ static unsigned long crec_ttl(struct crec *crecp, time_t now) else return daemon->max_ttl; } - + +static int cache_validated(const struct crec *crecp) +{ + return (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK)); +} /* return zero if we can't answer from cache, or packet size if we can */ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, @@ -1272,26 +1329,24 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, char *name = daemon->namebuff; unsigned char *p, *ansp; unsigned int qtype, qclass; - struct all_addr addr; + union all_addr addr; int nameoffset; unsigned short flag; int q, ans, anscount = 0, addncount = 0; int dryrun = 0; struct crec *crecp; - int nxdomain = 0, auth = 1, trunc = 0, sec_data = 1; + int nxdomain = 0, notimp = 0, auth = 1, trunc = 0, sec_data = 1; struct mx_srv_record *rec; size_t len; + int rd_bit = (header->hb3 & HB3_RD); + /* never answer queries with RD unset, to avoid cache snooping. */ if (ntohs(header->ancount) != 0 || ntohs(header->nscount) != 0 || - ntohs(header->qdcount) == 0 || + ntohs(header->qdcount) == 0 || OPCODE(header) != QUERY ) return 0; - /* always servfail queries with RD unset, to avoid cache snooping. */ - if (!(header->hb3 & HB3_RD)) - return setup_reply(header, qlen, NULL, F_SERVFAIL, 0); - /* Don't return AD set if checking disabled. */ if (header->hb4 & HB4_CD) sec_data = 0; @@ -1315,6 +1370,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, for (q = ntohs(header->qdcount); q != 0; q--) { + int count = 255; /* catch loops */ + /* save pointer to name for copying into answers */ nameoffset = p - (unsigned char *)header; @@ -1326,7 +1383,35 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, GETSHORT(qclass, p); ans = 0; /* have we answered this question */ - + + while (--count != 0 && (crecp = cache_find_by_name(NULL, name, now, F_CNAME))) + { + char *cname_target = cache_get_cname_target(crecp); + + /* If the client asked for DNSSEC don't use cached data. */ + if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || + (rd_bit && (!do_bit || cache_validated(crecp)))) + { + if (crecp->flags & F_CONFIG || qtype == T_CNAME) + ans = 1; + + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; + + if (!dryrun) + { + log_query(crecp->flags, name, NULL, record_source(crecp->uid)); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), &nameoffset, + T_CNAME, C_IN, "d", cname_target)) + anscount++; + } + + } + + strcpy(name, cname_target); + } + if (qtype == T_TXT || qtype == T_ANY) { struct txt_record *t; @@ -1334,12 +1419,11 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, { if (t->class == qclass && hostname_isequal(name, t->name)) { - ans = 1; + ans = 1, sec_data = 0; if (!dryrun) { unsigned long ttl = daemon->local_ttl; int ok = 1; - log_query(F_CONFIG | F_RRNAME, name, NULL, "<TXT>"); #ifndef NO_ID /* Dynamically generate stat record */ if (t->stat != 0) @@ -1349,12 +1433,33 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, ok = 0; } #endif - if (ok && add_resource_record(header, limit, &trunc, nameoffset, &ansp, - ttl, NULL, - T_TXT, t->class, "t", t->len, t->txt)) - anscount++; + if (ok) + { + log_query(F_CONFIG | F_RRNAME, name, NULL, "<TXT>"); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + ttl, NULL, + T_TXT, t->class, "t", t->len, t->txt)) + anscount++; + } + } + } + } + } + if (qclass == C_CHAOS) + { + /* don't forward *.bind and *.server chaos queries - always reply with NOTIMP */ + if (hostname_issubdomain("bind", name) || hostname_issubdomain("server", name)) + { + if (!ans) + { + notimp = 1, auth = 0; + if (!dryrun) + { + addr.log.rcode = NOTIMP; + log_query(F_CONFIG | F_RCODE, name, &addr, NULL); } + ans = 1, sec_data = 0; } } } @@ -1370,11 +1475,11 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, sec_data = 0; if (!dryrun) { - log_query(F_CONFIG | F_RRNAME, name, NULL, "<RR>"); + log_query(F_CONFIG | F_RRNAME, name, NULL, querystr(NULL, t->class)); if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, NULL, t->class, C_IN, "t", t->len, t->txt)) - anscount ++; + anscount++; } } @@ -1395,7 +1500,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, struct addrlist *addrlist; for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) - if (!(addrlist->flags & ADDRLIST_IPV6) && addr.addr.addr4.s_addr == addrlist->addr.addr.addr4.s_addr) + if (!(addrlist->flags & ADDRLIST_IPV6) && addr.addr4.s_addr == addrlist->addr.addr4.s_addr) break; if (addrlist) @@ -1404,14 +1509,13 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, 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->addr; addrlist; addrlist = addrlist->next) - if ((addrlist->flags & ADDRLIST_IPV6) && IN6_ARE_ADDR_EQUAL(&addr.addr.addr6, &addrlist->addr.addr.addr6)) + if ((addrlist->flags & ADDRLIST_IPV6) && IN6_ARE_ADDR_EQUAL(&addr.addr6, &addrlist->addr.addr6)) break; if (addrlist) @@ -1420,7 +1524,6 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, while (intr->next && strcmp(intr->intr, intr->next->intr) == 0) intr = intr->next; } -#endif if (intr) { @@ -1456,9 +1559,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, /* Don't use cache when DNSSEC data required, unless we know that the zone is unsigned, which implies that we're doing validation. */ - if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || - !do_bit || - (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK))) + if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || + (rd_bit && (!do_bit || cache_validated(crecp)) )) { do { @@ -1512,10 +1614,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } } else if (option_bool(OPT_BOGUSPRIV) && ( -#ifdef HAVE_IPV6 - (is_arpa == F_IPV6 && private_net6(&addr.addr.addr6)) || -#endif - (is_arpa == F_IPV4 && private_net(addr.addr.addr4, 1)))) + (is_arpa == F_IPV6 && private_net6(&addr.addr6)) || + (is_arpa == F_IPV4 && private_net(addr.addr4, 1)))) { struct server *serv; unsigned int namelen = strlen(name); @@ -1552,24 +1652,16 @@ 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; + unsigned short type = (flag == F_IPV6) ? T_AAAA : T_A; struct interface_name *intr; - if (flag == F_IPV6) -#ifdef HAVE_IPV6 - type = T_AAAA; -#else - break; -#endif - if (qtype != type && qtype != T_ANY) continue; /* interface name stuff */ - intname_restart: for (intr = daemon->int_names; intr; intr = intr->next) if (hostname_isequal(name, intr->name)) break; @@ -1587,31 +1679,26 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, for (intr = daemon->int_names; intr; intr = intr->next) if (hostname_isequal(name, intr->name)) for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) -#ifdef HAVE_IPV6 - if (!(addrlist->flags & ADDRLIST_IPV6)) -#endif - if (is_same_net(*((struct in_addr *)&addrlist->addr), local_addr, local_netmask)) - { - localise = 1; - break; - } + if (!(addrlist->flags & ADDRLIST_IPV6) && + is_same_net(addrlist->addr.addr4, local_addr, local_netmask)) + { + localise = 1; + break; + } for (intr = daemon->int_names; intr; intr = intr->next) if (hostname_isequal(name, intr->name)) { for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) -#ifdef HAVE_IPV6 if (((addrlist->flags & ADDRLIST_IPV6) ? T_AAAA : T_A) == type) -#endif { if (localise && - !is_same_net(*((struct in_addr *)&addrlist->addr), local_addr, local_netmask)) + !is_same_net(addrlist->addr.addr4, local_addr, local_netmask)) continue; -#ifdef HAVE_IPV6 if (addrlist->flags & ADDRLIST_REVONLY) continue; -#endif + ans = 1; sec_data = 0; if (!dryrun) @@ -1632,8 +1719,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, continue; } - cname_restart: - if ((crecp = cache_find_by_name(NULL, name, now, flag | F_CNAME | (dryrun ? F_NO_RR : 0)))) + if ((crecp = cache_find_by_name(NULL, name, now, flag | (dryrun ? F_NO_RR : 0)))) { int localise = 0; @@ -1644,17 +1730,18 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, struct crec *save = crecp; do { if ((crecp->flags & F_HOSTS) && - is_same_net(*((struct in_addr *)&crecp->addr), local_addr, local_netmask)) + is_same_net(crecp->addr.addr4, local_addr, local_netmask)) { localise = 1; break; } - } while ((crecp = cache_find_by_name(crecp, name, now, flag | F_CNAME))); + } while ((crecp = cache_find_by_name(crecp, name, now, flag))); crecp = save; } /* If the client asked for DNSSEC don't use cached data. */ - if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || !do_bit || !(crecp->flags & F_DNSSECOK)) + if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || + (rd_bit && (!do_bit || cache_validated(crecp)) )) do { /* don't answer wildcard queries with data not from /etc/hosts @@ -1665,27 +1752,6 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (!(crecp->flags & F_DNSSECOK)) sec_data = 0; - if (crecp->flags & F_CNAME) - { - char *cname_target = cache_get_cname_target(crecp); - - if (!dryrun) - { - log_query(crecp->flags, name, NULL, record_source(crecp->uid)); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - crec_ttl(crecp, now), &nameoffset, - T_CNAME, C_IN, "d", cname_target)) - anscount++; - } - - strcpy(name, cname_target); - /* check if target interface_name */ - if (crecp->addr.cname.uid == SRC_INTERFACE) - goto intname_restart; - else - goto cname_restart; - } - if (crecp->flags & F_NEG) { ans = 1; @@ -1701,7 +1767,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, filter here. */ if (localise && (crecp->flags & F_HOSTS) && - !is_same_net(*((struct in_addr *)&crecp->addr), local_addr, local_netmask)) + !is_same_net(crecp->addr.addr4, local_addr, local_netmask)) continue; if (!(crecp->flags & (F_HOSTS | F_DHCP))) @@ -1710,7 +1776,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, ans = 1; if (!dryrun) { - log_query(crecp->flags & ~F_REVERSE, name, &crecp->addr.addr, + log_query(crecp->flags & ~F_REVERSE, name, &crecp->addr, record_source(crecp->uid)); if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, @@ -1719,11 +1785,11 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, anscount++; } } - } while ((crecp = cache_find_by_name(crecp, name, now, flag | F_CNAME))); + } while ((crecp = cache_find_by_name(crecp, name, now, flag))); } else if (is_name_synthetic(flag, name, &addr)) { - ans = 1; + ans = 1, sec_data = 0; if (!dryrun) { log_query(F_FORWARD | F_CONFIG | flag, name, &addr, NULL); @@ -1734,52 +1800,33 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } } - if (qtype == T_CNAME || qtype == T_ANY) - { - if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME | (dryrun ? F_NO_RR : 0))) && - (qtype == T_CNAME || (crecp->flags & F_CONFIG)) && - ((crecp->flags & F_CONFIG) || !do_bit || !(crecp->flags & F_DNSSECOK))) - { - if (!(crecp->flags & F_DNSSECOK)) - sec_data = 0; - - ans = 1; - if (!dryrun) - { - log_query(crecp->flags, name, NULL, record_source(crecp->uid)); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - crec_ttl(crecp, now), &nameoffset, - T_CNAME, C_IN, "d", cache_get_cname_target(crecp))) - anscount++; - } - } - } - if (qtype == T_MX || qtype == T_ANY) { int found = 0; for (rec = daemon->mxnames; rec; rec = rec->next) if (!rec->issrv && hostname_isequal(name, rec->name)) { - ans = found = 1; - if (!dryrun) - { - int offset; - log_query(F_CONFIG | F_RRNAME, name, NULL, "<MX>"); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, - &offset, T_MX, C_IN, "sd", rec->weight, rec->target)) - { - anscount++; - if (rec->target) - rec->offset = offset; - } - } + ans = found = 1; + sec_data = 0; + if (!dryrun) + { + int offset; + log_query(F_CONFIG | F_RRNAME, name, NULL, "<MX>"); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, + &offset, T_MX, C_IN, "sd", rec->weight, rec->target)) + { + anscount++; + if (rec->target) + rec->offset = offset; + } + } } - if (!found && (option_bool(OPT_SELFMX) || option_bool(OPT_LOCALMX)) && + if (!found && (option_bool(OPT_SELFMX) || option_bool(OPT_LOCALMX)) && cache_find_by_name(NULL, name, now, F_HOSTS | F_DHCP | F_NO_RR)) { ans = 1; + sec_data = 0; if (!dryrun) { log_query(F_CONFIG | F_RRNAME, name, NULL, "<MX>"); @@ -1800,6 +1847,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (rec->issrv && hostname_isequal(name, rec->name)) { found = ans = 1; + sec_data = 0; if (!dryrun) { int offset; @@ -1832,10 +1880,45 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, *up = move; move->next = NULL; } - + + if (!found) + { + if ((crecp = cache_find_by_name(NULL, name, now, F_SRV | (dryrun ? F_NO_RR : 0))) && + rd_bit && (!do_bit || (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK)))) + { + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; + + auth = 0; + found = ans = 1; + + do { + if (crecp->flags & F_NEG) + { + if (crecp->flags & F_NXDOMAIN) + nxdomain = 1; + if (!dryrun) + log_query(crecp->flags, name, NULL, NULL); + } + else if (!dryrun) + { + char *target = blockdata_retrieve(crecp->addr.srv.target, crecp->addr.srv.targetlen, NULL); + log_query(crecp->flags, name, NULL, 0); + + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), NULL, T_SRV, C_IN, "sssd", + crecp->addr.srv.priority, crecp->addr.srv.weight, crecp->addr.srv.srvport, + target)) + anscount++; + } + } while ((crecp = cache_find_by_name(crecp, name, now, F_SRV))); + } + } + if (!found && option_bool(OPT_FILTER) && (qtype == T_SRV || (qtype == T_ANY && strchr(name, '_')))) { ans = 1; + sec_data = 0; if (!dryrun) log_query(F_CONFIG | F_NEG, name, NULL, NULL); } @@ -1848,6 +1931,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (hostname_isequal(name, na->name)) { ans = 1; + sec_data = 0; if (!dryrun) { log_query(F_CONFIG | F_RRNAME, name, NULL, "<NAPTR>"); @@ -1860,11 +1944,12 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } if (qtype == T_MAILB) - ans = 1, nxdomain = 1; + ans = 1, nxdomain = 1, sec_data = 0; if (qtype == T_SOA && option_bool(OPT_FILTER)) { - ans = 1; + ans = 1; + sec_data = 0; if (!dryrun) log_query(F_CONFIG | F_NEG, name, &addr, NULL); } @@ -1893,11 +1978,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, crecp = NULL; while ((crecp = cache_find_by_name(crecp, rec->target, now, F_IPV4 | F_IPV6))) { -#ifdef HAVE_IPV6 int type = crecp->flags & F_IPV4 ? T_A : T_AAAA; -#else - int type = T_A; -#endif + if (crecp->flags & F_NEG) continue; @@ -1924,6 +2006,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (nxdomain) SET_RCODE(header, NXDOMAIN); + else if (notimp) + SET_RCODE(header, NOTIMP); else SET_RCODE(header, NOERROR); /* no error */ header->ancount = htons(anscount); diff --git a/src/rfc2131.c b/src/rfc2131.c index c08a8ab..fc54aab 100644 --- a/src/rfc2131.c +++ b/src/rfc2131.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -75,7 +75,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, struct dhcp_vendor *vendor; struct dhcp_mac *mac; struct dhcp_netid_list *id_list; - int clid_len = 0, ignore = 0, do_classes = 0, selecting = 0, pxearch = -1; + int clid_len = 0, ignore = 0, do_classes = 0, rapid_commit = 0, selecting = 0, pxearch = -1; struct dhcp_packet *mess = (struct dhcp_packet *)daemon->dhcp_packet.iov_base; unsigned char *end = (unsigned char *)(mess + 1); unsigned char *real_end = (unsigned char *)(mess + 1); @@ -234,7 +234,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, subnet_addr = option_addr(opt); /* If there is no client identifier option, use the hardware address */ - if ((opt = option_find(mess, sz, OPTION_CLIENT_ID, 1))) + if (!option_bool(OPT_IGNORE_CLID) && (opt = option_find(mess, sz, OPTION_CLIENT_ID, 1))) { clid_len = option_len(opt); clid = option_ptr(opt, 0); @@ -274,8 +274,9 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if (mess->giaddr.s_addr || subnet_addr.s_addr || mess->ciaddr.s_addr) { struct dhcp_context *context_tmp, *context_new = NULL; + struct shared_network *share = NULL; struct in_addr addr; - int force = 0; + int force = 0, via_relay = 0; if (subnet_addr.s_addr) { @@ -286,6 +287,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, { addr = mess->giaddr; force = 1; + via_relay = 1; } else { @@ -302,42 +304,65 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, } if (!context_new) - for (context_tmp = daemon->dhcp; context_tmp; context_tmp = context_tmp->next) - { - struct in_addr netmask = context_tmp->netmask; + { + for (context_tmp = daemon->dhcp; context_tmp; context_tmp = context_tmp->next) + { + struct in_addr netmask = context_tmp->netmask; + + /* guess the netmask for relayed networks */ + if (!(context_tmp->flags & CONTEXT_NETMASK) && context_tmp->netmask.s_addr == 0) + { + if (IN_CLASSA(ntohl(context_tmp->start.s_addr)) && IN_CLASSA(ntohl(context_tmp->end.s_addr))) + netmask.s_addr = htonl(0xff000000); + else if (IN_CLASSB(ntohl(context_tmp->start.s_addr)) && IN_CLASSB(ntohl(context_tmp->end.s_addr))) + netmask.s_addr = htonl(0xffff0000); + else if (IN_CLASSC(ntohl(context_tmp->start.s_addr)) && IN_CLASSC(ntohl(context_tmp->end.s_addr))) + netmask.s_addr = htonl(0xffffff00); + } - /* guess the netmask for relayed networks */ - if (!(context_tmp->flags & CONTEXT_NETMASK) && context_tmp->netmask.s_addr == 0) - { - if (IN_CLASSA(ntohl(context_tmp->start.s_addr)) && IN_CLASSA(ntohl(context_tmp->end.s_addr))) - netmask.s_addr = htonl(0xff000000); - else if (IN_CLASSB(ntohl(context_tmp->start.s_addr)) && IN_CLASSB(ntohl(context_tmp->end.s_addr))) - netmask.s_addr = htonl(0xffff0000); - else if (IN_CLASSC(ntohl(context_tmp->start.s_addr)) && IN_CLASSC(ntohl(context_tmp->end.s_addr))) - netmask.s_addr = htonl(0xffffff00); - } - - /* This section fills in context mainly when a client which is on a remote (relayed) - network renews a lease without using the relay, after dnsmasq has restarted. */ - if (netmask.s_addr != 0 && - is_same_net(addr, context_tmp->start, netmask) && - is_same_net(addr, context_tmp->end, netmask)) - { - context_tmp->netmask = netmask; - if (context_tmp->local.s_addr == 0) - context_tmp->local = fallback; - if (context_tmp->router.s_addr == 0) - context_tmp->router = mess->giaddr; - - /* fill in missing broadcast addresses for relayed ranges */ - if (!(context_tmp->flags & CONTEXT_BRDCAST) && context_tmp->broadcast.s_addr == 0 ) - context_tmp->broadcast.s_addr = context_tmp->start.s_addr | ~context_tmp->netmask.s_addr; - - context_tmp->current = context_new; - context_new = context_tmp; - } - } - + /* check to see is a context is OK because of a shared address on + the relayed subnet. */ + if (via_relay) + for (share = daemon->shared_networks; share; share = share->next) + { +#ifdef HAVE_DHCP6 + if (share->shared_addr.s_addr == 0) + continue; +#endif + if (share->if_index != 0 || + share->match_addr.s_addr != mess->giaddr.s_addr) + continue; + + if (netmask.s_addr != 0 && + is_same_net(share->shared_addr, context_tmp->start, netmask) && + is_same_net(share->shared_addr, context_tmp->end, netmask)) + break; + } + + /* This section fills in context mainly when a client which is on a remote (relayed) + network renews a lease without using the relay, after dnsmasq has restarted. */ + if (share || + (netmask.s_addr != 0 && + is_same_net(addr, context_tmp->start, netmask) && + is_same_net(addr, context_tmp->end, netmask))) + { + context_tmp->netmask = netmask; + if (context_tmp->local.s_addr == 0) + context_tmp->local = fallback; + if (context_tmp->router.s_addr == 0 && !share) + context_tmp->router = mess->giaddr; + + /* fill in missing broadcast addresses for relayed ranges */ + if (!(context_tmp->flags & CONTEXT_BRDCAST) && context_tmp->broadcast.s_addr == 0 ) + context_tmp->broadcast.s_addr = context_tmp->start.s_addr | ~context_tmp->netmask.s_addr; + + context_tmp->current = context_new; + context_new = context_tmp; + } + + } + } + if (context_new || force) context = context_new; } @@ -388,7 +413,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, for (o2 = offset + 5; o2 < offset + len + 5; o2 += elen + 1) { elen = option_uint(opt, o2, 1); - if ((o2 + elen + 1 <= option_len(opt)) && + if ((o2 + elen + 1 <= (unsigned)option_len(opt)) && (match = match_bytes(o, option_ptr(opt, o2 + 1), elen))) break; } @@ -479,7 +504,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, mess->op = BOOTREPLY; config = find_config(daemon->dhcp_conf, context, clid, clid_len, - mess->chaddr, mess->hlen, mess->htype, NULL); + mess->chaddr, mess->hlen, mess->htype, NULL, run_tag_if(netid)); /* set "known" tag for known hosts */ if (config) @@ -489,7 +514,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, netid = &known_id; } else if (find_config(daemon->dhcp_conf, NULL, clid, clid_len, - mess->chaddr, mess->hlen, mess->htype, NULL)) + mess->chaddr, mess->hlen, mess->htype, NULL, run_tag_if(netid))) { known_id.net = "known-othernet"; known_id.next = netid; @@ -626,6 +651,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, } } + daemon->metrics[METRIC_BOOTP]++; log_packet("BOOTP", logaddr, mess->chaddr, mess->hlen, iface_name, NULL, message, mess->xid); return message ? 0 : dhcp_packet_size(mess, agent_id, real_end); @@ -699,8 +725,37 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, client_hostname = daemon->dhcp_buff; } - if (client_hostname && option_bool(OPT_LOG_OPTS)) - my_syslog(MS_DHCP | LOG_INFO, _("%u client provides name: %s"), ntohl(mess->xid), client_hostname); + if (client_hostname) + { + struct dhcp_match_name *m; + size_t nl = strlen(client_hostname); + + if (option_bool(OPT_LOG_OPTS)) + my_syslog(MS_DHCP | LOG_INFO, _("%u client provides name: %s"), ntohl(mess->xid), client_hostname); + for (m = daemon->dhcp_name_match; m; m = m->next) + { + size_t ml = strlen(m->name); + char save = 0; + + if (nl < ml) + continue; + if (nl > ml) + { + save = client_hostname[ml]; + client_hostname[ml] = 0; + } + + if (hostname_isequal(client_hostname, m->name) && + (save == 0 || m->wildcard)) + { + m->netid->next = netid; + netid = m->netid; + } + + if (save != 0) + client_hostname[ml] = save; + } + } if (have_config(config, CONFIG_NAME)) { @@ -718,6 +773,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if (strlen(client_hostname) != 0) { hostname = client_hostname; + if (!config) { /* Search again now we have a hostname. @@ -725,7 +781,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, to avoid impersonation by name. */ struct dhcp_config *new = find_config(daemon->dhcp_conf, context, NULL, 0, mess->chaddr, mess->hlen, - mess->htype, hostname); + mess->htype, hostname, run_tag_if(netid)); if (new && !have_config(new, CONFIG_CLID) && !new->hwaddr) { config = new; @@ -917,7 +973,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, mess->siaddr = a_record_from_hosts(boot->tftp_sname, now); if (boot->file) - strncpy((char *)mess->file, boot->file, sizeof(mess->file)-1); + safe_strncpy((char *)mess->file, boot->file, sizeof(mess->file)); } option_put(mess, end, OPTION_MESSAGE_TYPE, 1, @@ -928,6 +984,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if ((pxe && !workaround) || !redirect4011) do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0); + daemon->metrics[METRIC_PXE]++; log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", NULL, mess->xid); log_tags(tagif_netid, ntohl(mess->xid)); if (!ignore) @@ -962,6 +1019,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if (!(opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ))) return 0; + daemon->metrics[METRIC_DHCPDECLINE]++; log_packet("DHCPDECLINE", option_ptr(opt, 0), emac, emac_len, iface_name, NULL, daemon->dhcp_buff, mess->xid); if (lease && lease->addr.s_addr == option_addr(opt).s_addr) @@ -994,6 +1052,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, else message = _("unknown lease"); + daemon->metrics[METRIC_DHCPRELEASE]++; log_packet("DHCPRELEASE", &mess->ciaddr, emac, emac_len, iface_name, NULL, message, mess->xid); return 0; @@ -1060,6 +1119,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, message = _("no address available"); } + daemon->metrics[METRIC_DHCPDISCOVER]++; log_packet("DHCPDISCOVER", opt ? option_ptr(opt, 0) : NULL, emac, emac_len, iface_name, NULL, message, mess->xid); if (message || !(context = narrow_context(context, mess->yiaddr, tagif_netid))) @@ -1073,6 +1133,14 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, log_tags(tagif_netid, ntohl(mess->xid)); apply_delay(mess->xid, recvtime, tagif_netid); + + if (option_bool(OPT_RAPID_COMMIT) && option_find(mess, sz, OPTION_RAPID_COMMIT, 0)) + { + rapid_commit = 1; + goto rapid_commit; + } + + daemon->metrics[METRIC_DHCPOFFER]++; log_packet("DHCPOFFER" , &mess->yiaddr, emac, emac_len, iface_name, NULL, NULL, mess->xid); time = calc_time(context, config, option_find(mess, sz, OPTION_LEASE_TIME, 4)); @@ -1085,7 +1153,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz); return dhcp_packet_size(mess, agent_id, real_end); - + + case DHCPREQUEST: if (ignore || have_config(config, CONFIG_DISABLE)) return 0; @@ -1183,9 +1252,11 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, fuzz = rand16(); mess->yiaddr = mess->ciaddr; } - + + daemon->metrics[METRIC_DHCPREQUEST]++; log_packet("DHCPREQUEST", &mess->yiaddr, emac, emac_len, iface_name, NULL, NULL, mess->xid); - + + rapid_commit: if (!message) { struct dhcp_config *addr_config; @@ -1256,7 +1327,12 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if (message) { - log_packet("DHCPNAK", &mess->yiaddr, emac, emac_len, iface_name, NULL, message, mess->xid); + daemon->metrics[rapid_commit ? METRIC_NOANSWER : METRIC_DHCPNAK]++; + log_packet(rapid_commit ? "NOANSWER" : "DHCPNAK", &mess->yiaddr, emac, emac_len, iface_name, NULL, message, mess->xid); + + /* rapid commit case: lease allocate failed but don't send DHCPNAK */ + if (rapid_commit) + return 0; mess->yiaddr.s_addr = 0; clear_packet(mess, end); @@ -1413,13 +1489,16 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, else override = lease->override; + daemon->metrics[METRIC_DHCPACK]++; log_packet("DHCPACK", &mess->yiaddr, emac, emac_len, iface_name, hostname, NULL, mess->xid); - + clear_packet(mess, end); option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK); option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(server_id(context, override, fallback).s_addr)); option_put(mess, end, OPTION_LEASE_TIME, 4, time); - do_options(context, mess, end, req_options, hostname, get_domain(mess->yiaddr), + if (rapid_commit) + option_put(mess, end, OPTION_RAPID_COMMIT, 0, 0); + do_options(context, mess, end, req_options, hostname, get_domain(mess->yiaddr), netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz); } @@ -1429,6 +1508,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if (ignore || have_config(config, CONFIG_DISABLE)) message = _("ignored"); + daemon->metrics[METRIC_DHCPINFORM]++; log_packet("DHCPINFORM", &mess->ciaddr, emac, emac_len, iface_name, message, NULL, mess->xid); if (message || mess->ciaddr.s_addr == 0) @@ -1455,6 +1535,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, log_tags(tagif_netid, ntohl(mess->xid)); + daemon->metrics[METRIC_DHCPACK]++; log_packet("DHCPACK", &mess->ciaddr, emac, emac_len, iface_name, hostname, NULL, mess->xid); if (lease) @@ -1621,6 +1702,13 @@ static void log_packet(char *type, void *addr, unsigned char *ext_mac, daemon->namebuff, string ? string : "", err ? err : ""); + +#ifdef HAVE_UBUS + if (!strcmp(type, "DHCPACK")) + ubus_event_bcast("dhcp.ack", daemon->namebuff, addr ? inet_ntoa(a) : NULL, string ? string : NULL, interface); + else if (!strcmp(type, "DHCPRELEASE")) + ubus_event_bcast("dhcp.release", daemon->namebuff, addr ? inet_ntoa(a) : NULL, string ? string : NULL, interface); +#endif } static void log_options(unsigned char *start, u32 xid) @@ -2296,7 +2384,7 @@ static void do_options(struct dhcp_context *context, in_list(req_options, OPTION_SNAME)) option_put_string(mess, end, OPTION_SNAME, boot->sname, 1); else - strncpy((char *)mess->sname, boot->sname, sizeof(mess->sname)-1); + safe_strncpy((char *)mess->sname, boot->sname, sizeof(mess->sname)); } if (boot->file) @@ -2306,7 +2394,7 @@ static void do_options(struct dhcp_context *context, in_list(req_options, OPTION_FILENAME)) option_put_string(mess, end, OPTION_FILENAME, boot->file, 1); else - strncpy((char *)mess->file, boot->file, sizeof(mess->file)-1); + safe_strncpy((char *)mess->file, boot->file, sizeof(mess->file)); } if (boot->next_server.s_addr) @@ -2323,14 +2411,14 @@ static void do_options(struct dhcp_context *context, if ((!req_options || !in_list(req_options, OPTION_FILENAME)) && (opt = option_find2(OPTION_FILENAME)) && !(opt->flags & DHOPT_FORCE)) { - strncpy((char *)mess->file, (char *)opt->val, sizeof(mess->file)-1); + safe_strncpy((char *)mess->file, (char *)opt->val, sizeof(mess->file)); done_file = 1; } if ((!req_options || !in_list(req_options, OPTION_SNAME)) && (opt = option_find2(OPTION_SNAME)) && !(opt->flags & DHOPT_FORCE)) { - strncpy((char *)mess->sname, (char *)opt->val, sizeof(mess->sname)-1); + safe_strncpy((char *)mess->sname, (char *)opt->val, sizeof(mess->sname)); done_server = 1; } diff --git a/src/rfc3315.c b/src/rfc3315.c index 21fcd9b..b3f0a0a 100644 --- a/src/rfc3315.c +++ b/src/rfc3315.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -21,19 +21,16 @@ struct state { unsigned char *clid; - int clid_len, iaid, ia_type, interface, hostname_auth, lease_allocate; + int clid_len, ia_type, interface, hostname_auth, lease_allocate; char *client_hostname, *hostname, *domain, *send_domain; struct dhcp_context *context; struct in6_addr *link_address, *fallback, *ll_addr, *ula_addr; - unsigned int xid, fqdn_flags; + unsigned int xid, fqdn_flags, iaid; char *iface_name; void *packet_options, *end; struct dhcp_netid *tags, *context_tags; unsigned char mac[DHCP_CHADDR_MAX]; unsigned int mac_len, mac_type; -#ifdef OPTION6_PREFIX_CLASS - struct prefix_class *send_prefix_class; -#endif }; static int dhcp6_maybe_relay(struct state *state, void *inbuff, size_t sz, @@ -49,12 +46,11 @@ static void get_context_tag(struct state *state, struct dhcp_context *context); static int check_ia(struct state *state, void *opt, void **endp, void **ia_option); static int build_ia(struct state *state, int *t1cntr); static void end_ia(int t1cntr, unsigned int min_time, int do_fuzz); -#ifdef OPTION6_PREFIX_CLASS -static struct prefix_class *prefix_class_from_context(struct dhcp_context *context); -#endif static void mark_context_used(struct state *state, struct in6_addr *addr); static void mark_config_used(struct dhcp_context *context, struct in6_addr *addr); static int check_address(struct state *state, struct in6_addr *addr); +static int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr, struct state *state, time_t now); +static struct addrlist *config_implies(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr); static void add_address(struct state *state, struct dhcp_context *context, unsigned int lease_time, void *ia_option, unsigned int *min_time, struct in6_addr *addr, 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); @@ -134,21 +130,41 @@ static int dhcp6_maybe_relay(struct state *state, void *inbuff, size_t sz, else { struct dhcp_context *c; + struct shared_network *share = NULL; state->context = NULL; - + if (!IN6_IS_ADDR_LOOPBACK(state->link_address) && !IN6_IS_ADDR_LINKLOCAL(state->link_address) && !IN6_IS_ADDR_MULTICAST(state->link_address)) for (c = daemon->dhcp6; c; c = c->next) - if ((c->flags & CONTEXT_DHCP) && - !(c->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) && - is_same_net6(state->link_address, &c->start6, c->prefix) && - is_same_net6(state->link_address, &c->end6, c->prefix)) - { - c->preferred = c->valid = 0xffffffff; - c->current = state->context; - state->context = c; - } + { + for (share = daemon->shared_networks; share; share = share->next) + { + if (share->shared_addr.s_addr != 0) + continue; + + if (share->if_index != 0 || + !IN6_ARE_ADDR_EQUAL(state->link_address, &share->match_addr6)) + continue; + + if ((c->flags & CONTEXT_DHCP) && + !(c->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) && + is_same_net6(&share->shared_addr6, &c->start6, c->prefix) && + is_same_net6(&share->shared_addr6, &c->end6, c->prefix)) + break; + } + + if (share || + ((c->flags & CONTEXT_DHCP) && + !(c->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) && + is_same_net6(state->link_address, &c->start6, c->prefix) && + is_same_net6(state->link_address, &c->end6, c->prefix))) + { + c->preferred = c->valid = 0xffffffff; + c->current = state->context; + state->context = c; + } + } if (!state->context) { @@ -219,21 +235,25 @@ static int dhcp6_maybe_relay(struct state *state, void *inbuff, size_t sz, if (opt6_ptr(opt, 0) + opt6_len(opt) > end) return 0; - int o = new_opt6(opt6_type(opt)); - if (opt6_type(opt) == OPTION6_RELAY_MSG) + /* Don't copy MAC address into reply. */ + if (opt6_type(opt) != OPTION6_CLIENT_MAC) { - struct in6_addr align; - /* the packet data is unaligned, copy to aligned storage */ - memcpy(&align, inbuff + 2, IN6ADDRSZ); - state->link_address = &align; - /* zero is_unicast since that is now known to refer to the - relayed packet, not the original sent by the client */ - if (!dhcp6_maybe_relay(state, opt6_ptr(opt, 0), opt6_len(opt), client_addr, 0, now)) - return 0; + int o = new_opt6(opt6_type(opt)); + if (opt6_type(opt) == OPTION6_RELAY_MSG) + { + struct in6_addr align; + /* the packet data is unaligned, copy to aligned storage */ + memcpy(&align, inbuff + 2, IN6ADDRSZ); + state->link_address = &align; + /* zero is_unicast since that is now known to refer to the + relayed packet, not the original sent by the client */ + if (!dhcp6_maybe_relay(state, opt6_ptr(opt, 0), opt6_len(opt), client_addr, 0, now)) + return 0; + } + else + put_opt6(opt6_ptr(opt, 0), opt6_len(opt)); + end_opt6(o); } - else if (opt6_type(opt) != OPTION6_CLIENT_MAC) - put_opt6(opt6_ptr(opt, 0), opt6_len(opt)); - end_opt6(o); } return 1; @@ -252,10 +272,6 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ struct dhcp_context *context_tmp; struct dhcp_mac *mac_opt; unsigned int ignore = 0; -#ifdef OPTION6_PREFIX_CLASS - struct prefix_class *p; - int dump_all_prefix_classes = 0; -#endif state->packet_options = inbuff + 4; state->end = inbuff + sz; @@ -269,9 +285,6 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ state->hostname = NULL; state->client_hostname = NULL; state->fqdn_flags = 0x01; /* default to send if we receive no FQDN option */ -#ifdef OPTION6_PREFIX_CLASS - state->send_prefix_class = NULL; -#endif /* set tag with name == interface */ iface_id.net = state->iface_name; @@ -477,39 +490,66 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ if (legal_hostname(daemon->dhcp_buff)) { + struct dhcp_match_name *m; + size_t nl = strlen(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"), state->xid, state->client_hostname); + my_syslog(MS_DHCP | LOG_INFO, _("%u client provides name: %s"), state->xid, state->client_hostname); + + for (m = daemon->dhcp_name_match; m; m = m->next) + { + size_t ml = strlen(m->name); + char save = 0; + + if (nl < ml) + continue; + if (nl > ml) + { + save = state->client_hostname[ml]; + state->client_hostname[ml] = 0; + } + + if (hostname_isequal(state->client_hostname, m->name) && + (save == 0 || m->wildcard)) + { + m->netid->next = state->tags; + state->tags = m->netid; + } + + if (save != 0) + state->client_hostname[ml] = save; + } } } } - if (state->clid) + if (state->clid && + (config = find_config(daemon->dhcp_conf, state->context, state->clid, state->clid_len, + state->mac, state->mac_len, state->mac_type, NULL, run_tag_if(state->tags))) && + have_config(config, CONFIG_NAME)) { - config = find_config(daemon->dhcp_conf, state->context, state->clid, state->clid_len, state->mac, state->mac_len, state->mac_type, NULL); + state->hostname = config->hostname; + state->domain = config->domain; + state->hostname_auth = 1; + } + else if (state->client_hostname) + { + state->domain = strip_hostname(state->client_hostname); - if (have_config(config, CONFIG_NAME)) - { - state->hostname = config->hostname; - state->domain = config->domain; - state->hostname_auth = 1; - } - else if (state->client_hostname) + if (strlen(state->client_hostname) != 0) { - state->domain = strip_hostname(state->client_hostname); + state->hostname = state->client_hostname; - if (strlen(state->client_hostname) != 0) + if (!config) { - 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_config(daemon->dhcp_conf, state->context, NULL, 0, NULL, 0, 0, state->hostname); - if (new && !have_config(new, CONFIG_CLID) && !new->hwaddr) - config = new; - } + /* 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_config(daemon->dhcp_conf, state->context, NULL, 0, NULL, 0, 0, state->hostname, run_tag_if(state->tags)); + if (new && !have_config(new, CONFIG_CLID) && !new->hwaddr) + config = new; } } } @@ -533,38 +573,14 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ ignore = 1; } else if (state->clid && - find_config(daemon->dhcp_conf, NULL, state->clid, state->clid_len, state->mac, state->mac_len, state->mac_type, NULL)) + find_config(daemon->dhcp_conf, NULL, state->clid, state->clid_len, + state->mac, state->mac_len, state->mac_type, NULL, run_tag_if(state->tags))) { known_id.net = "known-othernet"; known_id.next = state->tags; state->tags = &known_id; } -#ifdef OPTION6_PREFIX_CLASS - /* OPTION_PREFIX_CLASS in ORO, send addresses in all prefix classes */ - if (daemon->prefix_classes && (msg_type == DHCP6SOLICIT || msg_type == DHCP6REQUEST)) - { - void *oro; - - if ((oro = opt6_find(state->packet_options, state->end, OPTION6_ORO, 0))) - for (i = 0; i < opt6_len(oro) - 1; i += 2) - if (opt6_uint(oro, i, 2) == OPTION6_PREFIX_CLASS) - { - dump_all_prefix_classes = 1; - break; - } - - if (msg_type != DHCP6SOLICIT || dump_all_prefix_classes) - /* Add the tags associated with prefix classes so we can use the DHCP ranges. - Not done for SOLICIT as we add them one-at-time. */ - for (p = daemon->prefix_classes; p ; p = p->next) - { - p->tag.next = state->tags; - state->tags = &p->tag; - } - } -#endif - tagif = run_tag_if(state->tags); /* if all the netids in the ignore list are present, ignore this client */ @@ -639,9 +655,8 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ int plain_range = 1; u32 lease_time; struct dhcp_lease *ltmp; - struct in6_addr *req_addr; - struct in6_addr addr; - + struct in6_addr req_addr, addr; + if (!check_ia(state, opt, &ia_end, &ia_option)) continue; @@ -649,93 +664,36 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ for (c = state->context; c; c = c->current) c->flags &= ~CONTEXT_USED; -#ifdef OPTION6_PREFIX_CLASS - if (daemon->prefix_classes && state->ia_type == OPTION6_IA_NA) - { - void *prefix_opt; - int prefix_class; - - if (dump_all_prefix_classes) - /* OPTION_PREFIX_CLASS in ORO, send addresses in all prefix classes */ - plain_range = 0; - else - { - if ((prefix_opt = opt6_find(opt6_ptr(opt, 12), ia_end, OPTION6_PREFIX_CLASS, 2))) - { - - prefix_class = opt6_uint(prefix_opt, 0, 2); - - for (p = daemon->prefix_classes; p ; p = p->next) - if (p->class == prefix_class) - break; - - if (!p) - my_syslog(MS_DHCP | LOG_WARNING, _("unknown prefix-class %d"), prefix_class); - else - { - /* add tag to list, and exclude undecorated dhcp-ranges */ - p->tag.next = state->tags; - solicit_tags = run_tag_if(&p->tag); - plain_range = 0; - state->send_prefix_class = p; - } - } - else - { - /* client didn't ask for a prefix class, lets see if we can find one. */ - for (p = daemon->prefix_classes; p ; p = p->next) - { - p->tag.next = NULL; - if (match_netid(&p->tag, solicit_tags, 1)) - break; - } - - if (p) - { - plain_range = 0; - state->send_prefix_class = p; - } - } - - if (p && option_bool(OPT_LOG_OPTS)) - my_syslog(MS_DHCP | LOG_INFO, "%u prefix class %d tag:%s", state->xid, p->class, p->tag.net); - } - } -#endif - o = build_ia(state, &t1cntr); if (address_assigned) address_assigned = 2; for (ia_counter = 0; ia_option; ia_counter++, ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) { - req_addr = opt6_ptr(ia_option, 0); + /* worry about alignment here. */ + memcpy(&req_addr, opt6_ptr(ia_option, 0), IN6ADDRSZ); - if ((c = address6_valid(state->context, req_addr, solicit_tags, plain_range))) + if ((c = address6_valid(state->context, &req_addr, solicit_tags, plain_range))) { 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 (!(c->flags & CONTEXT_CONF_USED) && config_valid(config, c, &addr) && check_address(state, &addr)) + if (!(c->flags & CONTEXT_CONF_USED) && config_valid(config, c, &addr, state, now)) { - req_addr = &addr; + req_addr = addr; mark_config_used(c, &addr); if (have_config(config, CONFIG_TIME)) lease_time = config->lease_time; } - else if (!(c = address6_available(state->context, req_addr, solicit_tags, plain_range))) + else if (!(c = address6_available(state->context, &req_addr, solicit_tags, plain_range))) continue; /* not an address we're allowed */ - else if (!check_address(state, req_addr)) + else if (!check_address(state, &req_addr)) continue; /* address leased elsewhere */ /* add address to output packet */ -#ifdef OPTION6_PREFIX_CLASS - if (dump_all_prefix_classes && state->ia_type == OPTION6_IA_NA) - state->send_prefix_class = prefix_class_from_context(c); -#endif - add_address(state, c, lease_time, ia_option, &min_time, req_addr, now); - mark_context_used(state, req_addr); + add_address(state, c, lease_time, ia_option, &min_time, &req_addr, now); + mark_context_used(state, &req_addr); get_context_tag(state, c); address_assigned = 1; } @@ -745,19 +703,15 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ for (c = state->context; c; c = c->current) if (!(c->flags & CONTEXT_CONF_USED) && match_netid(c->filter, solicit_tags, plain_range) && - config_valid(config, c, &addr) && - check_address(state, &addr)) + config_valid(config, c, &addr, state, now)) { mark_config_used(state->context, &addr); if (have_config(config, CONFIG_TIME)) lease_time = config->lease_time; else lease_time = c->lease_time; + /* add address to output packet */ -#ifdef OPTION6_PREFIX_CLASS - if (dump_all_prefix_classes && state->ia_type == OPTION6_IA_NA) - state->send_prefix_class = prefix_class_from_context(c); -#endif add_address(state, c, lease_time, NULL, &min_time, &addr, now); mark_context_used(state, &addr); get_context_tag(state, c); @@ -768,15 +722,11 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ 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 = <mp->addr6; - if ((c = address6_available(state->context, req_addr, solicit_tags, plain_range))) + req_addr = ltmp->addr6; + if ((c = address6_available(state->context, &req_addr, solicit_tags, plain_range))) { -#ifdef OPTION6_PREFIX_CLASS - if (dump_all_prefix_classes && state->ia_type == OPTION6_IA_NA) - state->send_prefix_class = prefix_class_from_context(c); -#endif - add_address(state, c, c->lease_time, NULL, &min_time, req_addr, now); - mark_context_used(state, req_addr); + add_address(state, c, c->lease_time, NULL, &min_time, &req_addr, now); + mark_context_used(state, &req_addr); get_context_tag(state, c); address_assigned = 1; } @@ -786,10 +736,6 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ while ((c = address6_allocate(state->context, state->clid, state->clid_len, state->ia_type == OPTION6_IA_TA, state->iaid, ia_counter, solicit_tags, plain_range, &addr))) { -#ifdef OPTION6_PREFIX_CLASS - if (dump_all_prefix_classes && state->ia_type == OPTION6_IA_NA) - state->send_prefix_class = prefix_class_from_context(c); -#endif add_address(state, c, c->lease_time, NULL, &min_time, &addr, now); mark_context_used(state, &addr); get_context_tag(state, c); @@ -892,16 +838,18 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ 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); + struct in6_addr req_addr; struct dhcp_context *dynamic, *c; unsigned int lease_time; - struct in6_addr addr; int config_ok = 0; + + /* align. */ + memcpy(&req_addr, opt6_ptr(ia_option, 0), IN6ADDRSZ); - if ((c = address6_valid(state->context, req_addr, tagif, 1))) - config_ok = config_valid(config, c, &addr) && IN6_ARE_ADDR_EQUAL(&addr, req_addr); + if ((c = address6_valid(state->context, &req_addr, tagif, 1))) + config_ok = (config_implies(config, c, &req_addr) != NULL); - if ((dynamic = address6_available(state->context, req_addr, tagif, 1)) || c) + if ((dynamic = address6_available(state->context, &req_addr, tagif, 1)) || c) { if (!dynamic && !config_ok) { @@ -911,7 +859,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ put_opt6_string(_("address unavailable")); end_opt6(o1); } - else if (!check_address(state, req_addr)) + else if (!check_address(state, &req_addr)) { /* Address leased to another DUID/IAID */ o1 = new_opt6(OPTION6_STATUS_CODE); @@ -929,11 +877,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ if (config_ok && have_config(config, CONFIG_TIME)) lease_time = config->lease_time; -#ifdef OPTION6_PREFIX_CLASS - if (dump_all_prefix_classes && state->ia_type == OPTION6_IA_NA) - state->send_prefix_class = prefix_class_from_context(c); -#endif - add_address(state, dynamic, lease_time, ia_option, &min_time, req_addr, now); + add_address(state, dynamic, lease_time, ia_option, &min_time, &req_addr, now); get_context_tag(state, dynamic); address_assigned = 1; } @@ -996,15 +940,17 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ 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); + struct in6_addr req_addr; unsigned int preferred_time = opt6_uint(ia_option, 16, 4); unsigned int valid_time = opt6_uint(ia_option, 20, 4); char *message = NULL; struct dhcp_context *this_context; + + memcpy(&req_addr, opt6_ptr(ia_option, 0), IN6ADDRSZ); if (!(lease = lease6_find(state->clid, state->clid_len, state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, - state->iaid, req_addr))) + 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 @@ -1012,7 +958,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ save_counter(iacntr); t1cntr = 0; - log6_packet(state, "DHCPREPLY", req_addr, _("lease not found")); + log6_packet(state, "DHCPREPLY", &req_addr, _("lease not found")); o1 = new_opt6(OPTION6_STATUS_CODE); put_opt6_short(DHCP6NOBINDING); @@ -1024,15 +970,14 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ } - if ((this_context = address6_available(state->context, req_addr, tagif, 1)) || - (this_context = address6_valid(state->context, req_addr, tagif, 1))) + if ((this_context = address6_available(state->context, &req_addr, tagif, 1)) || + (this_context = address6_valid(state->context, &req_addr, tagif, 1))) { - struct in6_addr addr; unsigned int lease_time; get_context_tag(state, this_context); - if (config_valid(config, this_context, &addr) && IN6_ARE_ADDR_EQUAL(&addr, req_addr) && have_config(config, CONFIG_TIME)) + if (config_implies(config, this_context, &req_addr) && have_config(config, CONFIG_TIME)) lease_time = config->lease_time; else lease_time = this_context->lease_time; @@ -1045,7 +990,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ lease_set_hwaddr(lease, state->mac, state->clid, state->mac_len, state->mac_type, state->clid_len, now, 0); if (state->ia_type == OPTION6_IA_NA && state->hostname) { - char *addr_domain = get_domain6(req_addr); + char *addr_domain = get_domain6(&req_addr); if (!state->send_domain) state->send_domain = addr_domain; lease_set_hostname(lease, state->hostname, state->hostname_auth, addr_domain, state->domain); @@ -1063,12 +1008,12 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ } if (message && (message != state->hostname)) - log6_packet(state, "DHCPREPLY", req_addr, message); + log6_packet(state, "DHCPREPLY", &req_addr, message); else - log6_quiet(state, "DHCPREPLY", req_addr, message); + log6_quiet(state, "DHCPREPLY", &req_addr, message); o1 = new_opt6(OPTION6_IAADDR); - put_opt6(req_addr, sizeof(*req_addr)); + put_opt6(&req_addr, sizeof(req_addr)); put_opt6_long(preferred_time); put_opt6_long(valid_time); end_opt6(o1); @@ -1100,19 +1045,23 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ 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); + struct in6_addr req_addr; + + /* alignment */ + memcpy(&req_addr, opt6_ptr(ia_option, 0), IN6ADDRSZ); - if (!address6_valid(state->context, req_addr, tagif, 1)) + if (!address6_valid(state->context, &req_addr, tagif, 1)) { o1 = new_opt6(OPTION6_STATUS_CODE); put_opt6_short(DHCP6NOTONLINK); put_opt6_string(_("confirm failed")); end_opt6(o1); + log6_quiet(state, "DHCPREPLY", &req_addr, _("confirm failed")); return 1; } good_addr = 1; - log6_quiet(state, "DHCPREPLY", req_addr, state->hostname); + log6_quiet(state, "DHCPREPLY", &req_addr, state->hostname); } } @@ -1171,9 +1120,12 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) { struct dhcp_lease *lease; - + struct in6_addr addr; + + /* align */ + memcpy(&addr, opt6_ptr(ia_option, 0), IN6ADDRSZ); 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)))) + state->iaid, &addr))) lease_prune(lease, now); else { @@ -1190,7 +1142,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ } o1 = new_opt6(OPTION6_IAADDR); - put_opt6(opt6_ptr(ia_option, 0), IN6ADDRSZ); + put_opt6(&addr, IN6ADDRSZ); put_opt6_long(0); put_opt6_long(0); end_opt6(o1); @@ -1233,16 +1185,20 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ 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); + struct in6_addr addr; + struct addrlist *addr_list; + + /* align */ + memcpy(&addr, opt6_ptr(ia_option, 0), IN6ADDRSZ); - if (have_config(config, CONFIG_ADDR6) && IN6_ARE_ADDR_EQUAL(&config->addr6, addrp)) + if ((addr_list = config_implies(config, state->context, &addr))) { prettyprint_time(daemon->dhcp_buff3, DECLINE_BACKOFF); - inet_ntop(AF_INET6, addrp, daemon->addrbuff, ADDRSTRLEN); + inet_ntop(AF_INET6, &addr, daemon->addrbuff, ADDRSTRLEN); my_syslog(MS_DHCP | LOG_WARNING, _("disabling DHCP static address %s for %s"), daemon->addrbuff, daemon->dhcp_buff3); - config->flags |= CONFIG_DECLINED; - config->decline_time = now; + addr_list->flags |= ADDRLIST_DECLINED; + addr_list->decline_time = now; } else /* make sure this host gets a different address next time. */ @@ -1250,7 +1206,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ context_tmp->addr_epoch++; 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)))) + state->iaid, &addr))) lease_prune(lease, now); else { @@ -1267,7 +1223,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ } o1 = new_opt6(OPTION6_IAADDR); - put_opt6(opt6_ptr(ia_option, 0), IN6ADDRSZ); + put_opt6(&addr, IN6ADDRSZ); put_opt6_long(0); put_opt6_long(0); end_opt6(o1); @@ -1355,23 +1311,39 @@ static struct dhcp_netid *add_options(struct state *state, int do_refresh) for (a = (struct in6_addr *)opt_cfg->val, j = 0; j < opt_cfg->len; j+=IN6ADDRSZ, a++) { + struct in6_addr *p = NULL; + if (IN6_IS_ADDR_UNSPECIFIED(a)) { if (!add_local_addrs(state->context)) - put_opt6(state->fallback, IN6ADDRSZ); + p = state->fallback; } else if (IN6_IS_ADDR_ULA_ZERO(a)) { if (!IN6_IS_ADDR_UNSPECIFIED(state->ula_addr)) - put_opt6(state->ula_addr, IN6ADDRSZ); + p = state->ula_addr; } else if (IN6_IS_ADDR_LINK_LOCAL_ZERO(a)) { if (!IN6_IS_ADDR_UNSPECIFIED(state->ll_addr)) - put_opt6(state->ll_addr, IN6ADDRSZ); + p = state->ll_addr; } else - put_opt6(a, IN6ADDRSZ); + p = a; + + if (!p) + continue; + else if (opt_cfg->opt == OPTION6_NTP_SERVER) + { + if (IN6_IS_ADDR_MULTICAST(p)) + o1 = new_opt6(NTP_SUBOPTION_MC_ADDR); + else + o1 = new_opt6(NTP_SUBOPTION_SRV_ADDR); + put_opt6(p, IN6ADDRSZ); + end_opt6(o1); + } + else + put_opt6(p, IN6ADDRSZ); } end_opt6(o); @@ -1565,21 +1537,6 @@ static void get_context_tag(struct state *state, struct dhcp_context *context) } } -#ifdef OPTION6_PREFIX_CLASS -static struct prefix_class *prefix_class_from_context(struct dhcp_context *context) -{ - struct prefix_class *p; - struct dhcp_netid *t; - - for (p = daemon->prefix_classes; p ; p = p->next) - for (t = context->filter; t; t = t->next) - if (strcmp(p->tag.net, t->net) == 0) - return p; - - return NULL; -} -#endif - static int check_ia(struct state *state, void *opt, void **endp, void **ia_option) { state->ia_type = opt6_type(opt); @@ -1664,16 +1621,6 @@ static void add_address(struct state *state, struct dhcp_context *context, unsig put_opt6(addr, sizeof(*addr)); put_opt6_long(preferred_time); put_opt6_long(valid_time); - -#ifdef OPTION6_PREFIX_CLASS - if (state->send_prefix_class) - { - int o1 = new_opt6(OPTION6_PREFIX_CLASS); - put_opt6_short(state->send_prefix_class->class); - end_opt6(o1); - } -#endif - end_opt6(o); if (state->lease_allocate) @@ -1709,16 +1656,9 @@ static void mark_context_used(struct state *state, struct in6_addr *addr) struct dhcp_context *context; /* Mark that we have an address for this prefix. */ -#ifdef OPTION6_PREFIX_CLASS - for (context = state->context; context; context = context->current) - if (is_same_net6(addr, &context->start6, context->prefix) && - (!state->send_prefix_class || state->send_prefix_class == prefix_class_from_context(context))) - context->flags |= CONTEXT_USED; -#else for (context = state->context; context; context = context->current) if (is_same_net6(addr, &context->start6, context->prefix)) context->flags |= CONTEXT_USED; -#endif } static void mark_config_used(struct dhcp_context *context, struct in6_addr *addr) @@ -1745,6 +1685,78 @@ static int check_address(struct state *state, struct in6_addr *addr) } +/* return true of *addr could have been generated from config. */ +static struct addrlist *config_implies(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr) +{ + int prefix; + struct in6_addr wild_addr; + struct addrlist *addr_list; + + if (!config || !(config->flags & CONFIG_ADDR6)) + return NULL; + + for (addr_list = config->addr6; addr_list; addr_list = addr_list->next) + { + prefix = (addr_list->flags & ADDRLIST_PREFIX) ? addr_list->prefixlen : 128; + wild_addr = addr_list->addr.addr6; + + if ((addr_list->flags & ADDRLIST_WILDCARD) && context->prefix == 64) + { + wild_addr = context->start6; + setaddr6part(&wild_addr, addr6part(&addr_list->addr.addr6)); + } + else if (!is_same_net6(&context->start6, addr, context->prefix)) + continue; + + if (is_same_net6(&wild_addr, addr, prefix)) + return addr_list; + } + + return NULL; +} + +static int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr, struct state *state, time_t now) +{ + u64 addrpart, i, addresses; + struct addrlist *addr_list; + + if (!config || !(config->flags & CONFIG_ADDR6)) + return 0; + + for (addr_list = config->addr6; addr_list; addr_list = addr_list->next) + if (!(addr_list->flags & ADDRLIST_DECLINED) || + difftime(now, addr_list->decline_time) >= (float)DECLINE_BACKOFF) + { + addrpart = addr6part(&addr_list->addr.addr6); + addresses = 1; + + if (addr_list->flags & ADDRLIST_PREFIX) + addresses = (u64)1<<(128-addr_list->prefixlen); + + if ((addr_list->flags & ADDRLIST_WILDCARD)) + { + if (context->prefix != 64) + continue; + + *addr = context->start6; + } + else if (is_same_net6(&context->start6, &addr_list->addr.addr6, context->prefix)) + *addr = addr_list->addr.addr6; + else + continue; + + for (i = 0 ; i < addresses; i++) + { + setaddr6part(addr, addrpart+i); + + if (check_address(state, addr)) + return 1; + } + } + + return 0; +} + /* Calculate valid and preferred times to send in leases/renewals. Inputs are: @@ -1935,19 +1947,16 @@ static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_op } else if (type == OPTION6_IAADDR) { - inet_ntop(AF_INET6, opt6_ptr(opt, 0), daemon->addrbuff, ADDRSTRLEN); + struct in6_addr addr; + + /* align */ + memcpy(&addr, opt6_ptr(opt, 0), IN6ADDRSZ); + inet_ntop(AF_INET6, &addr, daemon->addrbuff, ADDRSTRLEN); sprintf(daemon->namebuff, "%s PL=%u VL=%u", daemon->addrbuff, opt6_uint(opt, 16, 4), opt6_uint(opt, 20, 4)); optname = "iaaddr"; ia_options = opt6_ptr(opt, 24); } -#ifdef OPTION6_PREFIX_CLASS - else if (type == OPTION6_PREFIX_CLASS) - { - optname = "prefix-class"; - sprintf(daemon->namebuff, "class=%u", opt6_uint(opt, 0, 2)); - } -#endif else if (type == OPTION6_STATUS_CODE) { int len = sprintf(daemon->namebuff, "%u ", opt6_uint(opt, 0, 2)); @@ -2072,7 +2081,7 @@ void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, { /* ->local is same value for all relays on ->current chain */ - struct all_addr from; + union all_addr from; unsigned char *header; unsigned char *inbuff = daemon->dhcp_packet.iov_base; int msg_type = *inbuff; @@ -2085,7 +2094,7 @@ void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, get_client_mac(peer_address, scope_id, mac, &maclen, &mactype, now); /* source address == relay address */ - from.addr.addr6 = relay->local.addr.addr6; + from.addr6 = relay->local.addr6; /* Get hop count from nested relayed message */ if (msg_type == DHCP6RELAYFORW) @@ -2105,7 +2114,7 @@ void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, header[0] = DHCP6RELAYFORW; header[1] = hopcount; - memcpy(&header[2], &relay->local.addr.addr6, IN6ADDRSZ); + memcpy(&header[2], &relay->local.addr6, IN6ADDRSZ); memcpy(&header[18], peer_address, IN6ADDRSZ); /* RFC-6939 */ @@ -2126,12 +2135,12 @@ void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, union mysockaddr to; to.sa.sa_family = AF_INET6; - to.in6.sin6_addr = relay->server.addr.addr6; + to.in6.sin6_addr = relay->server.addr6; to.in6.sin6_port = htons(DHCPV6_SERVER_PORT); to.in6.sin6_flowinfo = 0; to.in6.sin6_scope_id = 0; - if (IN6_ARE_ADDR_EQUAL(&relay->server.addr.addr6, &multicast)) + if (IN6_ARE_ADDR_EQUAL(&relay->server.addr6, &multicast)) { int multicast_iface; if (!relay->interface || strchr(relay->interface, '*') || @@ -2170,7 +2179,7 @@ unsigned short relay_reply6(struct sockaddr_in6 *peer, ssize_t sz, char *arrival memcpy(&link, &inbuff[2], IN6ADDRSZ); for (relay = daemon->relay6; relay; relay = relay->next) - if (IN6_ARE_ADDR_EQUAL(&link, &relay->local.addr.addr6) && + if (IN6_ARE_ADDR_EQUAL(&link, &relay->local.addr6) && (!relay->interface || wildcard_match(relay->interface, arrival_interface))) break; diff --git a/src/rrfilter.c b/src/rrfilter.c index f5b9c61..16e6b55 100644 --- a/src/rrfilter.c +++ b/src/rrfilter.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 diff --git a/src/slaac.c b/src/slaac.c index 13317d8..4a3e01d 100644 --- a/src/slaac.c +++ b/src/slaac.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -166,7 +166,8 @@ time_t periodic_slaac(time_t now, struct dhcp_lease *leases) if (sendto(daemon->icmp6fd, daemon->outpacket.iov_base, save_counter(-1), 0, (struct sockaddr *)&addr, sizeof(addr)) == -1 && - errno == EHOSTUNREACH) + errno == EHOSTUNREACH && + slaac->backoff == 12) slaac->ping_time = 0; /* Give up */ else { diff --git a/src/tables.c b/src/tables.c index a3382ce..b34ea77 100644 --- a/src/tables.c +++ b/src/tables.c @@ -62,7 +62,7 @@ void ipset_init(void) } } -int add_to_ipset(const char *setname, const struct all_addr *ipaddr, +int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove) { struct pfr_addr addr; @@ -108,19 +108,18 @@ int add_to_ipset(const char *setname, const struct all_addr *ipaddr, my_syslog(LOG_INFO, _("info: table created")); bzero(&addr, sizeof(addr)); -#ifdef HAVE_IPV6 + if (flags & F_IPV6) { addr.pfra_af = AF_INET6; addr.pfra_net = 0x80; - memcpy(&(addr.pfra_ip6addr), &(ipaddr->addr), sizeof(struct in6_addr)); + memcpy(&(addr.pfra_ip6addr), ipaddr, sizeof(struct in6_addr)); } else -#endif { addr.pfra_af = AF_INET; addr.pfra_net = 0x20; - addr.pfra_ip4addr.s_addr = ipaddr->addr.addr4.s_addr; + addr.pfra_ip4addr.s_addr = ipaddr->addr4.s_addr; } bzero(&io, sizeof(io)); @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -18,6 +18,7 @@ #ifdef HAVE_TFTP +static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len); static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix); static void free_transfer(struct tftp_transfer *transfer); static ssize_t tftp_err(int err, char *packet, char *message, char *file); @@ -50,7 +51,7 @@ void tftp_request(struct listener *listen, time_t now) struct ifreq ifr; int is_err = 1, if_index = 0, mtu = 0; struct iname *tmp; - struct tftp_transfer *transfer; + struct tftp_transfer *transfer = NULL, **up; int port = daemon->start_tftp_port; /* may be zero to use ephemeral port */ #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) int mtuflag = IP_PMTUDISC_DONT; @@ -59,24 +60,21 @@ void tftp_request(struct listener *listen, time_t now) char *name = NULL; char *prefix = daemon->tftp_prefix; struct tftp_prefix *pref; - struct all_addr addra; -#ifdef HAVE_IPV6 + union all_addr addra; + int family = listen->addr.sa.sa_family; /* Can always get recvd interface for IPv6 */ - int check_dest = !option_bool(OPT_NOWILD) || listen->family == AF_INET6; -#else - int check_dest = !option_bool(OPT_NOWILD); -#endif + int check_dest = !option_bool(OPT_NOWILD) || family == AF_INET6; union { struct cmsghdr align; /* this ensures alignment */ -#ifdef HAVE_IPV6 char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; -#endif #if defined(HAVE_LINUX_NETWORK) char control[CMSG_SPACE(sizeof(struct in_pktinfo))]; #elif defined(HAVE_SOLARIS_NETWORK) - char control[CMSG_SPACE(sizeof(unsigned int))]; + char control[CMSG_SPACE(sizeof(struct in_addr)) + + CMSG_SPACE(sizeof(unsigned int))]; #elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF) - char control[CMSG_SPACE(sizeof(struct sockaddr_dl))]; + char control[CMSG_SPACE(sizeof(struct in_addr)) + + CMSG_SPACE(sizeof(struct sockaddr_dl))]; #endif } control_u; @@ -124,10 +122,10 @@ void tftp_request(struct listener *listen, time_t now) if (msg.msg_controllen < sizeof(struct cmsghdr)) return; - addr.sa.sa_family = listen->family; + addr.sa.sa_family = family; #if defined(HAVE_LINUX_NETWORK) - if (listen->family == AF_INET) + if (family == AF_INET) for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO) { @@ -141,7 +139,7 @@ void tftp_request(struct listener *listen, time_t now) } #elif defined(HAVE_SOLARIS_NETWORK) - if (listen->family == AF_INET) + if (family == AF_INET) for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) { union { @@ -157,7 +155,7 @@ void tftp_request(struct listener *listen, time_t now) } #elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF) - if (listen->family == AF_INET) + if (family == AF_INET) for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) { union { @@ -174,8 +172,7 @@ void tftp_request(struct listener *listen, time_t now) #endif -#ifdef HAVE_IPV6 - if (listen->family == AF_INET6) + if (family == AF_INET6) { for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo) @@ -190,19 +187,16 @@ void tftp_request(struct listener *listen, time_t now) if_index = p.p->ipi6_ifindex; } } -#endif if (!indextoname(listen->tftpfd, if_index, namebuff)) return; name = namebuff; - addra.addr.addr4 = addr.in.sin_addr; + addra.addr4 = addr.in.sin_addr; -#ifdef HAVE_IPV6 - if (listen->family == AF_INET6) - addra.addr.addr6 = addr.in6.sin6_addr; -#endif + if (family == AF_INET6) + addra.addr6 = addr.in6.sin6_addr; if (daemon->tftp_interfaces) { @@ -217,12 +211,12 @@ void tftp_request(struct listener *listen, time_t now) else { /* Do the same as DHCP */ - if (!iface_check(listen->family, &addra, name, NULL)) + if (!iface_check(family, &addra, name, NULL)) { if (!option_bool(OPT_CLEVERBIND)) enumerate_interfaces(0); - if (!loopback_exception(listen->tftpfd, listen->family, &addra, name) && - !label_exception(if_index, listen->family, &addra) ) + if (!loopback_exception(listen->tftpfd, family, &addra, name) && + !label_exception(if_index, family, &addra)) return; } @@ -234,7 +228,7 @@ void tftp_request(struct listener *listen, time_t now) #endif } - strncpy(ifr.ifr_name, name, IF_NAMESIZE); + safe_strncpy(ifr.ifr_name, name, IF_NAMESIZE); if (ioctl(listen->tftpfd, SIOCGIFMTU, &ifr) != -1) { mtu = ifr.ifr_mtu; @@ -247,6 +241,39 @@ void tftp_request(struct listener *listen, time_t now) if (mtu == 0) mtu = daemon->tftp_mtu; + /* data transfer via server listening socket */ + if (option_bool(OPT_SINGLE_PORT)) + { + int tftp_cnt; + + for (tftp_cnt = 0, transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; up = &transfer->next, transfer = transfer->next) + { + tftp_cnt++; + + if (sockaddr_isequal(&peer, &transfer->peer)) + { + if (ntohs(*((unsigned short *)packet)) == OP_RRQ) + { + /* Handle repeated RRQ or abandoned transfer from same host and port + by unlinking and reusing the struct transfer. */ + *up = transfer->next; + break; + } + else + { + handle_tftp(now, transfer, len); + return; + } + } + } + + /* Enforce simultaneous transfer limit. In non-single-port mode + this is doene by not listening on the server socket when + too many transfers are in progress. */ + if (!transfer && tftp_cnt >= daemon->tftp_max) + return; + } + if (name) { /* check for per-interface prefix */ @@ -255,14 +282,13 @@ void tftp_request(struct listener *listen, time_t now) prefix = pref->prefix; } - if (listen->family == AF_INET) + if (family == AF_INET) { addr.in.sin_port = htons(port); #ifdef HAVE_SOCKADDR_SA_LEN addr.in.sin_len = sizeof(addr.in); #endif } -#ifdef HAVE_IPV6 else { addr.in6.sin6_port = htons(port); @@ -272,18 +298,22 @@ void tftp_request(struct listener *listen, time_t now) addr.in6.sin6_len = sizeof(addr.in6); #endif } -#endif - if (!(transfer = whine_malloc(sizeof(struct tftp_transfer)))) + /* May reuse struct transfer from abandoned transfer in single port mode. */ + if (!transfer && !(transfer = whine_malloc(sizeof(struct tftp_transfer)))) return; - if ((transfer->sockfd = socket(listen->family, SOCK_DGRAM, 0)) == -1) + if (option_bool(OPT_SINGLE_PORT)) + transfer->sockfd = listen->tftpfd; + else if ((transfer->sockfd = socket(family, SOCK_DGRAM, 0)) == -1) { free(transfer); return; } transfer->peer = peer; + transfer->source = addra; + transfer->if_index = if_index; transfer->timeout = now + 2; transfer->backoff = 1; transfer->block = 1; @@ -293,10 +323,10 @@ void tftp_request(struct listener *listen, time_t now) transfer->opt_blocksize = transfer->opt_transize = 0; transfer->netascii = transfer->carrylf = 0; - prettyprint_addr(&peer, daemon->addrbuff); + (void)prettyprint_addr(&peer, daemon->addrbuff); /* if we have a nailed-down range, iterate until we find a free one. */ - while (1) + while (!option_bool(OPT_SINGLE_PORT)) { if (bind(transfer->sockfd, &addr.sa, sa_len(&addr)) == -1 || #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) @@ -308,12 +338,11 @@ void tftp_request(struct listener *listen, time_t now) { if (++port <= daemon->end_tftp_port) { - if (listen->family == AF_INET) + if (family == AF_INET) addr.in.sin_port = htons(port); -#ifdef HAVE_IPV6 else - addr.in6.sin6_port = htons(port); -#endif + addr.in6.sin6_port = htons(port); + continue; } my_syslog(MS_TFTP | LOG_ERR, _("unable to get free port for TFTP")); @@ -326,7 +355,7 @@ void tftp_request(struct listener *listen, time_t now) p = packet + 2; end = packet + len; - + if (ntohs(*((unsigned short *)packet)) != OP_RRQ || !(filename = next(&p, end)) || !(mode = next(&p, end)) || @@ -347,7 +376,7 @@ void tftp_request(struct listener *listen, time_t now) if ((opt = next(&p, end)) && !option_bool(OPT_TFTP_NOBLOCK)) { /* 32 bytes for IP, UDP and TFTP headers, 52 bytes for IPv6 */ - int overhead = (listen->family == AF_INET) ? 32 : 52; + int overhead = (family == AF_INET) ? 32 : 52; transfer->blocksize = atoi(opt); if (transfer->blocksize < 1) transfer->blocksize = 1; @@ -450,9 +479,8 @@ void tftp_request(struct listener *listen, time_t now) is_err = 0; } } - - while (sendto(transfer->sockfd, packet, len, 0, - (struct sockaddr *)&peer, sa_len(&peer)) == -1 && errno == EINTR); + + send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), packet, len, &peer, &addra, if_index); if (is_err) free_transfer(transfer); @@ -549,63 +577,28 @@ static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix) void check_tftp_listeners(time_t now) { struct tftp_transfer *transfer, *tmp, **up; - ssize_t len; - struct ack { - unsigned short op, block; - } *mess = (struct ack *)daemon->packet; - - /* Check for activity on any existing transfers */ - for (transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; transfer = tmp) - { - tmp = transfer->next; - - prettyprint_addr(&transfer->peer, daemon->addrbuff); - + /* In single port mode, all packets come via port 69 and tftp_request() */ + if (!option_bool(OPT_SINGLE_PORT)) + for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next) if (poll_check(transfer->sockfd, POLLIN)) { /* we overwrote the buffer... */ daemon->srv_save = NULL; - - if ((len = recv(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0)) >= (ssize_t)sizeof(struct ack)) - { - if (ntohs(mess->op) == OP_ACK && ntohs(mess->block) == (unsigned short)transfer->block) - { - /* Got ack, ensure we take the (re)transmit path */ - transfer->timeout = now; - transfer->backoff = 0; - if (transfer->block++ != 0) - transfer->offset += transfer->blocksize - transfer->expansion; - } - else if (ntohs(mess->op) == OP_ERR) - { - char *p = daemon->packet + sizeof(struct ack); - char *end = daemon->packet + len; - char *err = next(&p, end); - - /* Sanitise error message */ - if (!err) - err = ""; - else - sanitise(err); - - my_syslog(MS_TFTP | LOG_ERR, _("error %d %s received from %s"), - (int)ntohs(mess->block), err, - daemon->addrbuff); - - /* Got err, ensure we take abort */ - transfer->timeout = now; - transfer->backoff = 100; - } - } + handle_tftp(now, transfer, recv(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0)); } + + for (transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; transfer = tmp) + { + tmp = transfer->next; if (difftime(now, transfer->timeout) >= 0.0) { int endcon = 0; + ssize_t len; /* timeout, retransmit */ - transfer->timeout += 1 + (1<<transfer->backoff); + transfer->timeout += 1 + (1<<(transfer->backoff/2)); /* we overwrote the buffer... */ daemon->srv_save = NULL; @@ -615,22 +608,24 @@ void check_tftp_listeners(time_t now) len = tftp_err_oops(daemon->packet, transfer->file->filename); endcon = 1; } - /* don't complain about timeout when we're awaiting the last - ACK, some clients never send it */ - else if (++transfer->backoff > 7 && len != 0) + else if (++transfer->backoff > 7) { - endcon = 1; + /* don't complain about timeout when we're awaiting the last + ACK, some clients never send it */ + if ((unsigned)len == transfer->blocksize + 4) + endcon = 1; len = 0; } if (len != 0) - while(sendto(transfer->sockfd, daemon->packet, len, 0, - (struct sockaddr *)&transfer->peer, sa_len(&transfer->peer)) == -1 && errno == EINTR); - + send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), daemon->packet, len, + &transfer->peer, &transfer->source, transfer->if_index); + if (endcon || len == 0) { strcpy(daemon->namebuff, transfer->file->filename); sanitise(daemon->namebuff); + (void)prettyprint_addr(&transfer->peer, daemon->addrbuff); my_syslog(MS_TFTP | LOG_INFO, endcon ? _("failed sending %s to %s") : _("sent %s to %s"), daemon->namebuff, daemon->addrbuff); /* unlink */ *up = tmp; @@ -649,15 +644,60 @@ void check_tftp_listeners(time_t now) up = &transfer->next; } } + +/* packet in daemon->packet as this is called. */ +static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len) +{ + struct ack { + unsigned short op, block; + } *mess = (struct ack *)daemon->packet; + + if (len >= (ssize_t)sizeof(struct ack)) + { + if (ntohs(mess->op) == OP_ACK && ntohs(mess->block) == (unsigned short)transfer->block) + { + /* Got ack, ensure we take the (re)transmit path */ + transfer->timeout = now; + transfer->backoff = 0; + if (transfer->block++ != 0) + transfer->offset += transfer->blocksize - transfer->expansion; + } + else if (ntohs(mess->op) == OP_ERR) + { + char *p = daemon->packet + sizeof(struct ack); + char *end = daemon->packet + len; + char *err = next(&p, end); + + (void)prettyprint_addr(&transfer->peer, daemon->addrbuff); + + /* Sanitise error message */ + if (!err) + err = ""; + else + sanitise(err); + + my_syslog(MS_TFTP | LOG_ERR, _("error %d %s received from %s"), + (int)ntohs(mess->block), err, + daemon->addrbuff); + + /* Got err, ensure we take abort */ + transfer->timeout = now; + transfer->backoff = 100; + } + } +} static void free_transfer(struct tftp_transfer *transfer) { - close(transfer->sockfd); + if (!option_bool(OPT_SINGLE_PORT)) + close(transfer->sockfd); + if (transfer->file && (--transfer->file->refcount) == 0) { close(transfer->file->fd); free(transfer->file); } + free(transfer); } diff --git a/src/ubus.c b/src/ubus.c new file mode 100644 index 0000000..5f81287 --- /dev/null +++ b/src/ubus.c @@ -0,0 +1,205 @@ +/* dnsmasq is Copyright (c) 2000-2020 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 <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + +#ifdef HAVE_UBUS + +#include <libubus.h> + +static struct blob_buf b; +static int notify; +static int error_logged = 0; + +static int ubus_handle_metrics(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg); + +static void ubus_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj); + +static const struct ubus_method ubus_object_methods[] = { + UBUS_METHOD_NOARG("metrics", ubus_handle_metrics), +}; + +static struct ubus_object_type ubus_object_type = + UBUS_OBJECT_TYPE("dnsmasq", ubus_object_methods); + +static struct ubus_object ubus_object = { + .name = NULL, + .type = &ubus_object_type, + .methods = ubus_object_methods, + .n_methods = ARRAY_SIZE(ubus_object_methods), + .subscribe_cb = ubus_subscribe_cb, +}; + +static void ubus_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj) +{ + (void)ctx; + + my_syslog(LOG_DEBUG, _("UBus subscription callback: %s subscriber(s)"), obj->has_subscribers ? "1" : "0"); + notify = obj->has_subscribers; +} + +static void ubus_destroy(struct ubus_context *ubus) +{ + // Forces re-initialization when we're reusing the same definitions later on. + ubus_object.id = 0; + ubus_object_type.id = 0; + + ubus_free(ubus); + daemon->ubus = NULL; +} + +static void ubus_disconnect_cb(struct ubus_context *ubus) +{ + int ret; + + ret = ubus_reconnect(ubus, NULL); + if (ret) + { + my_syslog(LOG_ERR, _("Cannot reconnect to UBus: %s"), ubus_strerror(ret)); + + ubus_destroy(ubus); + } +} + +void ubus_init() +{ + struct ubus_context *ubus = NULL; + int ret = 0; + + ubus = ubus_connect(NULL); + if (!ubus) + { + if (!error_logged) + { + my_syslog(LOG_ERR, _("Cannot initialize UBus: connection failed")); + error_logged = 1; + } + + ubus_destroy(ubus); + return; + } + + ubus_object.name = daemon->ubus_name; + ret = ubus_add_object(ubus, &ubus_object); + if (ret) + { + if (!error_logged) + { + my_syslog(LOG_ERR, _("Cannot add object to UBus: %s"), ubus_strerror(ret)); + error_logged = 1; + } + ubus_destroy(ubus); + return; + } + + ubus->connection_lost = ubus_disconnect_cb; + daemon->ubus = ubus; + error_logged = 0; + + my_syslog(LOG_INFO, _("Connected to system UBus")); +} + +void set_ubus_listeners() +{ + struct ubus_context *ubus = (struct ubus_context *)daemon->ubus; + if (!ubus) + { + if (!error_logged) + { + my_syslog(LOG_ERR, _("Cannot set UBus listeners: no connection")); + error_logged = 1; + } + return; + } + + error_logged = 0; + + poll_listen(ubus->sock.fd, POLLIN); + poll_listen(ubus->sock.fd, POLLERR); + poll_listen(ubus->sock.fd, POLLHUP); +} + +void check_ubus_listeners() +{ + struct ubus_context *ubus = (struct ubus_context *)daemon->ubus; + if (!ubus) + { + if (!error_logged) + { + my_syslog(LOG_ERR, _("Cannot poll UBus listeners: no connection")); + error_logged = 1; + } + return; + } + + error_logged = 0; + + if (poll_check(ubus->sock.fd, POLLIN)) + ubus_handle_event(ubus); + + if (poll_check(ubus->sock.fd, POLLHUP | POLLERR)) + { + my_syslog(LOG_INFO, _("Disconnecting from UBus")); + + ubus_destroy(ubus); + } +} + +static int ubus_handle_metrics(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + int i; + + (void)obj; + (void)method; + (void)msg; + + blob_buf_init(&b, BLOBMSG_TYPE_TABLE); + + for (i=0; i < __METRIC_MAX; i++) + blobmsg_add_u32(&b, get_metric_name(i), daemon->metrics[i]); + + return ubus_send_reply(ctx, req, b.head); +} + +void ubus_event_bcast(const char *type, const char *mac, const char *ip, const char *name, const char *interface) +{ + struct ubus_context *ubus = (struct ubus_context *)daemon->ubus; + int ret; + + if (!ubus || !notify) + return; + + blob_buf_init(&b, BLOBMSG_TYPE_TABLE); + if (mac) + blobmsg_add_string(&b, "mac", mac); + if (ip) + blobmsg_add_string(&b, "ip", ip); + if (name) + blobmsg_add_string(&b, "name", name); + if (interface) + blobmsg_add_string(&b, "interface", interface); + + ret = ubus_notify(ubus, &ubus_object, type, b.head, -1); + if (!ret) + my_syslog(LOG_ERR, _("Failed to send UBus event: %s"), ubus_strerror(ret)); +} + + +#endif /* HAVE_UBUS */ @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2020 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 @@ -30,6 +30,10 @@ #include <idna.h> #endif +#ifdef HAVE_LINUX_NETWORK +#include <sys/utsname.h> +#endif + /* SURF random number generator */ static u32 seed[32]; @@ -281,7 +285,18 @@ void *safe_malloc(size_t size) die(_("could not get memory"), NULL, EC_NOMEM); return ret; -} +} + +/* Ensure limited size string is always terminated. + * Can be replaced by (void)strlcpy() on some platforms */ +void safe_strncpy(char *dest, const char *src, size_t size) +{ + if (size != 0) + { + dest[size-1] = '\0'; + strncpy(dest, src, size-1); + } +} void safe_pipe(int *fd, int read_noblock) { @@ -309,13 +324,12 @@ int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2) s1->in.sin_port == s2->in.sin_port && s1->in.sin_addr.s_addr == s2->in.sin_addr.s_addr) return 1; -#ifdef HAVE_IPV6 + if (s1->sa.sa_family == AF_INET6 && s1->in6.sin6_port == s2->in6.sin6_port && s1->in6.sin6_scope_id == s2->in6.sin6_scope_id && IN6_ARE_ADDR_EQUAL(&s1->in6.sin6_addr, &s2->in6.sin6_addr)) return 1; -#endif } return 0; } @@ -325,11 +339,9 @@ int sa_len(union mysockaddr *addr) #ifdef HAVE_SOCKADDR_SA_LEN return addr->sa.sa_len; #else -#ifdef HAVE_IPV6 if (addr->sa.sa_family == AF_INET6) return sizeof(addr->in6); else -#endif return sizeof(addr->in); #endif } @@ -355,6 +367,44 @@ int hostname_isequal(const char *a, const char *b) return 1; } +/* is b equal to or a subdomain of a return 2 for equal, 1 for subdomain */ +int hostname_issubdomain(char *a, char *b) +{ + char *ap, *bp; + unsigned int c1, c2; + + /* move to the end */ + for (ap = a; *ap; ap++); + for (bp = b; *bp; bp++); + + /* a shorter than b or a empty. */ + if ((bp - b) < (ap - a) || ap == a) + return 0; + + do + { + c1 = (unsigned char) *(--ap); + c2 = (unsigned char) *(--bp); + + if (c1 >= 'A' && c1 <= 'Z') + c1 += 'a' - 'A'; + if (c2 >= 'A' && c2 <= 'Z') + c2 += 'a' - 'A'; + + if (c1 != c2) + return 0; + } while (ap != a); + + if (bp == b) + return 2; + + if (*(--bp) == '.') + return 1; + + return 0; +} + + time_t dnsmasq_time(void) { #ifdef HAVE_BROKEN_RTC @@ -388,7 +438,6 @@ 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); } -#ifdef HAVE_IPV6 int is_same_net6(struct in6_addr *a, struct in6_addr *b, int prefixlen) { int pfbytes = prefixlen >> 3; @@ -427,15 +476,12 @@ void setaddr6part(struct in6_addr *addr, u64 host) } } -#endif - /* returns port number from address */ int prettyprint_addr(union mysockaddr *addr, char *buf) { int port = 0; -#ifdef HAVE_IPV6 if (addr->sa.sa_family == AF_INET) { inet_ntop(AF_INET, &addr->in.sin_addr, buf, ADDRSTRLEN); @@ -454,10 +500,6 @@ int prettyprint_addr(union mysockaddr *addr, char *buf) } port = ntohs(addr->in6.sin6_port); } -#else - strcpy(buf, inet_ntoa(addr->in.sin_addr)); - port = ntohs(addr->in.sin_port); -#endif return port; } @@ -486,20 +528,20 @@ void prettyprint_time(char *buf, unsigned int t) int parse_hex(char *in, unsigned char *out, int maxlen, unsigned int *wildcard_mask, int *mac_type) { - int mask = 0, i = 0; + int done = 0, mask = 0, i = 0; char *r; if (mac_type) *mac_type = 0; - while (maxlen == -1 || i < maxlen) + while (!done && (maxlen == -1 || i < maxlen)) { for (r = in; *r != 0 && *r != ':' && *r != '-' && *r != ' '; r++) if (*r != '*' && !isxdigit((unsigned char)*r)) return -1; if (*r == 0) - maxlen = i; + done = 1; if (r != in ) { @@ -667,6 +709,47 @@ int read_write(int fd, unsigned char *packet, int size, int rw) return 1; } +/* close all fds except STDIN, STDOUT and STDERR, spare1, spare2 and spare3 */ +void close_fds(long max_fd, int spare1, int spare2, int spare3) +{ + /* On Linux, use the /proc/ filesystem to find which files + are actually open, rather than iterate over the whole space, + for efficiency reasons. If this fails we drop back to the dumb code. */ +#ifdef HAVE_LINUX_NETWORK + DIR *d; + + if ((d = opendir("/proc/self/fd"))) + { + struct dirent *de; + + while ((de = readdir(d))) + { + long fd; + char *e = NULL; + + errno = 0; + fd = strtol(de->d_name, &e, 10); + + if (errno != 0 || !e || *e || fd == dirfd(d) || + fd == STDOUT_FILENO || fd == STDERR_FILENO || fd == STDIN_FILENO || + fd == spare1 || fd == spare2 || fd == spare3) + continue; + + close(fd); + } + + closedir(d); + return; + } +#endif + + /* fallback, dumb code. */ + for (max_fd--; max_fd >= 0; max_fd--) + if (max_fd != STDOUT_FILENO && max_fd != STDERR_FILENO && max_fd != STDIN_FILENO && + max_fd != spare1 && max_fd != spare2 && max_fd != spare3) + close(max_fd); +} + /* Basically match a string value against a wildcard pattern. */ int wildcard_match(const char* wildcard, const char* match) { @@ -703,3 +786,22 @@ int wildcard_matchn(const char* wildcard, const char* match, int num) return (!num) || (*wildcard == *match); } + +#ifdef HAVE_LINUX_NETWORK +int kernel_version(void) +{ + struct utsname utsname; + int version; + char *split; + + if (uname(&utsname) < 0) + die(_("failed to find kernel version: %s"), NULL, EC_MISC); + + split = strtok(utsname.release, "."); + version = (split ? atoi(split) : 0); + split = strtok(NULL, "."); + version = version * 256 + (split ? atoi(split) : 0); + split = strtok(NULL, "."); + return version * 256 + (split ? atoi(split) : 0); +} +#endif |