]>
Commit | Line | Data |
---|---|---|
7cbd5332 MF |
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] Handle UDP packet loss when fragmentation of large packets | |
5 | 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 af2b22c..d8fc57a 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 8def6f2..f75fe9d 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 824a860..ab16f79 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 a9e1215..e91d7c2 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 a8e403c..592243f 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 992f023..a1d90c8 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 f91cfbb..c7add88 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 5828055..8b1709d 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 | 1.7.10.4 |