Merge remote-tracking branch 'mfischer/dnsmasq' into next

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>

Conflicts:
	lfs/dnsmasq
This commit is contained in:
Michael Tremer
2015-06-18 13:12:33 +02:00
8 changed files with 497 additions and 118 deletions

View File

@@ -0,0 +1,331 @@
From a77cec8d58231d71cbc26615f0c0f0292c09ef54 Mon Sep 17 00:00:00 2001
From: Simon Kelley <simon@thekelleys.org.uk>
Date: Fri, 8 May 2015 16:25:38 +0100
Subject: [PATCH] Handle UDP packet loss when fragmentation of large packets
is broken.
---
CHANGELOG | 6 ++++++
src/config.h | 1 +
src/dnsmasq.h | 5 +++--
src/dnssec.c | 11 +++++++++--
src/forward.c | 37 +++++++++++++++++++++++++++++--------
src/network.c | 1 +
src/option.c | 18 +++++++++++-------
src/rfc1035.c | 22 ++++++----------------
8 files changed, 66 insertions(+), 35 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index af2b22c..d8fc57a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -109,6 +109,12 @@ version 2.73
by quiet-dhcp6. Thanks to J. Pablo Abonia for
spotting the problem.
+ Try and handle net connections with broken fragmentation
+ that lose large UDP packets. If a server times out,
+ reduce the maximum UDP packet size field in the EDNS0
+ header to 1280 bytes. If it then answers, make that
+ change permanent.
+
version 2.72
Add ra-advrouter mode, for RFC-3775 mobile IPv6 support.
diff --git a/src/config.h b/src/config.h
index 8def6f2..f75fe9d 100644
--- a/src/config.h
+++ b/src/config.h
@@ -19,6 +19,7 @@
#define CHILD_LIFETIME 150 /* secs 'till terminated (RFC1035 suggests > 120s) */
#define TCP_MAX_QUERIES 100 /* Maximum number of queries per incoming TCP connection */
#define EDNS_PKTSZ 4096 /* default max EDNS.0 UDP packet from RFC5625 */
+#define SAFE_PKTSZ 1280 /* "go anywhere" UDP packet size */
#define KEYBLOCK_LEN 40 /* choose to mininise fragmentation when storing DNSSEC keys */
#define DNSSEC_WORK 50 /* Max number of queries to validate one question */
#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 824a860..ab16f79 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -504,7 +504,7 @@ struct server {
char interface[IF_NAMESIZE+1];
struct serverfd *sfd;
char *domain; /* set if this server only handles a domain. */
- int flags, tcpfd;
+ int flags, tcpfd, edns_pktsz;
unsigned int queries, failed_queries;
#ifdef HAVE_LOOP
u32 uid;
@@ -594,6 +594,7 @@ struct hostsfile {
#define FREC_DO_QUESTION 64
#define FREC_ADDED_PHEADER 128
#define FREC_CHECK_NOSIGN 256
+#define FREC_TEST_PKTSZ 512
#ifdef HAVE_DNSSEC
#define HASH_SIZE 20 /* SHA-1 digest size */
@@ -1148,7 +1149,7 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
#endif
/* dnssec.c */
-size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr);
+size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr, int edns_pktsz);
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, 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 *neganswer, int *nons);
diff --git a/src/dnssec.c b/src/dnssec.c
index a9e1215..e91d7c2 100644
--- a/src/dnssec.c
+++ b/src/dnssec.c
@@ -2162,10 +2162,12 @@ int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
}
}
-size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr)
+size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class,
+ int type, union mysockaddr *addr, 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);
@@ -2194,7 +2196,12 @@ size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, i
PUTSHORT(type, p);
PUTSHORT(class, p);
- return add_do_bit(header, p - (unsigned char *)header, end);
+ ret = add_do_bit(header, p - (unsigned char *)header, end);
+
+ if (find_pseudoheader(header, ret, NULL, &p, NULL))
+ PUTSHORT(edns_pktsz, p);
+
+ return ret;
}
/* Go through a domain name, find "pointers" and fix them up based on how many bytes
diff --git a/src/forward.c b/src/forward.c
index a8e403c..592243f 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -253,6 +253,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
void *hash = &crc;
#endif
unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
+ unsigned char *pheader;
(void)do_bit;
@@ -261,19 +262,32 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
forward = NULL;
else if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash))))
{
+ /* If we didn't get an answer advertising a maximal packet in EDNS,
+ fall back to 1280, which should work everywhere on IPv6.
+ If that generates an answer, it will become the new default
+ for this server */
+ forward->flags |= FREC_TEST_PKTSZ;
+
#ifdef HAVE_DNSSEC
/* If we've already got an answer to this query, but we're awaiting keys for validation,
there's no point retrying the query, retry the key query instead...... */
if (forward->blocking_query)
{
int fd;
-
+
+ forward->flags &= ~FREC_TEST_PKTSZ;
+
while (forward->blocking_query)
forward = forward->blocking_query;
+
+ forward->flags |= FREC_TEST_PKTSZ;
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
plen = forward->stash_len;
+ if (find_pseudoheader(header, plen, NULL, &pheader, NULL))
+ PUTSHORT((forward->flags & FREC_TEST_PKTSZ) ? SAFE_PKTSZ : forward->sentto->edns_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
@@ -417,7 +431,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
plen = new_plen;
}
#endif
-
+
while (1)
{
/* only send to servers dealing with our domain.
@@ -464,6 +478,9 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
}
#endif
}
+
+ if (find_pseudoheader(header, plen, NULL, &pheader, NULL))
+ PUTSHORT((forward->flags & FREC_TEST_PKTSZ) ? SAFE_PKTSZ : start->edns_pktsz, pheader);
if (retry_send(sendto(fd, (char *)header, plen, 0,
&start->addr.sa,
@@ -760,7 +777,6 @@ void reply_query(int fd, int family, time_t now)
}
server = forward->sentto;
-
if ((forward->sentto->flags & SERV_TYPE) == 0)
{
if (RCODE(header) == REFUSED)
@@ -781,7 +797,12 @@ void reply_query(int fd, int family, time_t now)
if (!option_bool(OPT_ALL_SERVERS))
daemon->last_server = server;
}
-
+
+ /* We tried resending to this server with a smaller maximum size and got an answer.
+ Make that permanent. */
+ if (server && (forward->flags & FREC_TEST_PKTSZ))
+ server->edns_pktsz = SAFE_PKTSZ;
+
/* If the answer is an error, keep the forward record in place in case
we get a good reply from another server. Kill it when we've
had replies from all to avoid filling the forwarding table when
@@ -890,7 +911,7 @@ void reply_query(int fd, int family, time_t now)
{
new->flags |= FREC_DNSKEY_QUERY;
nn = dnssec_generate_query(header, ((char *) header) + daemon->packet_buff_sz,
- daemon->keyname, forward->class, T_DNSKEY, &server->addr);
+ daemon->keyname, forward->class, T_DNSKEY, &server->addr, server->edns_pktsz);
}
else
{
@@ -899,7 +920,7 @@ void reply_query(int fd, int family, time_t now)
else
new->flags |= FREC_DS_QUERY;
nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz,
- daemon->keyname, forward->class, T_DS, &server->addr);
+ daemon->keyname, forward->class, T_DS, &server->addr, server->edns_pktsz);
}
if ((hash = hash_questions(header, nn, daemon->namebuff)))
memcpy(new->hash, hash, HASH_SIZE);
@@ -1526,7 +1547,7 @@ static int tcp_check_for_unsigned_zone(time_t now, struct dns_header *header, s
/* Can't find it in the cache, have to send a query */
- m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr);
+ m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr, server->edns_pktsz);
*length = htons(m);
@@ -1638,7 +1659,7 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
another_tcp_key:
m = dnssec_generate_query(new_header, ((char *) new_header) + 65536, keyname, class,
- new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr);
+ new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr, server->edns_pktsz);
*length = htons(m);
diff --git a/src/network.c b/src/network.c
index 992f023..a1d90c8 100644
--- a/src/network.c
+++ b/src/network.c
@@ -1396,6 +1396,7 @@ void add_update_server(int flags,
serv->domain = domain_str;
serv->next = next;
serv->queries = serv->failed_queries = 0;
+ serv->edns_pktsz = daemon->edns_pktsz;
#ifdef HAVE_LOOP
serv->uid = rand32();
#endif
diff --git a/src/option.c b/src/option.c
index f91cfbb..c7add88 100644
--- a/src/option.c
+++ b/src/option.c
@@ -4498,15 +4498,19 @@ void read_opts(int argc, char **argv, char *compile_opts)
{
struct server *tmp;
for (tmp = daemon->servers; tmp; tmp = tmp->next)
- if (!(tmp->flags & SERV_HAS_SOURCE))
- {
- if (tmp->source_addr.sa.sa_family == AF_INET)
- tmp->source_addr.in.sin_port = htons(daemon->query_port);
+ {
+ tmp->edns_pktsz = daemon->edns_pktsz;
+
+ if (!(tmp->flags & SERV_HAS_SOURCE))
+ {
+ 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);
+ else if (tmp->source_addr.sa.sa_family == AF_INET6)
+ tmp->source_addr.in6.sin6_port = htons(daemon->query_port);
#endif
- }
+ }
+ }
}
if (daemon->if_addrs)
diff --git a/src/rfc1035.c b/src/rfc1035.c
index 5828055..8b1709d 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -552,7 +552,7 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned
return plen;
*p++ = 0; /* empty name */
PUTSHORT(T_OPT, p);
- PUTSHORT(daemon->edns_pktsz, p); /* max packet length */
+ PUTSHORT(SAFE_PKTSZ, p); /* max packet length, this will be overwritten */
PUTSHORT(0, p); /* extended RCODE and version */
PUTSHORT(set_do ? 0x8000 : 0, p); /* DO flag */
lenp = p;
@@ -1537,7 +1537,6 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
unsigned short flag;
int q, ans, anscount = 0, addncount = 0;
int dryrun = 0, sec_reqd = 0, have_pseudoheader = 0;
- int is_sign;
struct crec *crecp;
int nxdomain = 0, auth = 1, trunc = 0, sec_data = 1;
struct mx_srv_record *rec;
@@ -1557,28 +1556,19 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
forward rather than answering from the cache, which doesn't include
security information, unless we're in DNSSEC validation mode. */
- if (find_pseudoheader(header, qlen, NULL, &pheader, &is_sign))
+ if (find_pseudoheader(header, qlen, NULL, &pheader, NULL))
{
- unsigned short udpsz, flags;
- unsigned char *psave = pheader;
-
+ unsigned short flags;
+
have_pseudoheader = 1;
- GETSHORT(udpsz, pheader);
- pheader += 2; /* ext_rcode */
+ pheader += 4; /* udp size, ext_rcode */
GETSHORT(flags, pheader);
if ((sec_reqd = flags & 0x8000))
*do_bit = 1;/* do bit */
- *ad_reqd = 1;
-
- /* If our client is advertising a larger UDP packet size
- than we allow, trim it so that we don't get an overlarge
- response from upstream */
-
- if (!is_sign && (udpsz > daemon->edns_pktsz))
- PUTSHORT(daemon->edns_pktsz, psave);
+ *ad_reqd = 1;
dryrun = 1;
}
--
1.7.10.4

View File

@@ -0,0 +1,58 @@
From b059c96dc69dfe3055c5b32b078a05c53b11ebb3 Mon Sep 17 00:00:00 2001
From: Simon Kelley <simon@thekelleys.org.uk>
Date: Fri, 8 May 2015 20:25:51 +0100
Subject: [PATCH] Check IPv4-mapped IPv6 addresses with --stop-rebind.
---
CHANGELOG | 3 +++
src/rfc1035.c | 21 +++++++++++++++++----
2 files changed, 20 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index d8fc57a..94a521f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -115,6 +115,9 @@ version 2.73
header to 1280 bytes. If it then answers, make that
change permanent.
+ Check IPv4-mapped IPv6 addresses when --stop-rebind
+ is active. Thanks to Jordan Milne for spotting this.
+
version 2.72
Add ra-advrouter mode, for RFC-3775 mobile IPv6 support.
diff --git a/src/rfc1035.c b/src/rfc1035.c
index 8b1709d..5e3f566 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -1117,10 +1117,23 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
memcpy(&addr, p1, addrlen);
/* check for returned address in private space */
- if (check_rebind &&
- (flags & F_IPV4) &&
- private_net(addr.addr.addr4, !option_bool(OPT_LOCAL_REBIND)))
- return 1;
+ if (check_rebind)
+ {
+ 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))
+ {
+ struct in_addr v4;
+ v4.s_addr = ((const uint32_t *) (&addr.addr.addr6))[3];
+ if (private_net(v4, !option_bool(OPT_LOCAL_REBIND)))
+ return 1;
+ }
+#endif
+ }
#ifdef HAVE_IPSET
if (ipsets && (flags & (F_IPV4 | F_IPV6)))
--
1.7.10.4

View File

@@ -26,4 +26,3 @@ index 592243fd4d35..74e5ab66c423 100644
/* If the answer is an error, keep the forward record in place in case
--
2.1.0

View File

@@ -0,0 +1,27 @@
From 585840b03365372679907f175b07a01c9d621ae0 Mon Sep 17 00:00:00 2001
From: Simon Kelley <simon@thekelleys.org.uk>
Date: Wed, 13 May 2015 12:35:57 +0100
Subject: [PATCH] Pointer to mail-archive mailing list mirror in doc.html.
---
doc.html | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/doc.html b/doc.html
index 92c9d0d..54f59bb 100644
--- a/doc.html
+++ b/doc.html
@@ -74,7 +74,9 @@ for details.
There is a dnsmasq mailing list at <A
HREF="http://lists.thekelleys.org.uk/mailman/listinfo/dnsmasq-discuss">
http://lists.thekelleys.org.uk/mailman/listinfo/dnsmasq-discuss</A> which should be the
-first location for queries, bugreports, suggestions etc.
+first location for queries, bugreports, suggestions etc. The list is mirrored, with a
+search facility, at <A HREF="https://www.mail-archive.com/dnsmasq-discuss@lists.thekelleys.org.uk/">
+https://www.mail-archive.com/dnsmasq-discuss@lists.thekelleys.org.uk/</A>.
You can contact me at <A
HREF="mailto:simon@thekelleys.org.uk">simon@thekelleys.org.uk</A>.
--
1.7.10.4

View File

@@ -197,4 +197,3 @@ index 55526443dc84..a10e499ef768 100644
if (optno == OPTION_SNAME && done_server)
--
2.1.0

View File

@@ -0,0 +1,29 @@
From 62018e1f720fa11e83879111a4b1b3753b5c25bb Mon Sep 17 00:00:00 2001
From: Simon Kelley <simon@thekelleys.org.uk>
Date: Thu, 14 May 2015 21:30:00 +0100
Subject: [PATCH] Use correct DHCP context for PXE-proxy server-id.
---
src/rfc2131.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/rfc2131.c b/src/rfc2131.c
index b95f9be..70d1e59 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -888,10 +888,10 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
option_put(mess, end, OPTION_MESSAGE_TYPE, 1,
mess_type == DHCPDISCOVER ? DHCPOFFER : DHCPACK);
- option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(context->local.s_addr));
+ option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(tmp->local.s_addr));
pxe_misc(mess, end, uuid);
prune_vendor_opts(tagif_netid);
- do_encap_opts(pxe_opts(pxearch, tagif_netid, context->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
+ do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", NULL, mess->xid);
log_tags(tagif_netid, ntohl(mess->xid));
--
1.7.10.4

View File

@@ -0,0 +1,49 @@
From 5d07d77e75e0f02bc0a8f6029ffbc8b371fa804e Mon Sep 17 00:00:00 2001
From: Simon Kelley <simon@thekelleys.org.uk>
Date: Fri, 15 May 2015 18:13:06 +0100
Subject: [PATCH] Fix buffer overflow introduced in 2.73rc6.
Fix off-by-one in code which checks for over-long domain names
in received DNS packets. This enables buffer overflow attacks
which can certainly crash dnsmasq and may allow for arbitrary
code execution. The problem was introduced in commit b8f16556d,
release 2.73rc6, so has not escaped into any stable release.
Note that the off-by-one was in the label length determination,
so the buffer can be overflowed by as many bytes as there are
labels in the name - ie, many.
Thanks to Ron Bowes, who used lcmatuf's afl-fuzz tool to find
the problem.
---
src/rfc1035.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/rfc1035.c b/src/rfc1035.c
index 5e3f566..a95241f 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -94,8 +94,8 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
count = 256;
digs = ((count-1)>>2)+1;
- /* output is \[x<hex>/siz]. which is digs+6/7/8 chars */
- namelen += digs+6;
+ /* output is \[x<hex>/siz]. which is digs+7/8/9 chars */
+ namelen += digs+7;
if (count > 9)
namelen++;
if (count > 99)
@@ -125,8 +125,8 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
}
else
{ /* label_type = 0 -> label. */
- namelen += l;
- if (namelen+1 >= MAXDNAME)
+ namelen += l + 1; /* include period */
+ if (namelen >= MAXDNAME)
return 0;
if (!CHECK_LEN(header, p, plen, l))
return 0;
--
1.7.10.4