]>
Commit | Line | Data |
---|---|---|
9e4abcb5 SK |
1 | /* dnsmasq is Copyright (c) 2000 - 2003 Simon Kelley |
2 | ||
3 | This program is free software; you can redistribute it and/or modify | |
4 | it under the terms of the GNU General Public License as published by | |
5 | the Free Software Foundation; version 2 dated June, 1991. | |
6 | ||
7 | This program is distributed in the hope that it will be useful, | |
8 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
10 | GNU General Public License for more details. | |
11 | */ | |
12 | ||
13 | /* Author's email: simon@thekelleys.org.uk */ | |
14 | ||
15 | #include "dnsmasq.h" | |
16 | ||
17 | static struct frec *frec_list; | |
18 | ||
19 | static struct frec *get_new_frec(time_t now); | |
20 | static struct frec *lookup_frec(unsigned short id); | |
21 | static struct frec *lookup_frec_by_sender(unsigned short id, | |
22 | union mysockaddr *addr); | |
23 | static unsigned short get_id(void); | |
24 | ||
25 | /* May be called more than once. */ | |
26 | void forward_init(int first) | |
27 | { | |
28 | struct frec *f; | |
29 | ||
30 | if (first) | |
31 | frec_list = NULL; | |
32 | for (f = frec_list; f; f = f->next) | |
33 | f->new_id = 0; | |
34 | } | |
35 | ||
36 | /* delete all forward records recieved from socket fd */ | |
37 | void reap_forward(int fd) | |
38 | { | |
39 | struct frec *f; | |
40 | ||
41 | for (f = frec_list; f; f = f->next) | |
42 | if (f->fd == fd) | |
43 | f->new_id = 0; | |
44 | } | |
45 | ||
46 | /* returns new last_server */ | |
47 | struct server *forward_query(int udpfd, union mysockaddr *udpaddr, HEADER *header, | |
48 | int plen, unsigned int options, char *dnamebuff, | |
49 | struct server *servers, struct server *last_server, | |
50 | time_t now, unsigned long local_ttl) | |
51 | { | |
52 | struct frec *forward; | |
53 | char *domain = NULL; | |
54 | int type = 0; | |
55 | struct server *serv; | |
56 | struct all_addr *addrp = NULL; | |
57 | unsigned short flags = 0; | |
58 | unsigned short gotname = extract_request(header, (unsigned int)plen, dnamebuff); | |
59 | ||
60 | /* may be recursion not speced or no servers available. */ | |
61 | if (!header->rd || !servers) | |
62 | forward = NULL; | |
63 | else if ((forward = lookup_frec_by_sender(ntohs(header->id), udpaddr))) | |
64 | { | |
65 | /* retry on existing query, send to next server */ | |
66 | domain = forward->sentto->domain; | |
67 | type = forward->sentto->flags & SERV_TYPE; | |
68 | if (!(forward->sentto = forward->sentto->next)) | |
69 | forward->sentto = servers; /* at end of list, recycle */ | |
70 | header->id = htons(forward->new_id); | |
71 | } | |
72 | else | |
73 | { | |
74 | if (gotname) | |
75 | { | |
76 | /* If the query ends in the domain in one of our servers, set | |
77 | domain to point to that name. We find the largest match to allow both | |
78 | domain.org and sub.domain.org to exist. */ | |
79 | ||
80 | unsigned int namelen = strlen(dnamebuff); | |
81 | unsigned int matchlen = 0; | |
82 | ||
83 | for (serv=servers; serv; serv=serv->next) | |
84 | /* domain matches take priority over NODOTS matches */ | |
85 | if ((serv->flags & SERV_FOR_NODOTS) && type != SERV_HAS_DOMAIN && !strchr(dnamebuff, '.')) | |
86 | { | |
87 | if (serv->flags & SERV_LITERAL_ADDRESS) | |
88 | { | |
89 | /* flags gets set if server is in fact an answer */ | |
90 | unsigned short sflag = serv->addr.sa.sa_family == AF_INET ? F_IPV4 : F_IPV6; | |
91 | if (sflag & gotname) /* only OK if addrfamily == query */ | |
92 | { | |
93 | type = SERV_FOR_NODOTS; | |
94 | flags = sflag; | |
95 | if (serv->addr.sa.sa_family == AF_INET) | |
96 | addrp = (struct all_addr *)&serv->addr.in.sin_addr; | |
97 | #ifdef HAVE_IPV6 | |
98 | else | |
99 | addrp = (struct all_addr *)&serv->addr.in6.sin6_addr; | |
100 | #endif | |
101 | } | |
102 | } | |
103 | else | |
104 | flags = 0; | |
105 | } | |
106 | else if (serv->flags & SERV_HAS_DOMAIN) | |
107 | { | |
108 | unsigned int domainlen = strlen(serv->domain); | |
109 | if (namelen >= domainlen && | |
110 | hostname_isequal(dnamebuff + namelen - domainlen, serv->domain) && | |
111 | domainlen > matchlen) | |
112 | { | |
113 | if (serv->flags & SERV_LITERAL_ADDRESS) | |
114 | { /* flags gets set if server is in fact an answer */ | |
115 | unsigned short sflag = serv->addr.sa.sa_family == AF_INET ? F_IPV4 : F_IPV6; | |
116 | if (sflag & gotname) /* only OK if addrfamily == query */ | |
117 | { | |
118 | type = SERV_HAS_DOMAIN; | |
119 | flags = sflag; | |
120 | domain = serv->domain; | |
121 | matchlen = domainlen; | |
122 | if (serv->addr.sa.sa_family == AF_INET) | |
123 | addrp = (struct all_addr *)&serv->addr.in.sin_addr; | |
124 | #ifdef HAVE_IPV6 | |
125 | else | |
126 | addrp = (struct all_addr *)&serv->addr.in6.sin6_addr; | |
127 | #endif | |
128 | } | |
129 | } | |
130 | else | |
131 | { | |
132 | flags = 0; /* may be better match from previous literal */ | |
133 | domain = serv->domain; | |
134 | matchlen = domainlen; | |
135 | } | |
136 | } | |
137 | } | |
138 | } | |
139 | ||
140 | if (flags) /* flags set here means a literal found */ | |
141 | log_query(F_CONFIG | F_FORWARD | flags, dnamebuff, addrp); | |
142 | else | |
143 | { | |
144 | /* we may by policy not forward names without a domain part */ | |
145 | if (gotname && (options & OPT_NODOTS_LOCAL) && !strchr(dnamebuff, '.')) | |
146 | flags = F_NXDOMAIN; | |
147 | else if (!(forward = get_new_frec(now))) | |
148 | /* table full - server failure. */ | |
149 | flags = F_NEG; | |
150 | } | |
151 | ||
152 | if (forward) | |
153 | { | |
154 | /* In strict_order mode, or when using domain specific servers | |
155 | always try servers in the order specified in resolv.conf, | |
156 | otherwise, use the one last known to work. */ | |
157 | ||
158 | if (type != 0 || (options & OPT_ORDER)) | |
159 | forward->sentto = servers; | |
160 | else | |
161 | forward->sentto = last_server; | |
162 | ||
163 | forward->source = *udpaddr; | |
164 | forward->new_id = get_id(); | |
165 | forward->fd = udpfd; | |
166 | forward->orig_id = ntohs(header->id); | |
167 | header->id = htons(forward->new_id); | |
168 | } | |
169 | } | |
170 | ||
171 | /* check for send errors here (no route to host) | |
172 | if we fail to send to all nameservers, send back an error | |
173 | packet straight away (helps modem users when offline) */ | |
174 | ||
175 | if (!flags && forward) | |
176 | { | |
177 | struct server *firstsentto = forward->sentto; | |
178 | ||
179 | while (1) | |
180 | { | |
181 | int logflags = 0; | |
182 | ||
183 | if (forward->sentto->addr.sa.sa_family == AF_INET) | |
184 | { | |
185 | logflags = F_SERVER | F_IPV4 | F_FORWARD; | |
186 | addrp = (struct all_addr *)&forward->sentto->addr.in.sin_addr; | |
187 | } | |
188 | #ifdef HAVE_IPV6 | |
189 | else | |
190 | { | |
191 | logflags = F_SERVER | F_IPV6 | F_FORWARD; | |
192 | addrp = (struct all_addr *)&forward->sentto->addr.in6.sin6_addr; | |
193 | } | |
194 | #endif | |
195 | /* only send to servers dealing with our domain. | |
196 | domain may be NULL, in which case server->domain | |
197 | must be NULL also. */ | |
198 | ||
199 | if (type == (forward->sentto->flags & SERV_TYPE) && | |
200 | (type != SERV_HAS_DOMAIN || hostname_isequal(domain, forward->sentto->domain))) | |
201 | { | |
202 | if (forward->sentto->flags & SERV_NO_ADDR) | |
203 | flags = F_NOERR; /* NULL servers are OK. */ | |
204 | else if (!(forward->sentto->flags & SERV_LITERAL_ADDRESS) && | |
205 | sendto(forward->sentto->sfd->fd, (char *)header, plen, 0, | |
206 | &forward->sentto->addr.sa, | |
207 | sa_len(&forward->sentto->addr)) != -1) | |
208 | { | |
209 | log_query(logflags, gotname ? dnamebuff : "query", addrp); | |
210 | /* for no-domain, don't update last_server */ | |
211 | return domain ? last_server : (forward->sentto->next ? forward->sentto->next : servers); | |
212 | } | |
213 | } | |
214 | ||
215 | if (!(forward->sentto = forward->sentto->next)) | |
216 | forward->sentto = servers; | |
217 | ||
218 | /* check if we tried all without success */ | |
219 | if (forward->sentto == firstsentto) | |
220 | break; | |
221 | } | |
222 | ||
223 | /* could not send on, prepare to return */ | |
224 | header->id = htons(forward->orig_id); | |
225 | forward->new_id = 0; /* cancel */ | |
226 | } | |
227 | ||
228 | /* could not send on, return empty answer or address if known for whole domain */ | |
229 | plen = setup_reply(header, (unsigned int)plen, addrp, flags, local_ttl); | |
230 | sendto(udpfd, (char *)header, plen, 0, &udpaddr->sa, sa_len(udpaddr)); | |
231 | ||
232 | if (flags & (F_NOERR | F_NXDOMAIN)) | |
233 | log_query(F_CONFIG | F_FORWARD | F_NEG | gotname | (flags & F_NXDOMAIN), dnamebuff, NULL); | |
234 | ||
235 | return last_server; | |
236 | } | |
237 | ||
238 | /* returns new last_server */ | |
239 | struct server *reply_query(int fd, int options, char *packet, time_t now, | |
240 | char *dnamebuff, struct server *last_server, struct bogus_addr *bogus_nxdomain) | |
241 | { | |
242 | /* packet from peer server, extract data for cache, and send to | |
243 | original requester */ | |
244 | struct frec *forward; | |
245 | HEADER *header; | |
246 | int n = recv(fd, packet, PACKETSZ, 0); | |
247 | ||
248 | header = (HEADER *)packet; | |
249 | if (n >= (int)sizeof(HEADER) && header->qr) | |
250 | { | |
251 | if ((forward = lookup_frec(ntohs(header->id)))) | |
252 | { | |
253 | if (header->rcode == NOERROR || header->rcode == NXDOMAIN) | |
254 | { | |
255 | if (!forward->sentto->domain) | |
256 | last_server = forward->sentto; /* known good */ | |
257 | if (header->opcode == QUERY) | |
258 | { | |
259 | if (!(bogus_nxdomain && | |
260 | header->rcode == NOERROR && | |
261 | check_for_bogus_wildcard(header, (unsigned int)n, dnamebuff, bogus_nxdomain, now))) | |
262 | { | |
263 | if (header->rcode == NOERROR && ntohs(header->ancount) != 0) | |
264 | extract_addresses(header, (unsigned int)n, dnamebuff, now); | |
265 | else if (!(options & OPT_NO_NEG)) | |
266 | extract_neg_addrs(header, (unsigned int)n, dnamebuff, now); | |
267 | } | |
268 | } | |
269 | } | |
270 | header->id = htons(forward->orig_id); | |
271 | /* There's no point returning an upstream reply marked as truncated, | |
272 | since that will prod the resolver into moving to TCP - which we | |
273 | don't support. */ | |
274 | header->tc = 0; /* goodbye truncate */ | |
275 | sendto(forward->fd, packet, n, 0, | |
276 | &forward->source.sa, sa_len(&forward->source)); | |
277 | forward->new_id = 0; /* cancel */ | |
278 | } | |
279 | } | |
280 | ||
281 | return last_server; | |
282 | } | |
283 | ||
284 | static struct frec *get_new_frec(time_t now) | |
285 | { | |
286 | struct frec *f = frec_list, *oldest = NULL; | |
287 | time_t oldtime = now; | |
288 | int count = 0; | |
289 | static time_t warntime = 0; | |
290 | ||
291 | while (f) | |
292 | { | |
293 | if (f->new_id == 0) | |
294 | { | |
295 | f->time = now; | |
296 | return f; | |
297 | } | |
298 | ||
299 | if (difftime(f->time, oldtime) <= 0) | |
300 | { | |
301 | oldtime = f->time; | |
302 | oldest = f; | |
303 | } | |
304 | ||
305 | count++; | |
306 | f = f->next; | |
307 | } | |
308 | ||
309 | /* can't find empty one, use oldest if there is one | |
310 | and it's older than timeout */ | |
311 | if (oldest && difftime(now, oldtime) > TIMEOUT) | |
312 | { | |
313 | oldest->time = now; | |
314 | return oldest; | |
315 | } | |
316 | ||
317 | if (count > FTABSIZ) | |
318 | { /* limit logging rate so syslog isn't DOSed either */ | |
319 | if (!warntime || difftime(now, warntime) > LOGRATE) | |
320 | { | |
321 | warntime = now; | |
322 | syslog(LOG_WARNING, "forwarding table overflow: check for server loops."); | |
323 | } | |
324 | return NULL; | |
325 | } | |
326 | ||
327 | if ((f = (struct frec *)malloc(sizeof(struct frec)))) | |
328 | { | |
329 | f->next = frec_list; | |
330 | f->time = now; | |
331 | frec_list = f; | |
332 | } | |
333 | return f; /* OK if malloc fails and this is NULL */ | |
334 | } | |
335 | ||
336 | static struct frec *lookup_frec(unsigned short id) | |
337 | { | |
338 | struct frec *f; | |
339 | ||
340 | for(f = frec_list; f; f = f->next) | |
341 | if (f->new_id == id) | |
342 | return f; | |
343 | ||
344 | return NULL; | |
345 | } | |
346 | ||
347 | static struct frec *lookup_frec_by_sender(unsigned short id, | |
348 | union mysockaddr *addr) | |
349 | { | |
350 | struct frec *f; | |
351 | ||
352 | for(f = frec_list; f; f = f->next) | |
353 | if (f->new_id && | |
354 | f->orig_id == id && | |
355 | sockaddr_isequal(&f->source, addr)) | |
356 | return f; | |
357 | ||
358 | return NULL; | |
359 | } | |
360 | ||
361 | ||
362 | /* return unique random ids between 1 and 65535 */ | |
363 | static unsigned short get_id(void) | |
364 | { | |
365 | unsigned short ret = 0; | |
366 | ||
367 | while (ret == 0) | |
368 | { | |
369 | ret = rand16(); | |
370 | ||
371 | /* scrap ids already in use */ | |
372 | if ((ret != 0) && lookup_frec(ret)) | |
373 | ret = 0; | |
374 | } | |
375 | ||
376 | return ret; | |
377 | } | |
378 | ||
379 | ||
380 | ||
381 | ||
382 |