]> git.ipfire.org Git - ipfire-2.x.git/blob - src/patches/dnsmasq/0088-Handle-UDP-packet-loss-when-fragmentation-of-large-p.patch
Merge remote-tracking branch 'earl/tor' into next
[ipfire-2.x.git] / src / patches / dnsmasq / 0088-Handle-UDP-packet-loss-when-fragmentation-of-large-p.patch
1 From a77cec8d58231d71cbc26615f0c0f0292c09ef54 Mon Sep 17 00:00:00 2001
2 From: Simon Kelley <simon@thekelleys.org.uk>
3 Date: Fri, 8 May 2015 16:25:38 +0100
4 Subject: [PATCH 88/98] Handle UDP packet loss when fragmentation of large
5 packets is broken.
6
7 ---
8 CHANGELOG | 6 ++++++
9 src/config.h | 1 +
10 src/dnsmasq.h | 5 +++--
11 src/dnssec.c | 11 +++++++++--
12 src/forward.c | 37 +++++++++++++++++++++++++++++--------
13 src/network.c | 1 +
14 src/option.c | 18 +++++++++++-------
15 src/rfc1035.c | 22 ++++++----------------
16 8 files changed, 66 insertions(+), 35 deletions(-)
17
18 diff --git a/CHANGELOG b/CHANGELOG
19 index af2b22cf8f73..d8fc57a418bb 100644
20 --- a/CHANGELOG
21 +++ b/CHANGELOG
22 @@ -109,6 +109,12 @@ version 2.73
23 by quiet-dhcp6. Thanks to J. Pablo Abonia for
24 spotting the problem.
25
26 + Try and handle net connections with broken fragmentation
27 + that lose large UDP packets. If a server times out,
28 + reduce the maximum UDP packet size field in the EDNS0
29 + header to 1280 bytes. If it then answers, make that
30 + change permanent.
31 +
32
33 version 2.72
34 Add ra-advrouter mode, for RFC-3775 mobile IPv6 support.
35 diff --git a/src/config.h b/src/config.h
36 index 8def6f200461..f75fe9db7081 100644
37 --- a/src/config.h
38 +++ b/src/config.h
39 @@ -19,6 +19,7 @@
40 #define CHILD_LIFETIME 150 /* secs 'till terminated (RFC1035 suggests > 120s) */
41 #define TCP_MAX_QUERIES 100 /* Maximum number of queries per incoming TCP connection */
42 #define EDNS_PKTSZ 4096 /* default max EDNS.0 UDP packet from RFC5625 */
43 +#define SAFE_PKTSZ 1280 /* "go anywhere" UDP packet size */
44 #define KEYBLOCK_LEN 40 /* choose to mininise fragmentation when storing DNSSEC keys */
45 #define DNSSEC_WORK 50 /* Max number of queries to validate one question */
46 #define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
47 diff --git a/src/dnsmasq.h b/src/dnsmasq.h
48 index 824a86009439..ab16f79b3ec9 100644
49 --- a/src/dnsmasq.h
50 +++ b/src/dnsmasq.h
51 @@ -504,7 +504,7 @@ struct server {
52 char interface[IF_NAMESIZE+1];
53 struct serverfd *sfd;
54 char *domain; /* set if this server only handles a domain. */
55 - int flags, tcpfd;
56 + int flags, tcpfd, edns_pktsz;
57 unsigned int queries, failed_queries;
58 #ifdef HAVE_LOOP
59 u32 uid;
60 @@ -594,6 +594,7 @@ struct hostsfile {
61 #define FREC_DO_QUESTION 64
62 #define FREC_ADDED_PHEADER 128
63 #define FREC_CHECK_NOSIGN 256
64 +#define FREC_TEST_PKTSZ 512
65
66 #ifdef HAVE_DNSSEC
67 #define HASH_SIZE 20 /* SHA-1 digest size */
68 @@ -1148,7 +1149,7 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
69 #endif
70
71 /* dnssec.c */
72 -size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr);
73 +size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr, int edns_pktsz);
74 int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class);
75 int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
76 int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int *neganswer, int *nons);
77 diff --git a/src/dnssec.c b/src/dnssec.c
78 index a9e12153ccf2..e91d7c2cf040 100644
79 --- a/src/dnssec.c
80 +++ b/src/dnssec.c
81 @@ -2162,10 +2162,12 @@ int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
82 }
83 }
84
85 -size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr)
86 +size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class,
87 + int type, union mysockaddr *addr, int edns_pktsz)
88 {
89 unsigned char *p;
90 char *types = querystr("dnssec-query", type);
91 + size_t ret;
92
93 if (addr->sa.sa_family == AF_INET)
94 log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types);
95 @@ -2194,7 +2196,12 @@ size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, i
96 PUTSHORT(type, p);
97 PUTSHORT(class, p);
98
99 - return add_do_bit(header, p - (unsigned char *)header, end);
100 + ret = add_do_bit(header, p - (unsigned char *)header, end);
101 +
102 + if (find_pseudoheader(header, ret, NULL, &p, NULL))
103 + PUTSHORT(edns_pktsz, p);
104 +
105 + return ret;
106 }
107
108 /* Go through a domain name, find "pointers" and fix them up based on how many bytes
109 diff --git a/src/forward.c b/src/forward.c
110 index a8e403c4b25e..592243fd4d35 100644
111 --- a/src/forward.c
112 +++ b/src/forward.c
113 @@ -253,6 +253,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
114 void *hash = &crc;
115 #endif
116 unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
117 + unsigned char *pheader;
118
119 (void)do_bit;
120
121 @@ -261,19 +262,32 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
122 forward = NULL;
123 else if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash))))
124 {
125 + /* If we didn't get an answer advertising a maximal packet in EDNS,
126 + fall back to 1280, which should work everywhere on IPv6.
127 + If that generates an answer, it will become the new default
128 + for this server */
129 + forward->flags |= FREC_TEST_PKTSZ;
130 +
131 #ifdef HAVE_DNSSEC
132 /* If we've already got an answer to this query, but we're awaiting keys for validation,
133 there's no point retrying the query, retry the key query instead...... */
134 if (forward->blocking_query)
135 {
136 int fd;
137 -
138 +
139 + forward->flags &= ~FREC_TEST_PKTSZ;
140 +
141 while (forward->blocking_query)
142 forward = forward->blocking_query;
143 +
144 + forward->flags |= FREC_TEST_PKTSZ;
145
146 blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
147 plen = forward->stash_len;
148
149 + if (find_pseudoheader(header, plen, NULL, &pheader, NULL))
150 + PUTSHORT((forward->flags & FREC_TEST_PKTSZ) ? SAFE_PKTSZ : forward->sentto->edns_pktsz, pheader);
151 +
152 if (forward->sentto->addr.sa.sa_family == AF_INET)
153 log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (struct all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec");
154 #ifdef HAVE_IPV6
155 @@ -417,7 +431,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
156 plen = new_plen;
157 }
158 #endif
159 -
160 +
161 while (1)
162 {
163 /* only send to servers dealing with our domain.
164 @@ -464,6 +478,9 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
165 }
166 #endif
167 }
168 +
169 + if (find_pseudoheader(header, plen, NULL, &pheader, NULL))
170 + PUTSHORT((forward->flags & FREC_TEST_PKTSZ) ? SAFE_PKTSZ : start->edns_pktsz, pheader);
171
172 if (retry_send(sendto(fd, (char *)header, plen, 0,
173 &start->addr.sa,
174 @@ -760,7 +777,6 @@ void reply_query(int fd, int family, time_t now)
175 }
176
177 server = forward->sentto;
178 -
179 if ((forward->sentto->flags & SERV_TYPE) == 0)
180 {
181 if (RCODE(header) == REFUSED)
182 @@ -781,7 +797,12 @@ void reply_query(int fd, int family, time_t now)
183 if (!option_bool(OPT_ALL_SERVERS))
184 daemon->last_server = server;
185 }
186 -
187 +
188 + /* We tried resending to this server with a smaller maximum size and got an answer.
189 + Make that permanent. */
190 + if (server && (forward->flags & FREC_TEST_PKTSZ))
191 + server->edns_pktsz = SAFE_PKTSZ;
192 +
193 /* If the answer is an error, keep the forward record in place in case
194 we get a good reply from another server. Kill it when we've
195 had replies from all to avoid filling the forwarding table when
196 @@ -890,7 +911,7 @@ void reply_query(int fd, int family, time_t now)
197 {
198 new->flags |= FREC_DNSKEY_QUERY;
199 nn = dnssec_generate_query(header, ((char *) header) + daemon->packet_buff_sz,
200 - daemon->keyname, forward->class, T_DNSKEY, &server->addr);
201 + daemon->keyname, forward->class, T_DNSKEY, &server->addr, server->edns_pktsz);
202 }
203 else
204 {
205 @@ -899,7 +920,7 @@ void reply_query(int fd, int family, time_t now)
206 else
207 new->flags |= FREC_DS_QUERY;
208 nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz,
209 - daemon->keyname, forward->class, T_DS, &server->addr);
210 + daemon->keyname, forward->class, T_DS, &server->addr, server->edns_pktsz);
211 }
212 if ((hash = hash_questions(header, nn, daemon->namebuff)))
213 memcpy(new->hash, hash, HASH_SIZE);
214 @@ -1526,7 +1547,7 @@ static int tcp_check_for_unsigned_zone(time_t now, struct dns_header *header, s
215
216 /* Can't find it in the cache, have to send a query */
217
218 - m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr);
219 + m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr, server->edns_pktsz);
220
221 *length = htons(m);
222
223 @@ -1638,7 +1659,7 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
224
225 another_tcp_key:
226 m = dnssec_generate_query(new_header, ((char *) new_header) + 65536, keyname, class,
227 - new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr);
228 + new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr, server->edns_pktsz);
229
230 *length = htons(m);
231
232 diff --git a/src/network.c b/src/network.c
233 index 992f023c31de..a1d90c876fc1 100644
234 --- a/src/network.c
235 +++ b/src/network.c
236 @@ -1396,6 +1396,7 @@ void add_update_server(int flags,
237 serv->domain = domain_str;
238 serv->next = next;
239 serv->queries = serv->failed_queries = 0;
240 + serv->edns_pktsz = daemon->edns_pktsz;
241 #ifdef HAVE_LOOP
242 serv->uid = rand32();
243 #endif
244 diff --git a/src/option.c b/src/option.c
245 index f91cfbb1aa54..c7add88de7ac 100644
246 --- a/src/option.c
247 +++ b/src/option.c
248 @@ -4498,15 +4498,19 @@ void read_opts(int argc, char **argv, char *compile_opts)
249 {
250 struct server *tmp;
251 for (tmp = daemon->servers; tmp; tmp = tmp->next)
252 - if (!(tmp->flags & SERV_HAS_SOURCE))
253 - {
254 - if (tmp->source_addr.sa.sa_family == AF_INET)
255 - tmp->source_addr.in.sin_port = htons(daemon->query_port);
256 + {
257 + tmp->edns_pktsz = daemon->edns_pktsz;
258 +
259 + if (!(tmp->flags & SERV_HAS_SOURCE))
260 + {
261 + if (tmp->source_addr.sa.sa_family == AF_INET)
262 + tmp->source_addr.in.sin_port = htons(daemon->query_port);
263 #ifdef HAVE_IPV6
264 - else if (tmp->source_addr.sa.sa_family == AF_INET6)
265 - tmp->source_addr.in6.sin6_port = htons(daemon->query_port);
266 + else if (tmp->source_addr.sa.sa_family == AF_INET6)
267 + tmp->source_addr.in6.sin6_port = htons(daemon->query_port);
268 #endif
269 - }
270 + }
271 + }
272 }
273
274 if (daemon->if_addrs)
275 diff --git a/src/rfc1035.c b/src/rfc1035.c
276 index 5828055caa5d..8b1709dd3495 100644
277 --- a/src/rfc1035.c
278 +++ b/src/rfc1035.c
279 @@ -552,7 +552,7 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned
280 return plen;
281 *p++ = 0; /* empty name */
282 PUTSHORT(T_OPT, p);
283 - PUTSHORT(daemon->edns_pktsz, p); /* max packet length */
284 + PUTSHORT(SAFE_PKTSZ, p); /* max packet length, this will be overwritten */
285 PUTSHORT(0, p); /* extended RCODE and version */
286 PUTSHORT(set_do ? 0x8000 : 0, p); /* DO flag */
287 lenp = p;
288 @@ -1537,7 +1537,6 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
289 unsigned short flag;
290 int q, ans, anscount = 0, addncount = 0;
291 int dryrun = 0, sec_reqd = 0, have_pseudoheader = 0;
292 - int is_sign;
293 struct crec *crecp;
294 int nxdomain = 0, auth = 1, trunc = 0, sec_data = 1;
295 struct mx_srv_record *rec;
296 @@ -1557,28 +1556,19 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
297 forward rather than answering from the cache, which doesn't include
298 security information, unless we're in DNSSEC validation mode. */
299
300 - if (find_pseudoheader(header, qlen, NULL, &pheader, &is_sign))
301 + if (find_pseudoheader(header, qlen, NULL, &pheader, NULL))
302 {
303 - unsigned short udpsz, flags;
304 - unsigned char *psave = pheader;
305 -
306 + unsigned short flags;
307 +
308 have_pseudoheader = 1;
309
310 - GETSHORT(udpsz, pheader);
311 - pheader += 2; /* ext_rcode */
312 + pheader += 4; /* udp size, ext_rcode */
313 GETSHORT(flags, pheader);
314
315 if ((sec_reqd = flags & 0x8000))
316 *do_bit = 1;/* do bit */
317 - *ad_reqd = 1;
318 -
319 - /* If our client is advertising a larger UDP packet size
320 - than we allow, trim it so that we don't get an overlarge
321 - response from upstream */
322 -
323 - if (!is_sign && (udpsz > daemon->edns_pktsz))
324 - PUTSHORT(daemon->edns_pktsz, psave);
325
326 + *ad_reqd = 1;
327 dryrun = 1;
328 }
329
330 --
331 2.1.0
332