clarify per-query options vs. per-upstream options
Sending DNS cookies was overwriting any existing options (DNS OPT) in
the outbound query.
Also, DNS cookies may not be the only option that gets set
per-upstream (instead of per-query).
This changeset establishes a set of per-query options (established at
the time of the query), and a buffer of additional space for adding
options based on the upstream is in use.
The size of this buffer is defined at configure time (defaults to 3000
octets).
Just before a query is sent out, we add the per-upstream options to
the query.
Note: we're also standardizing the query in tls too, even though we're
not sending any upstream options in that case at the moment
(edns_cookies are much weaker than TLS itself)
Daniel Kahn Gillmor
8 years ago
407 | 407 | AC_DEFINE_UNQUOTED([EDNS_COOKIE_OPCODE], [10], [The edns cookie option code.]) |
408 | 408 | AC_DEFINE_UNQUOTED([EDNS_COOKIE_ROLLOVER_TIME], [(24 * 60 * 60)], [How often the edns client cookie is refreshed.]) |
409 | 409 | |
410 | AC_DEFINE_UNQUOTED([MAXIMUM_UPSTREAM_OPTION_SPACE], [3000], [limit for dynamically-generated DNS options]) | |
411 | ||
410 | 412 | my_with_libunbound=1 |
411 | 413 | AC_ARG_ENABLE(stub-only, AC_HELP_STRING([--enable-stub-only], [Restricts resolution modes to STUB (which will be the default mode). Removes the libunbound dependency.])) |
412 | 414 | case "$enable_stub_only" in |
112 | 112 | ? edns_maximum_udp_payload_size : 1432; |
113 | 113 | net_req->write_queue_tail = NULL; |
114 | 114 | net_req->response_len = 0; |
115 | net_req->base_query_option_sz = opt_options_size; | |
115 | 116 | |
116 | 117 | net_req->wire_data_sz = wire_data_sz; |
117 | 118 | if (max_query_sz) { |
183 | 184 | return r; |
184 | 185 | } |
185 | 186 | |
187 | /* req->opt + 9 is the length; req->opt + 11 is the start of the | |
188 | options. | |
189 | ||
190 | clear_upstream_options() goes back to the per-query options. | |
191 | */ | |
192 | ||
193 | void | |
194 | _getdns_network_req_clear_upstream_options(getdns_network_req * req) | |
195 | { | |
196 | size_t pktlen; | |
197 | if (req->opt) { | |
198 | gldns_write_uint16(req->opt + 9, req->base_query_option_sz); | |
199 | req->response = req->opt + 11 + req->base_query_option_sz; | |
200 | pktlen = req->response - req->query; | |
201 | gldns_write_uint16(req->query - 2, pktlen); | |
202 | } | |
203 | } | |
204 | ||
205 | /* add_upstream_option appends an option that is derived at send time. | |
206 | (you can send data as NULL and it will fill with all zeros) */ | |
207 | getdns_return_t | |
208 | _getdns_network_req_add_upstream_option(getdns_network_req * req, uint16_t code, uint16_t sz, const void* data) | |
209 | { | |
210 | uint16_t oldlen; | |
211 | uint32_t newlen; | |
212 | uint32_t pktlen; | |
213 | size_t cur_upstream_option_sz; | |
214 | ||
215 | /* if no options are set, we can't add upstream options */ | |
216 | if (!req->opt) | |
217 | return GETDNS_RETURN_GENERIC_ERROR; | |
218 | ||
219 | /* if TCP, no overflow allowed for length field | |
220 | https://tools.ietf.org/html/rfc1035#section-4.2.2 */ | |
221 | pktlen = req->response - req->query; | |
222 | pktlen += 4 + sz; | |
223 | if (pktlen > UINT16_MAX) | |
224 | return GETDNS_RETURN_GENERIC_ERROR; | |
225 | ||
226 | /* no overflow allowed for OPT size either (maybe this is overkill | |
227 | given the above check?) */ | |
228 | oldlen = gldns_read_uint16(req->opt + 9); | |
229 | newlen = oldlen + 4 + sz; | |
230 | if (newlen > UINT16_MAX) | |
231 | return GETDNS_RETURN_GENERIC_ERROR; | |
232 | ||
233 | /* avoid overflowing MAXIMUM_UPSTREAM_OPTION_SPACE */ | |
234 | cur_upstream_option_sz = (size_t)oldlen - req->base_query_option_sz; | |
235 | if (cur_upstream_option_sz + 4 + sz > MAXIMUM_UPSTREAM_OPTION_SPACE) | |
236 | return GETDNS_RETURN_GENERIC_ERROR; | |
237 | ||
238 | /* actually add the option: */ | |
239 | gldns_write_uint16(req->opt + 11 + oldlen, code); | |
240 | gldns_write_uint16(req->opt + 11 + oldlen + 2, sz); | |
241 | if (data != NULL) | |
242 | memcpy(req->opt + 11 + oldlen + 4, data, sz); | |
243 | else | |
244 | memset(req->opt + 11 + oldlen + 4, 0, sz); | |
245 | gldns_write_uint16(req->opt + 9, newlen); | |
246 | ||
247 | /* the response should start right after the options end: */ | |
248 | req->response = req->opt + 11 + newlen; | |
249 | ||
250 | /* for TCP, adjust the size of the wire format itself: */ | |
251 | gldns_write_uint16(req->query - 2, pktlen); | |
252 | ||
253 | return GETDNS_RETURN_GOOD; | |
254 | } | |
255 | ||
186 | 256 | void |
187 | 257 | _getdns_dns_req_free(getdns_dns_req * req) |
188 | 258 | { |
322 | 392 | max_query_sz = ( GLDNS_HEADER_SIZE |
323 | 393 | + strlen(name) + 1 + 4 /* dname always smaller then strlen(name) + 1 */ |
324 | 394 | + 12 + opt_options_size /* space needed for OPT (if needed) */ |
325 | + ( !edns_cookies ? 0 | |
326 | : 2 /* EDNS0 Option Code */ | |
327 | + 2 /* Option length = 8 + 16 = 24 */ | |
328 | + 8 /* client cookie */ | |
329 | + 16 /* server cookie */ | |
330 | ) | |
395 | + MAXIMUM_UPSTREAM_OPTION_SPACE | |
331 | 396 | /* TODO: TSIG */ |
332 | 397 | + 7) / 8 * 8; |
333 | 398 | } |
45 | 45 | #include "util-internal.h" |
46 | 46 | #include "general.h" |
47 | 47 | |
48 | #define STUB_OUT_OF_OPTIONS -5 /* upstream options exceeded MAXIMUM_UPSTREAM_OPTION_SPACE */ | |
48 | 49 | #define STUB_TLS_SETUP_ERROR -4 |
49 | 50 | #define STUB_TCP_AGAIN -3 |
50 | 51 | #define STUB_TCP_ERROR -2 |
128 | 129 | cookie[i % 8] ^= md_value[i]; |
129 | 130 | } |
130 | 131 | |
131 | static uint8_t * | |
132 | attach_edns_cookie(getdns_upstream *upstream, uint8_t *opt) | |
133 | { | |
132 | static getdns_return_t | |
133 | attach_edns_cookie(getdns_network_req *req) | |
134 | { | |
135 | getdns_upstream *upstream = req->upstream; | |
136 | uint16_t sz; | |
137 | void* val; | |
138 | uint8_t buf[8 + 32]; /* server cookies can be no larger than 32 bytes */ | |
134 | 139 | rollover_secret(); |
135 | 140 | |
136 | 141 | if (!upstream->has_client_cookie) { |
138 | 143 | upstream->secret = secret; |
139 | 144 | upstream->has_client_cookie = 1; |
140 | 145 | |
141 | gldns_write_uint16(opt + 9, 12); /* rdata len */ | |
142 | gldns_write_uint16(opt + 11, EDNS_COOKIE_OPCODE); | |
143 | gldns_write_uint16(opt + 13, 8); /* opt len */ | |
144 | memcpy(opt + 15, upstream->client_cookie, 8); | |
145 | return opt + 23; | |
146 | ||
146 | sz = 8; | |
147 | val = upstream->client_cookie; | |
147 | 148 | } else if (upstream->secret != secret) { |
148 | 149 | memcpy( upstream->prev_client_cookie |
149 | 150 | , upstream->client_cookie, 8); |
151 | 152 | calc_new_cookie(upstream, upstream->client_cookie); |
152 | 153 | upstream->secret = secret; |
153 | 154 | |
154 | gldns_write_uint16(opt + 9, 12); /* rdata len */ | |
155 | gldns_write_uint16(opt + 11, EDNS_COOKIE_OPCODE); | |
156 | gldns_write_uint16(opt + 13, 8); /* opt len */ | |
157 | memcpy(opt + 15, upstream->client_cookie, 8); | |
158 | return opt + 23; | |
159 | ||
155 | sz = 8; | |
156 | val = upstream->client_cookie; | |
160 | 157 | } else if (!upstream->has_server_cookie) { |
161 | gldns_write_uint16(opt + 9, 12); /* rdata len */ | |
162 | gldns_write_uint16(opt + 11, EDNS_COOKIE_OPCODE); | |
163 | gldns_write_uint16(opt + 13, 8); /* opt len */ | |
164 | memcpy(opt + 15, upstream->client_cookie, 8); | |
165 | return opt + 23; | |
158 | sz = 8; | |
159 | val = upstream->client_cookie; | |
166 | 160 | } else { |
167 | gldns_write_uint16( opt + 9, 12 /* rdata len */ | |
168 | + upstream->server_cookie_len); | |
169 | gldns_write_uint16(opt + 11, EDNS_COOKIE_OPCODE); | |
170 | gldns_write_uint16(opt + 13, 8 /* opt len */ | |
171 | + upstream->server_cookie_len); | |
172 | memcpy(opt + 15, upstream->client_cookie, 8); | |
173 | memcpy(opt + 23, upstream->server_cookie | |
174 | , upstream->server_cookie_len); | |
175 | return opt + 23+ upstream->server_cookie_len; | |
176 | } | |
161 | sz = 8 + upstream->server_cookie_len; | |
162 | memcpy(buf, upstream->client_cookie, 8); | |
163 | memcpy(buf+8, upstream->server_cookie, upstream->server_cookie_len); | |
164 | val = buf; | |
165 | } | |
166 | return _getdns_network_req_add_upstream_option(req, EDNS_COOKIE_OPCODE, sz, val); | |
167 | ||
177 | 168 | } |
178 | 169 | |
179 | 170 | static int |
680 | 671 | stub_tcp_write(int fd, getdns_tcp_state *tcp, getdns_network_req *netreq) |
681 | 672 | { |
682 | 673 | |
683 | size_t pkt_len = netreq->response - netreq->query; | |
674 | size_t pkt_len; | |
684 | 675 | ssize_t written; |
685 | 676 | uint16_t query_id; |
686 | 677 | intptr_t query_id_intptr; |
703 | 694 | |
704 | 695 | GLDNS_ID_SET(netreq->query, query_id); |
705 | 696 | if (netreq->opt) { |
697 | _getdns_network_req_clear_upstream_options(netreq); | |
706 | 698 | /* no limits on the max udp payload size with tcp */ |
707 | 699 | gldns_write_uint16(netreq->opt + 3, 65535); |
708 | 700 | |
709 | if (netreq->owner->edns_cookies) { | |
710 | netreq->response = attach_edns_cookie( | |
711 | netreq->upstream, netreq->opt); | |
712 | pkt_len = netreq->response - netreq->query; | |
713 | gldns_write_uint16(netreq->query - 2, pkt_len); | |
714 | } | |
715 | } | |
701 | if (netreq->owner->edns_cookies) | |
702 | if (attach_edns_cookie(netreq)) | |
703 | return STUB_OUT_OF_OPTIONS; | |
704 | } | |
705 | pkt_len = netreq->response - netreq->query; | |
716 | 706 | /* We have an initialized packet buffer. |
717 | 707 | * Lets see how much of it we can write |
718 | 708 | */ |
1125 | 1115 | stub_tls_write(getdns_upstream *upstream, getdns_tcp_state *tcp, |
1126 | 1116 | getdns_network_req *netreq) |
1127 | 1117 | { |
1128 | size_t pkt_len = netreq->response - netreq->query; | |
1118 | size_t pkt_len; | |
1129 | 1119 | ssize_t written; |
1130 | 1120 | uint16_t query_id; |
1131 | 1121 | intptr_t query_id_intptr; |
1155 | 1145 | &netreq->upstream->netreq_by_query_id, &netreq->node)); |
1156 | 1146 | |
1157 | 1147 | GLDNS_ID_SET(netreq->query, query_id); |
1158 | if (netreq->opt) | |
1148 | if (netreq->opt) { | |
1149 | _getdns_network_req_clear_upstream_options(netreq); | |
1159 | 1150 | /* no limits on the max udp payload size with tcp */ |
1160 | 1151 | gldns_write_uint16(netreq->opt + 3, 65535); |
1161 | ||
1152 | /* we do not edns_cookie over TLS, since TLS | |
1153 | * provides stronger guarantees than cookies | |
1154 | * already */ | |
1155 | } | |
1156 | ||
1157 | pkt_len = netreq->response - netreq->query; | |
1162 | 1158 | /* We have an initialized packet buffer. |
1163 | 1159 | * Lets see how much of it we can write */ |
1164 | 1160 | |
1247 | 1243 | DEBUG_STUB("%s\n", __FUNCTION__); |
1248 | 1244 | getdns_network_req *netreq = (getdns_network_req *)userarg; |
1249 | 1245 | getdns_dns_req *dnsreq = netreq->owner; |
1250 | size_t pkt_len = netreq->response - netreq->query; | |
1246 | size_t pkt_len; | |
1251 | 1247 | |
1252 | 1248 | GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event); |
1253 | 1249 | |
1254 | 1250 | netreq->query_id = arc4random(); |
1255 | 1251 | GLDNS_ID_SET(netreq->query, netreq->query_id); |
1256 | 1252 | if (netreq->opt) { |
1253 | _getdns_network_req_clear_upstream_options(netreq); | |
1257 | 1254 | if (netreq->edns_maximum_udp_payload_size == -1) |
1258 | 1255 | gldns_write_uint16(netreq->opt + 3, |
1259 | 1256 | ( netreq->max_udp_payload_size = |
1260 | 1257 | netreq->upstream->addr.ss_family == AF_INET6 |
1261 | 1258 | ? 1232 : 1432)); |
1262 | if (netreq->owner->edns_cookies) { | |
1263 | netreq->response = attach_edns_cookie( | |
1264 | netreq->upstream, netreq->opt); | |
1265 | pkt_len = netreq->response - netreq->query; | |
1266 | } | |
1267 | } | |
1268 | ||
1259 | if (netreq->owner->edns_cookies) | |
1260 | if (attach_edns_cookie(netreq)) | |
1261 | return; /* too many upstream options */ | |
1262 | } | |
1263 | pkt_len = netreq->response - netreq->query; | |
1269 | 1264 | if ((ssize_t)pkt_len != sendto(netreq->fd, netreq->query, pkt_len, 0, |
1270 | 1265 | (struct sockaddr *)&netreq->upstream->addr, |
1271 | 1266 | netreq->upstream->addr_len)) { |
231 | 231 | */ |
232 | 232 | uint8_t *query; |
233 | 233 | uint8_t *opt; /* offset of OPT RR in query */ |
234 | ||
235 | /* each network_req has a set of base options that are | |
236 | * specific to the query, which are static and included when | |
237 | * the network_req is created. When the query is sent out to | |
238 | * a given upstream, some additional options are added that | |
239 | * are specific to the upstream. There can be at most | |
240 | * GETDNS_MAXIMUM_UPSTREAM_OPTION_SPACE bytes of | |
241 | * upstream-specific options. | |
242 | ||
243 | * use _getdns_network_req_clear_upstream_options() and | |
244 | * _getdns_network_req_add_upstream_option() to fiddle with the | |
245 | */ | |
246 | size_t base_query_option_sz; | |
234 | 247 | size_t response_len; |
235 | 248 | uint8_t *response; |
236 | 249 | size_t wire_data_sz; |
343 | 356 | |
344 | 357 | void _getdns_dns_req_free(getdns_dns_req * req); |
345 | 358 | |
359 | /* network request utils */ | |
360 | getdns_return_t _getdns_network_req_add_upstream_option(getdns_network_req * req, | |
361 | uint16_t code, uint16_t sz, const void* data); | |
362 | void _getdns_network_req_clear_upstream_options(getdns_network_req * req); | |
363 | ||
346 | 364 | #endif |
347 | 365 | /* types-internal.h */ |