Commit | Line | Data |
---|---|---|
dc0d77d7 CE |
1 | #include <sys/types.h> |
2 | #include <sys/socket.h> | |
3 | #include <unistd.h> | |
4 | #include "socket.h" | |
5 | #include "alloc.h" | |
6 | #include "error.h" | |
7 | #include "byte.h" | |
8 | #include "uint16.h" | |
9 | #include "dns.h" | |
10 | ||
11 | static int serverwantstcp(const char *buf,unsigned int len) | |
12 | { | |
13 | char out[12]; | |
14 | ||
15 | if (!dns_packet_copy(buf,len,0,out,12)) return 1; | |
16 | if (out[2] & 2) return 1; | |
17 | return 0; | |
18 | } | |
19 | ||
20 | static int serverfailed(const char *buf,unsigned int len) | |
21 | { | |
22 | char out[12]; | |
23 | unsigned int rcode; | |
24 | ||
25 | if (!dns_packet_copy(buf,len,0,out,12)) return 1; | |
26 | rcode = out[3]; | |
27 | rcode &= 15; | |
28 | if (rcode && (rcode != 3)) { errno = error_again; return 1; } | |
29 | return 0; | |
30 | } | |
31 | ||
32 | static int irrelevant(const struct dns_transmit *d,const char *buf,unsigned int len) | |
33 | { | |
34 | char out[12]; | |
35 | char *dn; | |
36 | unsigned int pos; | |
37 | ||
38 | pos = dns_packet_copy(buf,len,0,out,12); if (!pos) return 1; | |
39 | if (byte_diff(out,2,d->query + 2)) return 1; | |
40 | if (out[4] != 0) return 1; | |
41 | if (out[5] != 1) return 1; | |
42 | ||
43 | dn = 0; | |
44 | pos = dns_packet_getname(buf,len,pos,&dn); if (!pos) return 1; | |
45 | if (!dns_domain_equal(dn,d->query + 14)) { alloc_free(dn); return 1; } | |
46 | alloc_free(dn); | |
47 | ||
48 | pos = dns_packet_copy(buf,len,pos,out,4); if (!pos) return 1; | |
49 | if (byte_diff(out,2,d->qtype)) return 1; | |
50 | if (byte_diff(out + 2,2,DNS_C_IN)) return 1; | |
51 | ||
52 | return 0; | |
53 | } | |
54 | ||
55 | static void packetfree(struct dns_transmit *d) | |
56 | { | |
57 | if (!d->packet) return; | |
58 | alloc_free(d->packet); | |
59 | d->packet = 0; | |
60 | } | |
61 | ||
62 | static void queryfree(struct dns_transmit *d) | |
63 | { | |
64 | if (!d->query) return; | |
65 | alloc_free(d->query); | |
66 | d->query = 0; | |
67 | } | |
68 | ||
69 | static void socketfree(struct dns_transmit *d) | |
70 | { | |
71 | if (!d->s1) return; | |
72 | close(d->s1 - 1); | |
73 | d->s1 = 0; | |
74 | } | |
75 | ||
76 | void dns_transmit_free(struct dns_transmit *d) | |
77 | { | |
78 | queryfree(d); | |
79 | socketfree(d); | |
80 | packetfree(d); | |
81 | } | |
82 | ||
83 | static int randombind(struct dns_transmit *d) | |
84 | { | |
85 | int j; | |
86 | ||
87 | for (j = 0;j < 10;++j) | |
88 | if (socket_bind4(d->s1 - 1,d->localip,1025 + dns_random(64510)) == 0) | |
89 | return 0; | |
90 | if (socket_bind4(d->s1 - 1,d->localip,0) == 0) | |
91 | return 0; | |
92 | return -1; | |
93 | } | |
94 | ||
95 | static const int timeouts[4] = { 1, 3, 11, 45 }; | |
96 | ||
97 | static int thisudp(struct dns_transmit *d) | |
98 | { | |
99 | const char *ip; | |
100 | ||
101 | socketfree(d); | |
102 | ||
103 | while (d->udploop < 4) { | |
104 | for (;d->curserver < 16;++d->curserver) { | |
105 | ip = d->servers + 4 * d->curserver; | |
106 | if (byte_diff(ip,4,"\0\0\0\0")) { | |
107 | d->query[2] = dns_random(256); | |
108 | d->query[3] = dns_random(256); | |
109 | ||
110 | d->s1 = 1 + socket_udp(); | |
111 | if (!d->s1) { dns_transmit_free(d); return -1; } | |
112 | if (randombind(d) == -1) { dns_transmit_free(d); return -1; } | |
113 | ||
114 | if (socket_connect4(d->s1 - 1,ip,53) == 0) | |
115 | if (send(d->s1 - 1,d->query + 2,d->querylen - 2,0) == d->querylen - 2) { | |
116 | struct taia now; | |
117 | taia_now(&now); | |
118 | taia_uint(&d->deadline,timeouts[d->udploop]); | |
119 | taia_add(&d->deadline,&d->deadline,&now); | |
120 | d->tcpstate = 0; | |
121 | return 0; | |
122 | } | |
123 | ||
124 | socketfree(d); | |
125 | } | |
126 | } | |
127 | ||
128 | ++d->udploop; | |
129 | d->curserver = 0; | |
130 | } | |
131 | ||
132 | dns_transmit_free(d); return -1; | |
133 | } | |
134 | ||
135 | static int firstudp(struct dns_transmit *d) | |
136 | { | |
137 | d->curserver = 0; | |
138 | return thisudp(d); | |
139 | } | |
140 | ||
141 | static int nextudp(struct dns_transmit *d) | |
142 | { | |
143 | ++d->curserver; | |
144 | return thisudp(d); | |
145 | } | |
146 | ||
147 | static int thistcp(struct dns_transmit *d) | |
148 | { | |
149 | struct taia now; | |
150 | const char *ip; | |
151 | ||
152 | socketfree(d); | |
153 | packetfree(d); | |
154 | ||
155 | for (;d->curserver < 16;++d->curserver) { | |
156 | ip = d->servers + 4 * d->curserver; | |
157 | if (byte_diff(ip,4,"\0\0\0\0")) { | |
158 | d->query[2] = dns_random(256); | |
159 | d->query[3] = dns_random(256); | |
160 | ||
161 | d->s1 = 1 + socket_tcp(); | |
162 | if (!d->s1) { dns_transmit_free(d); return -1; } | |
163 | if (randombind(d) == -1) { dns_transmit_free(d); return -1; } | |
164 | ||
165 | taia_now(&now); | |
166 | taia_uint(&d->deadline,10); | |
167 | taia_add(&d->deadline,&d->deadline,&now); | |
168 | if (socket_connect4(d->s1 - 1,ip,53) == 0) { | |
169 | d->tcpstate = 2; | |
170 | return 0; | |
171 | } | |
172 | if ((errno == error_inprogress) || (errno == error_wouldblock)) { | |
173 | d->tcpstate = 1; | |
174 | return 0; | |
175 | } | |
176 | ||
177 | socketfree(d); | |
178 | } | |
179 | } | |
180 | ||
181 | dns_transmit_free(d); return -1; | |
182 | } | |
183 | ||
184 | static int firsttcp(struct dns_transmit *d) | |
185 | { | |
186 | d->curserver = 0; | |
187 | return thistcp(d); | |
188 | } | |
189 | ||
190 | static int nexttcp(struct dns_transmit *d) | |
191 | { | |
192 | ++d->curserver; | |
193 | return thistcp(d); | |
194 | } | |
195 | ||
196 | int dns_transmit_start(struct dns_transmit *d,const char servers[64],int flagrecursive,const char *q,const char qtype[2],const char localip[4]) | |
197 | { | |
198 | unsigned int len; | |
199 | ||
200 | dns_transmit_free(d); | |
201 | errno = error_io; | |
202 | ||
203 | len = dns_domain_length(q); | |
204 | d->querylen = len + 18; | |
205 | d->query = alloc(d->querylen); | |
206 | if (!d->query) return -1; | |
207 | ||
208 | uint16_pack_big(d->query,len + 16); | |
209 | byte_copy(d->query + 2,12,flagrecursive ? "\0\0\1\0\0\1\0\0\0\0\0\0" : "\0\0\0\0\0\1\0\0\0\0\0\0gcc-bug-workaround"); | |
210 | byte_copy(d->query + 14,len,q); | |
211 | byte_copy(d->query + 14 + len,2,qtype); | |
212 | byte_copy(d->query + 16 + len,2,DNS_C_IN); | |
213 | ||
214 | byte_copy(d->qtype,2,qtype); | |
215 | d->servers = servers; | |
216 | byte_copy(d->localip,4,localip); | |
217 | ||
218 | d->udploop = flagrecursive ? 1 : 0; | |
219 | ||
220 | if (len + 16 > 512) return firsttcp(d); | |
221 | return firstudp(d); | |
222 | } | |
223 | ||
224 | void dns_transmit_io(struct dns_transmit *d,iopause_fd *x,struct taia *deadline) | |
225 | { | |
226 | x->fd = d->s1 - 1; | |
227 | ||
228 | switch(d->tcpstate) { | |
229 | case 0: case 3: case 4: case 5: | |
230 | x->events = IOPAUSE_READ; | |
231 | break; | |
232 | case 1: case 2: | |
233 | x->events = IOPAUSE_WRITE; | |
234 | break; | |
235 | } | |
236 | ||
237 | if (taia_less(&d->deadline,deadline)) | |
238 | *deadline = d->deadline; | |
239 | } | |
240 | ||
241 | int dns_transmit_get(struct dns_transmit *d,const iopause_fd *x,const struct taia *when) | |
242 | { | |
243 | char udpbuf[513]; | |
244 | unsigned char ch; | |
245 | int r; | |
246 | int fd; | |
247 | ||
248 | errno = error_io; | |
249 | fd = d->s1 - 1; | |
250 | ||
251 | if (!x->revents) { | |
252 | if (taia_less(when,&d->deadline)) return 0; | |
253 | errno = error_timeout; | |
254 | if (d->tcpstate == 0) return nextudp(d); | |
255 | return nexttcp(d); | |
256 | } | |
257 | ||
258 | if (d->tcpstate == 0) { | |
259 | /* | |
260 | have attempted to send UDP query to each server udploop times | |
261 | have sent query to curserver on UDP socket s | |
262 | */ | |
263 | r = recv(fd,udpbuf,sizeof udpbuf,0); | |
264 | if (r <= 0) { | |
265 | if (errno == error_connrefused) if (d->udploop == 2) return 0; | |
266 | return nextudp(d); | |
267 | } | |
268 | if (r + 1 > sizeof udpbuf) return 0; | |
269 | ||
270 | if (irrelevant(d,udpbuf,r)) return 0; | |
271 | if (serverwantstcp(udpbuf,r)) return firsttcp(d); | |
272 | if (serverfailed(udpbuf,r)) { | |
273 | if (d->udploop == 2) return 0; | |
274 | return nextudp(d); | |
275 | } | |
276 | socketfree(d); | |
277 | ||
278 | d->packetlen = r; | |
279 | d->packet = alloc(d->packetlen); | |
280 | if (!d->packet) { dns_transmit_free(d); return -1; } | |
281 | byte_copy(d->packet,d->packetlen,udpbuf); | |
282 | queryfree(d); | |
283 | return 1; | |
284 | } | |
285 | ||
286 | if (d->tcpstate == 1) { | |
287 | /* | |
288 | have sent connection attempt to curserver on TCP socket s | |
289 | pos not defined | |
290 | */ | |
291 | if (!socket_connected(fd)) return nexttcp(d); | |
292 | d->pos = 0; | |
293 | d->tcpstate = 2; | |
294 | return 0; | |
295 | } | |
296 | ||
297 | if (d->tcpstate == 2) { | |
298 | /* | |
299 | have connection to curserver on TCP socket s | |
300 | have sent pos bytes of query | |
301 | */ | |
302 | r = write(fd,d->query + d->pos,d->querylen - d->pos); | |
303 | if (r <= 0) return nexttcp(d); | |
304 | d->pos += r; | |
305 | if (d->pos == d->querylen) { | |
306 | struct taia now; | |
307 | taia_now(&now); | |
308 | taia_uint(&d->deadline,10); | |
309 | taia_add(&d->deadline,&d->deadline,&now); | |
310 | d->tcpstate = 3; | |
311 | } | |
312 | return 0; | |
313 | } | |
314 | ||
315 | if (d->tcpstate == 3) { | |
316 | /* | |
317 | have sent entire query to curserver on TCP socket s | |
318 | pos not defined | |
319 | */ | |
320 | r = read(fd,&ch,1); | |
321 | if (r <= 0) return nexttcp(d); | |
322 | d->packetlen = ch; | |
323 | d->tcpstate = 4; | |
324 | return 0; | |
325 | } | |
326 | ||
327 | if (d->tcpstate == 4) { | |
328 | /* | |
329 | have sent entire query to curserver on TCP socket s | |
330 | pos not defined | |
331 | have received one byte of packet length into packetlen | |
332 | */ | |
333 | r = read(fd,&ch,1); | |
334 | if (r <= 0) return nexttcp(d); | |
335 | d->packetlen <<= 8; | |
336 | d->packetlen += ch; | |
337 | d->tcpstate = 5; | |
338 | d->pos = 0; | |
339 | d->packet = alloc(d->packetlen); | |
340 | if (!d->packet) { dns_transmit_free(d); return -1; } | |
341 | return 0; | |
342 | } | |
343 | ||
344 | if (d->tcpstate == 5) { | |
345 | /* | |
346 | have sent entire query to curserver on TCP socket s | |
347 | have received entire packet length into packetlen | |
348 | packet is allocated | |
349 | have received pos bytes of packet | |
350 | */ | |
351 | r = read(fd,d->packet + d->pos,d->packetlen - d->pos); | |
352 | if (r <= 0) return nexttcp(d); | |
353 | d->pos += r; | |
354 | if (d->pos < d->packetlen) return 0; | |
355 | ||
356 | socketfree(d); | |
357 | if (irrelevant(d,d->packet,d->packetlen)) return nexttcp(d); | |
358 | if (serverwantstcp(d->packet,d->packetlen)) return nexttcp(d); | |
359 | if (serverfailed(d->packet,d->packetlen)) return nexttcp(d); | |
360 | ||
361 | queryfree(d); | |
362 | return 1; | |
363 | } | |
364 | ||
365 | return 0; | |
366 | } |