/src/h2o/lib/handler/connect.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2021 Fastly Inc. |
3 | | * |
4 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | | * of this software and associated documentation files (the "Software"), to |
6 | | * deal in the Software without restriction, including without limitation the |
7 | | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
8 | | * sell copies of the Software, and to permit persons to whom the Software is |
9 | | * furnished to do so, subject to the following conditions: |
10 | | * |
11 | | * The above copyright notice and this permission notice shall be included in |
12 | | * all copies or substantial portions of the Software. |
13 | | * |
14 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
15 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
16 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
17 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
18 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
19 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
20 | | * IN THE SOFTWARE. |
21 | | */ |
22 | | #include <limits.h> |
23 | | #include "h2o/hostinfo.h" |
24 | | #include "h2o/memory.h" |
25 | | #include "h2o/socket.h" |
26 | | #include "h2o.h" |
27 | | #include "../probes_.h" |
28 | | |
29 | 0 | #define MODULE_NAME "lib/handler/connect.c" |
30 | | |
31 | | struct st_connect_handler_t { |
32 | | h2o_handler_t super; |
33 | | h2o_proxy_config_vars_t config; |
34 | | struct { |
35 | | size_t count; |
36 | | h2o_connect_acl_entry_t entries[0]; |
37 | | } acl; |
38 | | }; |
39 | | |
40 | 0 | #define MAX_ADDRESSES_PER_FAMILY 4 |
41 | 0 | #define UDP_CHUNK_OVERHEAD 10 /* sufficient space to hold DATAGRAM capsule header (RFC 9297) and context ID of zero (RFC 9298) */ |
42 | | |
43 | | struct st_server_address_t { |
44 | | struct sockaddr *sa; |
45 | | socklen_t salen; |
46 | | }; |
47 | | |
48 | | struct st_connect_generator_t { |
49 | | h2o_generator_t super; |
50 | | struct st_connect_handler_t *handler; |
51 | | h2o_req_t *src_req; |
52 | | |
53 | | struct { |
54 | | h2o_hostinfo_getaddr_req_t *v4, *v6; |
55 | | } getaddr_req; |
56 | | struct { |
57 | | struct st_server_address_t list[MAX_ADDRESSES_PER_FAMILY * 2]; |
58 | | size_t size; |
59 | | size_t used; |
60 | | } server_addresses; |
61 | | |
62 | | h2o_socket_t *sock; |
63 | | /** |
64 | | * Most significant and latest error that occurred, if any. Significance is represented as `class`, in descending order. |
65 | | */ |
66 | | struct { |
67 | | enum error_class { ERROR_CLASS_NAME_RESOLUTION, ERROR_CLASS_ACCESS_PROHIBITED, ERROR_CLASS_CONNECT } class; |
68 | | const char *str; |
69 | | } last_error; |
70 | | |
71 | | /** |
72 | | * timer used to handle user-visible timeouts (i.e., connect- and io-timeout) |
73 | | */ |
74 | | h2o_timer_t timeout; |
75 | | /** |
76 | | * timer used to for RFC 8305-style happy eyeballs (resolution delay and connection attempt delay) |
77 | | */ |
78 | | h2o_timer_t eyeball_delay; |
79 | | |
80 | | /** |
81 | | * Pick v4 (or v6) address in the next connection attempt. RFC 8305 recommends trying the other family one by one. |
82 | | */ |
83 | | unsigned pick_v4 : 1; |
84 | | /** |
85 | | * `h2o_process_request` was called without request streaming; all data that have to be sent is inside `h2o_req_t::entity` |
86 | | */ |
87 | | unsigned no_req_streaming : 1; |
88 | | /** |
89 | | * set when the send-side is closed by the user |
90 | | */ |
91 | | unsigned write_closed : 1; |
92 | | /** |
93 | | * set when h2o_send has been called to notify that the socket has been closed |
94 | | */ |
95 | | unsigned read_closed : 1; |
96 | | /** |
97 | | * if socket has been closed |
98 | | */ |
99 | | unsigned socket_closed : 1; |
100 | | /** |
101 | | * if connecting using TCP (or UDP) |
102 | | */ |
103 | | unsigned is_tcp : 1; |
104 | | /** |
105 | | * TCP- and UDP-specific data |
106 | | */ |
107 | | union { |
108 | | struct { |
109 | | h2o_buffer_t *sendbuf; |
110 | | h2o_buffer_t *recvbuf_detached; |
111 | | } tcp; |
112 | | struct { |
113 | | struct { |
114 | | h2o_buffer_t *buf; /* for datagram fragments */ |
115 | | h2o_timer_t delayed; |
116 | | } egress; |
117 | | struct { |
118 | | char buf[UDP_CHUNK_OVERHEAD + 1500]; |
119 | | } ingress; |
120 | | /** |
121 | | * if using draft-03 style encoding rather than RFC 9298 |
122 | | */ |
123 | | unsigned is_draft03 : 1; |
124 | | } udp; |
125 | | }; |
126 | | }; |
127 | | |
128 | | static h2o_iovec_t get_proxy_status_identity(h2o_req_t *req) |
129 | 0 | { |
130 | 0 | h2o_iovec_t identity = req->conn->ctx->globalconf->proxy_status_identity; |
131 | 0 | if (identity.base == NULL) |
132 | 0 | identity = h2o_iovec_init(H2O_STRLIT("h2o")); |
133 | 0 | return identity; |
134 | 0 | } |
135 | | |
136 | | static const struct st_server_address_t *get_dest_addr(struct st_connect_generator_t *self) |
137 | 0 | { |
138 | 0 | if (self->server_addresses.used > 0) { |
139 | 0 | return &self->server_addresses.list[self->server_addresses.used - 1]; |
140 | 0 | } else { |
141 | 0 | return NULL; |
142 | 0 | } |
143 | 0 | } |
144 | | |
145 | | static void add_proxy_status_header(struct st_connect_handler_t *handler, h2o_req_t *req, const char *error_type, |
146 | | const char *details, const char *rcode, h2o_iovec_t dest_addr_str) |
147 | 0 | { |
148 | 0 | if (!handler->config.connect_proxy_status_enabled) |
149 | 0 | return; |
150 | | |
151 | 0 | h2o_mem_pool_t *pool = &req->pool; |
152 | 0 | h2o_iovec_t parts[10] = { |
153 | 0 | get_proxy_status_identity(req), |
154 | 0 | }; |
155 | 0 | size_t nparts = 1; |
156 | 0 | if (error_type != NULL) { |
157 | 0 | parts[nparts++] = h2o_iovec_init(H2O_STRLIT("; error=")); |
158 | 0 | parts[nparts++] = h2o_iovec_init(error_type, strlen(error_type)); |
159 | 0 | } |
160 | 0 | if (rcode != NULL) { |
161 | 0 | parts[nparts++] = h2o_iovec_init(H2O_STRLIT("; rcode=")); |
162 | 0 | parts[nparts++] = h2o_encode_sf_string(pool, rcode, SIZE_MAX); |
163 | 0 | } |
164 | 0 | if (details != NULL) { |
165 | 0 | parts[nparts++] = h2o_iovec_init(H2O_STRLIT("; details=")); |
166 | 0 | parts[nparts++] = h2o_encode_sf_string(pool, details, SIZE_MAX); |
167 | 0 | } |
168 | 0 | if (dest_addr_str.base != NULL) { |
169 | 0 | parts[nparts++] = h2o_iovec_init(H2O_STRLIT("; next-hop=\"")); |
170 | 0 | parts[nparts++] = dest_addr_str; |
171 | 0 | parts[nparts++] = h2o_iovec_init(H2O_STRLIT("\"")); |
172 | 0 | } |
173 | 0 | assert(nparts <= sizeof(parts) / sizeof(parts[0])); |
174 | 0 | h2o_iovec_t hval = h2o_concat_list(pool, parts, nparts); |
175 | 0 | h2o_add_header_by_str(pool, &req->res.headers, H2O_STRLIT("proxy-status"), 0, NULL, hval.base, hval.len); |
176 | 0 | } |
177 | | |
178 | 0 | #define TO_BITMASK(type, len) ((type) ~(((type)1 << (sizeof(type) * 8 - (len))) - 1)) |
179 | | |
180 | | static void record_error(struct st_connect_handler_t *handler, h2o_req_t *req, const struct st_server_address_t *addr, |
181 | | const char *error_type, const char *details, const char *rcode) |
182 | 0 | { |
183 | 0 | H2O_PROBE_REQUEST(CONNECT_ERROR, req, error_type, details, rcode); |
184 | |
|
185 | 0 | char dest_addr_strbuf[NI_MAXHOST]; |
186 | 0 | h2o_iovec_t dest_addr_str = h2o_iovec_init(NULL, 0); |
187 | 0 | if (addr != NULL) { |
188 | 0 | size_t len = h2o_socket_getnumerichost(addr->sa, addr->salen, dest_addr_strbuf); |
189 | 0 | if (len != SIZE_MAX) { |
190 | 0 | dest_addr_str = h2o_iovec_init(dest_addr_strbuf, len); |
191 | 0 | } |
192 | 0 | } |
193 | |
|
194 | 0 | h2o_req_log_error(req, MODULE_NAME, "%s; rcode=%s; details=%s; next-hop=%s", error_type, rcode != NULL ? rcode : "(null)", |
195 | 0 | details != NULL ? details : "(null)", dest_addr_str.base != NULL ? dest_addr_str.base : "(null)"); |
196 | |
|
197 | 0 | add_proxy_status_header(handler, req, error_type, details, rcode, dest_addr_str); |
198 | 0 | } |
199 | | |
200 | | static void record_connect_success(struct st_connect_generator_t *self) |
201 | 0 | { |
202 | 0 | const struct st_server_address_t *addr = get_dest_addr(self); |
203 | 0 | if (addr == NULL) |
204 | 0 | return; |
205 | | |
206 | 0 | H2O_PROBE_REQUEST(CONNECT_SUCCESS, self->src_req, addr->sa); |
207 | |
|
208 | 0 | char dest_addr_strbuf[NI_MAXHOST]; |
209 | 0 | size_t len = h2o_socket_getnumerichost(addr->sa, addr->salen, dest_addr_strbuf); |
210 | 0 | if (len != SIZE_MAX) { |
211 | 0 | add_proxy_status_header(self->handler, self->src_req, NULL, NULL, NULL, h2o_iovec_init(dest_addr_strbuf, len)); |
212 | 0 | } |
213 | 0 | } |
214 | | |
215 | | static void record_socket_error(struct st_connect_generator_t *self, const char *err) |
216 | 0 | { |
217 | 0 | const char *error_type; |
218 | 0 | const char *details = NULL; |
219 | 0 | if (err == h2o_socket_error_conn_refused) |
220 | 0 | error_type = "connection_refused"; |
221 | 0 | else if (err == h2o_socket_error_conn_timed_out) |
222 | 0 | error_type = "connection_timeout"; |
223 | 0 | else if (err == h2o_socket_error_network_unreachable || err == h2o_socket_error_host_unreachable) |
224 | 0 | error_type = "destination_ip_unroutable"; |
225 | 0 | else { |
226 | 0 | error_type = "proxy_internal_error"; |
227 | 0 | details = err; |
228 | 0 | } |
229 | 0 | record_error(self->handler, self->src_req, get_dest_addr(self), error_type, details, NULL); |
230 | 0 | } |
231 | | |
232 | | static void try_connect(struct st_connect_generator_t *self); |
233 | | static int tcp_start_connect(struct st_connect_generator_t *self, struct st_server_address_t *server_address); |
234 | | static int udp_connect(struct st_connect_generator_t *self, struct st_server_address_t *server_address); |
235 | | |
236 | | static h2o_loop_t *get_loop(struct st_connect_generator_t *self) |
237 | 0 | { |
238 | 0 | return self->src_req->conn->ctx->loop; |
239 | 0 | } |
240 | | |
241 | | static void stop_eyeballs(struct st_connect_generator_t *self) |
242 | 0 | { |
243 | 0 | if (self->getaddr_req.v4 != NULL) { |
244 | 0 | h2o_hostinfo_getaddr_cancel(self->getaddr_req.v4); |
245 | 0 | self->getaddr_req.v4 = NULL; |
246 | 0 | } |
247 | 0 | if (self->getaddr_req.v6 != NULL) { |
248 | 0 | h2o_hostinfo_getaddr_cancel(self->getaddr_req.v6); |
249 | 0 | self->getaddr_req.v6 = NULL; |
250 | 0 | } |
251 | 0 | if (self->eyeball_delay.cb != NULL) { |
252 | 0 | h2o_timer_unlink(&self->eyeball_delay); |
253 | 0 | self->eyeball_delay.cb = NULL; |
254 | 0 | } |
255 | 0 | } |
256 | | |
257 | | static void dispose_generator(struct st_connect_generator_t *self) |
258 | 0 | { |
259 | 0 | stop_eyeballs(self); |
260 | 0 | if (self->sock != NULL) { |
261 | 0 | h2o_socket_close(self->sock); |
262 | 0 | self->sock = NULL; |
263 | 0 | self->socket_closed = 1; |
264 | 0 | } |
265 | 0 | if (self->is_tcp) { |
266 | 0 | if (self->tcp.sendbuf != NULL) |
267 | 0 | h2o_buffer_dispose(&self->tcp.sendbuf); |
268 | 0 | if (self->tcp.recvbuf_detached != NULL) |
269 | 0 | h2o_buffer_dispose(&self->tcp.recvbuf_detached); |
270 | 0 | } else { |
271 | 0 | if (self->udp.egress.buf != NULL) |
272 | 0 | h2o_buffer_dispose(&self->udp.egress.buf); |
273 | 0 | h2o_timer_unlink(&self->udp.egress.delayed); |
274 | 0 | } |
275 | 0 | h2o_timer_unlink(&self->timeout); |
276 | 0 | } |
277 | | |
278 | | static int close_socket(struct st_connect_generator_t *self) |
279 | 0 | { |
280 | 0 | int send_inflight; |
281 | |
|
282 | 0 | if (self->is_tcp) { |
283 | 0 | self->tcp.recvbuf_detached = self->sock->input; |
284 | 0 | send_inflight = self->tcp.recvbuf_detached->size != 0; |
285 | 0 | } else { |
286 | 0 | send_inflight = !h2o_socket_is_reading(self->sock); |
287 | 0 | } |
288 | 0 | h2o_buffer_init(&self->sock->input, &h2o_socket_buffer_prototype); |
289 | 0 | h2o_socket_close(self->sock); |
290 | 0 | self->sock = NULL; |
291 | 0 | self->socket_closed = 1; |
292 | |
|
293 | 0 | return send_inflight; |
294 | 0 | } |
295 | | |
296 | | static void close_readwrite(struct st_connect_generator_t *self) |
297 | 0 | { |
298 | 0 | int send_inflight = 0; |
299 | |
|
300 | 0 | if (self->sock != NULL) |
301 | 0 | send_inflight = close_socket(self); |
302 | 0 | else if (self->is_tcp) |
303 | 0 | send_inflight = self->tcp.recvbuf_detached->size != 0; |
304 | |
|
305 | 0 | if (h2o_timer_is_linked(&self->timeout)) |
306 | 0 | h2o_timer_unlink(&self->timeout); |
307 | | |
308 | | /* immediately notify read-close if necessary, setting up delayed task to for destroying other items; the timer is reset if |
309 | | * `h2o_send` indirectly invokes `dispose_generator`. */ |
310 | 0 | if (!self->read_closed && !send_inflight) { |
311 | 0 | h2o_timer_link(get_loop(self), 0, &self->timeout); |
312 | 0 | self->read_closed = 1; |
313 | 0 | h2o_send(self->src_req, NULL, 0, H2O_SEND_STATE_FINAL); |
314 | 0 | return; |
315 | 0 | } |
316 | | |
317 | | /* notify write-close if necessary; see the comment above regarding the use of the timer */ |
318 | 0 | if (!self->write_closed && self->is_tcp && self->tcp.sendbuf->size != 0) { |
319 | 0 | self->write_closed = 1; |
320 | 0 | h2o_timer_link(get_loop(self), 0, &self->timeout); |
321 | 0 | self->src_req->proceed_req(self->src_req, h2o_httpclient_error_io /* TODO notify as cancel? */); |
322 | 0 | return; |
323 | 0 | } |
324 | 0 | } |
325 | | |
326 | | static void on_io_timeout(h2o_timer_t *timer) |
327 | 0 | { |
328 | 0 | struct st_connect_generator_t *self = H2O_STRUCT_FROM_MEMBER(struct st_connect_generator_t, timeout, timer); |
329 | 0 | H2O_PROBE_REQUEST0(CONNECT_IO_TIMEOUT, self->src_req); |
330 | 0 | close_readwrite(self); |
331 | 0 | } |
332 | | |
333 | | static void reset_io_timeout(struct st_connect_generator_t *self) |
334 | 0 | { |
335 | 0 | if (self->sock != NULL) { |
336 | 0 | h2o_timer_unlink(&self->timeout); |
337 | 0 | h2o_timer_link(get_loop(self), self->handler->config.io_timeout, &self->timeout); |
338 | 0 | } |
339 | 0 | } |
340 | | |
341 | | static void send_connect_error(struct st_connect_generator_t *self, int code, const char *msg, const char *errstr) |
342 | 0 | { |
343 | 0 | stop_eyeballs(self); |
344 | 0 | h2o_timer_unlink(&self->timeout); |
345 | |
|
346 | 0 | if (self->sock != NULL) { |
347 | 0 | h2o_socket_close(self->sock); |
348 | 0 | self->sock = NULL; |
349 | 0 | } |
350 | |
|
351 | 0 | h2o_send_error_generic(self->src_req, code, msg, errstr, H2O_SEND_ERROR_KEEP_HEADERS); |
352 | 0 | } |
353 | | |
354 | | static void on_connect_error(struct st_connect_generator_t *self, const char *errstr) |
355 | 0 | { |
356 | 0 | send_connect_error(self, 502, "Gateway Error", errstr); |
357 | 0 | } |
358 | | |
359 | | static void on_connect_timeout(h2o_timer_t *entry) |
360 | 0 | { |
361 | 0 | struct st_connect_generator_t *self = H2O_STRUCT_FROM_MEMBER(struct st_connect_generator_t, timeout, entry); |
362 | 0 | if (self->server_addresses.size > 0) { |
363 | 0 | record_error(self->handler, self->src_req, get_dest_addr(self), "connection_timeout", NULL, NULL); |
364 | 0 | } else { |
365 | 0 | record_error(self->handler, self->src_req, NULL, "dns_timeout", NULL, NULL); |
366 | 0 | } |
367 | 0 | on_connect_error(self, h2o_httpclient_error_io_timeout); |
368 | 0 | } |
369 | | |
370 | | static void set_last_error(struct st_connect_generator_t *self, enum error_class class, const char *str) |
371 | 0 | { |
372 | 0 | if (self->last_error.class <= class) { |
373 | 0 | self->last_error.class = class; |
374 | 0 | self->last_error.str = str; |
375 | 0 | } |
376 | 0 | } |
377 | | |
378 | | static void on_resolution_delay_timeout(h2o_timer_t *entry) |
379 | 0 | { |
380 | 0 | struct st_connect_generator_t *self = H2O_STRUCT_FROM_MEMBER(struct st_connect_generator_t, eyeball_delay, entry); |
381 | |
|
382 | 0 | assert(self->server_addresses.used == 0); |
383 | | |
384 | 0 | try_connect(self); |
385 | 0 | } |
386 | | |
387 | | static void on_connection_attempt_delay_timeout(h2o_timer_t *entry) |
388 | 0 | { |
389 | 0 | struct st_connect_generator_t *self = H2O_STRUCT_FROM_MEMBER(struct st_connect_generator_t, eyeball_delay, entry); |
390 | | |
391 | | /* If no more addresses are available, continue trying the current attempt until the connect_timeout expires. */ |
392 | 0 | if (self->server_addresses.used == self->server_addresses.size) |
393 | 0 | return; |
394 | | |
395 | | /* close current connection attempt and try next. */ |
396 | 0 | h2o_socket_close(self->sock); |
397 | 0 | self->sock = NULL; |
398 | 0 | try_connect(self); |
399 | 0 | } |
400 | | |
401 | | static int store_server_addresses(struct st_connect_generator_t *self, struct addrinfo *res) |
402 | 0 | { |
403 | 0 | size_t num_added = 0; |
404 | | |
405 | | /* copy first entries in the response; ordering of addresses being returned by `getaddrinfo` is respected, as ordinary clients |
406 | | * (incl. forward proxy) are not expected to distribute the load among the addresses being returned. */ |
407 | 0 | do { |
408 | 0 | assert(self->server_addresses.size < PTLS_ELEMENTSOF(self->server_addresses.list)); |
409 | 0 | if (h2o_connect_lookup_acl(self->handler->acl.entries, self->handler->acl.count, res->ai_addr)) { |
410 | 0 | struct st_server_address_t *dst = self->server_addresses.list + self->server_addresses.size++; |
411 | 0 | dst->sa = h2o_mem_alloc_pool_aligned(&self->src_req->pool, H2O_ALIGNOF(struct sockaddr_storage), res->ai_addrlen); |
412 | 0 | memcpy(dst->sa, res->ai_addr, res->ai_addrlen); |
413 | 0 | dst->salen = res->ai_addrlen; |
414 | 0 | ++num_added; |
415 | 0 | } |
416 | 0 | } while ((res = res->ai_next) != NULL && num_added < MAX_ADDRESSES_PER_FAMILY); |
417 | | |
418 | 0 | return num_added != 0; |
419 | 0 | } |
420 | | |
421 | | static void on_getaddr(h2o_hostinfo_getaddr_req_t *getaddr_req, const char *errstr, struct addrinfo *res, void *_self) |
422 | 0 | { |
423 | 0 | struct st_connect_generator_t *self = _self; |
424 | 0 | if (getaddr_req == self->getaddr_req.v4) { |
425 | 0 | self->getaddr_req.v4 = NULL; |
426 | 0 | } else if (getaddr_req == self->getaddr_req.v6) { |
427 | 0 | self->getaddr_req.v6 = NULL; |
428 | 0 | } else { |
429 | 0 | h2o_fatal("unexpected getaddr_req"); |
430 | 0 | } |
431 | | |
432 | | /* Store addresses, or convert error to ACL denial. */ |
433 | 0 | if (errstr == NULL) { |
434 | 0 | if (self->is_tcp) { |
435 | 0 | assert(res->ai_socktype == SOCK_STREAM); |
436 | 0 | } else { |
437 | 0 | assert(res->ai_socktype == SOCK_DGRAM); |
438 | 0 | } |
439 | 0 | assert(res != NULL && "upon successful return, getaddrinfo shall return at least one address (RFC 3493 Section 6.1)"); |
440 | 0 | if (!store_server_addresses(self, res)) |
441 | 0 | set_last_error(self, ERROR_CLASS_ACCESS_PROHIBITED, "destination_ip_prohibited"); |
442 | 0 | } else { |
443 | 0 | set_last_error(self, ERROR_CLASS_NAME_RESOLUTION, errstr); |
444 | 0 | } |
445 | | |
446 | 0 | if (self->getaddr_req.v4 == NULL) { |
447 | | /* If v6 lookup is still running, that means that v4 lookup has *just* completed. Set the resolution delay timer if v4 |
448 | | * addresses are available. */ |
449 | 0 | if (self->getaddr_req.v6 != NULL) { |
450 | 0 | assert(self->server_addresses.used == 0); |
451 | 0 | if (self->server_addresses.size != 0) { |
452 | 0 | self->eyeball_delay.cb = on_resolution_delay_timeout; |
453 | 0 | h2o_timer_link(get_loop(self), self->handler->config.happy_eyeballs.name_resolution_delay, &self->eyeball_delay); |
454 | 0 | } |
455 | 0 | return; |
456 | 0 | } |
457 | | |
458 | | /* Both v4 and v6 lookups are complete. If the resolution delay timer is running. Reset it. */ |
459 | 0 | if (h2o_timer_is_linked(&self->eyeball_delay) && self->eyeball_delay.cb == on_resolution_delay_timeout) { |
460 | 0 | assert(self->server_addresses.used == 0); |
461 | 0 | h2o_timer_unlink(&self->eyeball_delay); |
462 | 0 | } |
463 | | /* In case no addresses are available, send HTTP error. */ |
464 | 0 | if (self->server_addresses.size == 0) { |
465 | 0 | if (self->last_error.class == ERROR_CLASS_ACCESS_PROHIBITED) { |
466 | 0 | record_error(self->handler, self->src_req, NULL, self->last_error.str, NULL, NULL); |
467 | 0 | send_connect_error(self, 403, "Destination IP Prohibited", "Destination IP Prohibited"); |
468 | 0 | } else { |
469 | 0 | const char *rcode; |
470 | 0 | if (self->last_error.str == h2o_hostinfo_error_nxdomain) { |
471 | 0 | rcode = "NXDOMAIN"; |
472 | 0 | } else if (self->last_error.str == h2o_hostinfo_error_nodata) { |
473 | 0 | rcode = "NODATA"; |
474 | 0 | } else if (self->last_error.str == h2o_hostinfo_error_refused) { |
475 | 0 | rcode = "REFUSED"; |
476 | 0 | } else if (self->last_error.str == h2o_hostinfo_error_servfail) { |
477 | 0 | rcode = "SERVFAIL"; |
478 | 0 | } else { |
479 | 0 | rcode = NULL; |
480 | 0 | } |
481 | 0 | record_error(self->handler, self->src_req, NULL, "dns_error", self->last_error.str, rcode); |
482 | 0 | on_connect_error(self, self->last_error.str); |
483 | 0 | } |
484 | 0 | return; |
485 | 0 | } |
486 | 0 | } |
487 | | |
488 | | /* If the connection attempt has been under way for more than CONNECTION_ATTEMPT_DELAY_MS and the lookup that just completed |
489 | | * gave us a new address to try, then stop that connection attempt and start a new connection attempt using the new address. |
490 | | * |
491 | | * If the connection attempt has been under way for less than that, then do nothing for now. Eventually, either the timeout |
492 | | * will expire or the connection attempt will complete. |
493 | | * |
494 | | * If the connection attempt is under way but the lookup has not provided us any new address to try, then do nothing for now, |
495 | | * and wait for the connection attempt to complete. */ |
496 | 0 | if (self->sock != NULL) { |
497 | 0 | if (h2o_timer_is_linked(&self->eyeball_delay)) |
498 | 0 | return; |
499 | 0 | if (self->server_addresses.used == self->server_addresses.size) |
500 | 0 | return; |
501 | 0 | h2o_socket_close(self->sock); |
502 | 0 | self->sock = NULL; |
503 | 0 | } |
504 | 0 | try_connect(self); |
505 | 0 | } |
506 | | |
507 | | static struct st_server_address_t *pick_and_swap(struct st_connect_generator_t *self, size_t idx) |
508 | 0 | { |
509 | 0 | struct st_server_address_t *server_address = NULL; |
510 | |
|
511 | 0 | if (idx != self->server_addresses.used) { |
512 | 0 | struct st_server_address_t swap = self->server_addresses.list[idx]; |
513 | 0 | self->server_addresses.list[idx] = self->server_addresses.list[self->server_addresses.used]; |
514 | 0 | self->server_addresses.list[self->server_addresses.used] = swap; |
515 | 0 | } |
516 | 0 | server_address = &self->server_addresses.list[self->server_addresses.used]; |
517 | 0 | self->server_addresses.used++; |
518 | 0 | self->pick_v4 = !self->pick_v4; |
519 | 0 | return server_address; |
520 | 0 | } |
521 | | |
522 | | static struct st_server_address_t *get_next_server_address_for_connect(struct st_connect_generator_t *self) |
523 | 0 | { |
524 | 0 | struct st_server_address_t *server_address = NULL; |
525 | | |
526 | | /* Fetch the next address from the list of resolved addresses. */ |
527 | 0 | for (size_t i = self->server_addresses.used; i < self->server_addresses.size; i++) { |
528 | 0 | if (self->pick_v4 && self->server_addresses.list[i].sa->sa_family == AF_INET) { |
529 | 0 | server_address = pick_and_swap(self, i); |
530 | 0 | break; |
531 | 0 | } else if (!self->pick_v4 && self->server_addresses.list[i].sa->sa_family == AF_INET6) { |
532 | 0 | server_address = pick_and_swap(self, i); |
533 | 0 | break; |
534 | 0 | } |
535 | 0 | } |
536 | | |
537 | | /* If address of the preferred address family is not available, select one of the other family, if available. Otherwise, |
538 | | * send an HTTP error response or wait for address resolution. */ |
539 | 0 | if (server_address == NULL && self->server_addresses.used < self->server_addresses.size) { |
540 | 0 | server_address = &self->server_addresses.list[self->server_addresses.used]; |
541 | 0 | self->server_addresses.used++; |
542 | 0 | } |
543 | |
|
544 | 0 | return server_address; |
545 | 0 | } |
546 | | |
547 | | static void try_connect(struct st_connect_generator_t *self) |
548 | 0 | { |
549 | 0 | struct st_server_address_t *server_address; |
550 | |
|
551 | 0 | do { |
552 | 0 | server_address = get_next_server_address_for_connect(self); |
553 | 0 | if (server_address == NULL) { |
554 | | /* If address an is not available, send an HTTP error response or wait for address resolution. */ |
555 | 0 | if (self->getaddr_req.v4 == NULL && self->getaddr_req.v6 == NULL) { |
556 | | /* No pending address resolution, send error response. */ |
557 | 0 | assert(self->last_error.class == ERROR_CLASS_CONNECT); |
558 | 0 | record_socket_error(self, self->last_error.str); |
559 | 0 | on_connect_error(self, self->last_error.str); |
560 | 0 | } |
561 | 0 | return; |
562 | 0 | } |
563 | | |
564 | | /* Connect. Retry if the connect function returns error immediately. */ |
565 | 0 | } while (!(self->is_tcp ? tcp_start_connect : udp_connect)(self, server_address)); |
566 | 0 | } |
567 | | |
568 | | static void tcp_on_write_complete(h2o_socket_t *_sock, const char *err) |
569 | 0 | { |
570 | 0 | struct st_connect_generator_t *self = _sock->data; |
571 | |
|
572 | 0 | if (err != NULL) { |
573 | 0 | H2O_PROBE_REQUEST(CONNECT_TCP_WRITE_ERROR, self->src_req, err); |
574 | 0 | } |
575 | | |
576 | | /* until h2o_socket_t implements shutdown(SHUT_WR), do a bidirectional close when we close the write-side */ |
577 | 0 | if (err != NULL || self->write_closed) { |
578 | 0 | close_readwrite(self); |
579 | 0 | return; |
580 | 0 | } |
581 | | |
582 | 0 | reset_io_timeout(self); |
583 | |
|
584 | 0 | h2o_buffer_consume(&self->tcp.sendbuf, self->tcp.sendbuf->size); |
585 | 0 | self->src_req->proceed_req(self->src_req, NULL); |
586 | 0 | } |
587 | | |
588 | | static void tcp_do_write(struct st_connect_generator_t *self) |
589 | 0 | { |
590 | 0 | reset_io_timeout(self); |
591 | |
|
592 | 0 | h2o_iovec_t vec = h2o_iovec_init(self->tcp.sendbuf->bytes, self->tcp.sendbuf->size); |
593 | 0 | H2O_PROBE_REQUEST(CONNECT_TCP_WRITE, self->src_req, vec.len); |
594 | 0 | h2o_socket_write(self->sock, &vec, 1, tcp_on_write_complete); |
595 | 0 | } |
596 | | |
597 | | static int tcp_write(void *_self, int is_end_stream) |
598 | 0 | { |
599 | 0 | struct st_connect_generator_t *self = _self; |
600 | 0 | h2o_iovec_t chunk = self->src_req->entity; |
601 | |
|
602 | 0 | assert(!self->write_closed); |
603 | 0 | assert(self->tcp.sendbuf->size == 0); |
604 | | |
605 | | /* the socket might have been closed due to a read error */ |
606 | 0 | if (self->socket_closed) |
607 | 0 | return 1; |
608 | | |
609 | 0 | assert(self->sock != NULL && "write_req called before proceed_req is called?"); |
610 | | |
611 | | /* buffer input */ |
612 | 0 | h2o_buffer_append(&self->tcp.sendbuf, chunk.base, chunk.len); |
613 | 0 | if (is_end_stream) |
614 | 0 | self->write_closed = 1; |
615 | | |
616 | | /* write if the socket has been opened */ |
617 | 0 | if (self->sock != NULL && !h2o_socket_is_writing(self->sock)) |
618 | 0 | tcp_do_write(self); |
619 | |
|
620 | 0 | return 0; |
621 | 0 | } |
622 | | |
623 | | static void tcp_on_read(h2o_socket_t *_sock, const char *err) |
624 | 0 | { |
625 | 0 | struct st_connect_generator_t *self = _sock->data; |
626 | |
|
627 | 0 | h2o_socket_read_stop(self->sock); |
628 | 0 | h2o_timer_unlink(&self->timeout); |
629 | |
|
630 | 0 | if (err == NULL) { |
631 | 0 | h2o_iovec_t vec = h2o_iovec_init(self->sock->input->bytes, self->sock->input->size); |
632 | 0 | H2O_PROBE_REQUEST(CONNECT_TCP_READ, self->src_req, vec.len); |
633 | 0 | h2o_send(self->src_req, &vec, 1, H2O_SEND_STATE_IN_PROGRESS); |
634 | 0 | } else { |
635 | 0 | H2O_PROBE_REQUEST(CONNECT_TCP_READ_ERROR, self->src_req, err); |
636 | | /* unidirectional close is signalled using H2O_SEND_STATE_FINAL, but the write side remains open */ |
637 | 0 | self->read_closed = 1; |
638 | 0 | h2o_send(self->src_req, NULL, 0, H2O_SEND_STATE_FINAL); |
639 | 0 | } |
640 | 0 | } |
641 | | |
642 | | static void tcp_on_proceed(h2o_generator_t *_self, h2o_req_t *req) |
643 | 0 | { |
644 | 0 | struct st_connect_generator_t *self = H2O_STRUCT_FROM_MEMBER(struct st_connect_generator_t, super, _self); |
645 | |
|
646 | 0 | assert(!self->read_closed); |
647 | | |
648 | 0 | if (self->sock != NULL) { |
649 | 0 | h2o_buffer_consume(&self->sock->input, self->sock->input->size); |
650 | 0 | reset_io_timeout(self); |
651 | 0 | h2o_socket_read_start(self->sock, tcp_on_read); |
652 | 0 | } else { |
653 | 0 | self->read_closed = 1; |
654 | 0 | h2o_send(self->src_req, NULL, 0, H2O_SEND_STATE_FINAL); |
655 | 0 | } |
656 | 0 | } |
657 | | |
658 | | static void tcp_on_connect(h2o_socket_t *_sock, const char *err) |
659 | 0 | { |
660 | 0 | struct st_connect_generator_t *self = _sock->data; |
661 | |
|
662 | 0 | assert(self->sock == _sock); |
663 | | |
664 | 0 | if (err != NULL) { |
665 | 0 | set_last_error(self, ERROR_CLASS_CONNECT, err); |
666 | 0 | h2o_socket_close(self->sock); |
667 | 0 | self->sock = NULL; |
668 | 0 | try_connect(self); |
669 | 0 | return; |
670 | 0 | } |
671 | | |
672 | 0 | stop_eyeballs(self); |
673 | 0 | self->timeout.cb = on_io_timeout; |
674 | 0 | reset_io_timeout(self); |
675 | | |
676 | | /* Start write. Once write is complete (or if there is nothing to write), `proceed_req` will be called or the socket would be |
677 | | * closed if `write_closed` is set. */ |
678 | 0 | self->src_req->write_req.cb(self, self->no_req_streaming); |
679 | |
|
680 | 0 | record_connect_success(self); |
681 | | |
682 | | /* build and submit 200 response */ |
683 | 0 | self->src_req->res.status = 200; |
684 | 0 | h2o_start_response(self->src_req, &self->super); |
685 | 0 | h2o_send(self->src_req, NULL, 0, H2O_SEND_STATE_IN_PROGRESS); |
686 | 0 | } |
687 | | |
688 | | static int tcp_start_connect(struct st_connect_generator_t *self, struct st_server_address_t *server_address) |
689 | 0 | { |
690 | 0 | H2O_PROBE_REQUEST(CONNECT_TCP_START, self->src_req, server_address->sa); |
691 | |
|
692 | 0 | const char *errstr; |
693 | 0 | if ((self->sock = h2o_socket_connect(get_loop(self), server_address->sa, server_address->salen, tcp_on_connect, &errstr)) == |
694 | 0 | NULL) { |
695 | 0 | set_last_error(self, ERROR_CLASS_CONNECT, errstr); |
696 | 0 | return 0; |
697 | 0 | } |
698 | | |
699 | 0 | self->sock->data = self; |
700 | 0 | #if !H2O_USE_LIBUV |
701 | | /* This is the maximum amount of data that will be buffered within userspace. It is hard-coded to 64KB to balance throughput |
702 | | * and latency, and because we do not expect the need to change the value. */ |
703 | 0 | h2o_evloop_socket_set_max_read_size(self->sock, 64 * 1024); |
704 | 0 | #endif |
705 | 0 | self->eyeball_delay.cb = on_connection_attempt_delay_timeout; |
706 | 0 | h2o_timer_link(get_loop(self), self->handler->config.happy_eyeballs.connection_attempt_delay, &self->eyeball_delay); |
707 | |
|
708 | 0 | return 1; |
709 | 0 | } |
710 | | |
711 | | static h2o_iovec_t udp_get_next_chunk(const char *start, size_t len, size_t *to_consume, int *skip) |
712 | 0 | { |
713 | 0 | const uint8_t *bytes = (const uint8_t *)start; |
714 | 0 | const uint8_t *end = bytes + len; |
715 | 0 | uint64_t chunk_type, chunk_length; |
716 | |
|
717 | 0 | chunk_type = ptls_decode_quicint(&bytes, end); |
718 | 0 | if (chunk_type == UINT64_MAX) |
719 | 0 | return h2o_iovec_init(NULL, 0); |
720 | 0 | chunk_length = ptls_decode_quicint(&bytes, end); |
721 | 0 | if (chunk_length == UINT64_MAX) |
722 | 0 | return h2o_iovec_init(NULL, 0); |
723 | | |
724 | | /* chunk is incomplete */ |
725 | 0 | if (end - bytes < chunk_length) |
726 | 0 | return h2o_iovec_init(NULL, 0); |
727 | | |
728 | | /* |
729 | | * https://tools.ietf.org/html/draft-ietf-masque-connect-udp-03#section-6 |
730 | | * CONNECT-UDP Stream Chunks can be used to convey UDP payloads, by |
731 | | * using a CONNECT-UDP Stream Chunk Type of UDP_PACKET (value 0x00). |
732 | | */ |
733 | 0 | *skip = chunk_type != 0; |
734 | 0 | *to_consume = (bytes + chunk_length) - (const uint8_t *)start; |
735 | |
|
736 | 0 | return h2o_iovec_init(bytes, chunk_length); |
737 | 0 | } |
738 | | |
739 | | static void udp_write_core(struct st_connect_generator_t *self, h2o_iovec_t datagram) |
740 | 0 | { |
741 | 0 | const uint8_t *src = (const uint8_t *)datagram.base, *end = src + datagram.len; |
742 | | |
743 | | /* When using RFC 9298, the payload starts with a Context ID; drop anything other than UDP packets. |
744 | | * TODO: propagate error when decoding fails? */ |
745 | 0 | if (!self->udp.is_draft03 && (ptls_decode_quicint(&src, end)) != 0) |
746 | 0 | return; |
747 | | |
748 | 0 | H2O_PROBE_REQUEST(CONNECT_UDP_WRITE, self->src_req, end - src); |
749 | 0 | while (send(h2o_socket_get_fd(self->sock), src, end - src, 0) == -1 && errno == EINTR) |
750 | 0 | ; |
751 | 0 | } |
752 | | |
753 | | static void udp_write_stream_complete_delayed(h2o_timer_t *_timer) |
754 | 0 | { |
755 | 0 | struct st_connect_generator_t *self = H2O_STRUCT_FROM_MEMBER(struct st_connect_generator_t, udp.egress.delayed, _timer); |
756 | |
|
757 | 0 | if (self->write_closed) { |
758 | 0 | close_readwrite(self); |
759 | 0 | return; |
760 | 0 | } |
761 | | |
762 | 0 | self->src_req->proceed_req(self->src_req, NULL); |
763 | 0 | } |
764 | | |
765 | | static void udp_do_write_stream(struct st_connect_generator_t *self, h2o_iovec_t chunk) |
766 | 0 | { |
767 | 0 | int from_buf = 0; |
768 | 0 | size_t off = 0; |
769 | |
|
770 | 0 | reset_io_timeout(self); |
771 | |
|
772 | 0 | if (self->udp.egress.buf->size != 0) { |
773 | 0 | from_buf = 1; |
774 | 0 | if (chunk.len != 0) |
775 | 0 | h2o_buffer_append(&self->udp.egress.buf, chunk.base, chunk.len); |
776 | 0 | chunk.base = self->udp.egress.buf->bytes; |
777 | 0 | chunk.len = self->udp.egress.buf->size; |
778 | 0 | } |
779 | 0 | do { |
780 | 0 | int skip = 0; |
781 | 0 | size_t to_consume; |
782 | 0 | h2o_iovec_t datagram = udp_get_next_chunk(chunk.base + off, chunk.len - off, &to_consume, &skip); |
783 | 0 | if (datagram.base == NULL) |
784 | 0 | break; |
785 | 0 | if (!skip) |
786 | 0 | udp_write_core(self, datagram); |
787 | 0 | off += to_consume; |
788 | 0 | } while (1); |
789 | |
|
790 | 0 | if (from_buf) { |
791 | 0 | h2o_buffer_consume(&self->udp.egress.buf, off); |
792 | 0 | } else if (chunk.len != off) { |
793 | 0 | h2o_buffer_append(&self->udp.egress.buf, chunk.base + off, chunk.len - off); |
794 | 0 | } |
795 | |
|
796 | 0 | h2o_timer_link(get_loop(self), 0, &self->udp.egress.delayed); |
797 | 0 | } |
798 | | |
799 | | static int udp_write_stream(void *_self, int is_end_stream) |
800 | 0 | { |
801 | 0 | struct st_connect_generator_t *self = _self; |
802 | 0 | h2o_iovec_t chunk = self->src_req->entity; |
803 | |
|
804 | 0 | assert(!self->write_closed); |
805 | | |
806 | | /* the socket might have been closed tue to a read error */ |
807 | 0 | if (self->socket_closed) |
808 | 0 | return 1; |
809 | | |
810 | 0 | assert(self->sock != NULL && "write_req called before proceed_req is called?"); |
811 | | |
812 | 0 | if (is_end_stream) |
813 | 0 | self->write_closed = 1; |
814 | | |
815 | | /* if the socket is not yet open, buffer input and return */ |
816 | 0 | if (self->sock == NULL) { |
817 | 0 | h2o_buffer_append(&self->udp.egress.buf, chunk.base, chunk.len); |
818 | 0 | return 0; |
819 | 0 | } |
820 | | |
821 | 0 | udp_do_write_stream(self, chunk); |
822 | 0 | return 0; |
823 | 0 | } |
824 | | |
825 | | static void udp_write_datagrams(h2o_req_t *_req, h2o_iovec_t *datagrams, size_t num_datagrams) |
826 | 0 | { |
827 | 0 | struct st_connect_generator_t *self = _req->write_req.ctx; |
828 | |
|
829 | 0 | reset_io_timeout(self); |
830 | |
|
831 | 0 | for (size_t i = 0; i != num_datagrams; ++i) |
832 | 0 | udp_write_core(self, datagrams[i]); |
833 | 0 | } |
834 | | |
835 | | static void udp_on_read(h2o_socket_t *_sock, const char *err) |
836 | 0 | { |
837 | 0 | struct st_connect_generator_t *self = _sock->data; |
838 | 0 | h2o_iovec_t payload = |
839 | 0 | h2o_iovec_init(self->udp.ingress.buf + UDP_CHUNK_OVERHEAD, sizeof(self->udp.ingress.buf) - UDP_CHUNK_OVERHEAD); |
840 | |
|
841 | 0 | if (err != NULL) { |
842 | 0 | close_readwrite(self); |
843 | 0 | return; |
844 | 0 | } |
845 | | |
846 | 0 | { /* read UDP packet, or return */ |
847 | 0 | ssize_t rret; |
848 | 0 | while ((rret = recv(h2o_socket_get_fd(self->sock), payload.base, payload.len, 0)) == -1 && errno == EINTR) |
849 | 0 | ; |
850 | 0 | if (rret == -1) |
851 | 0 | return; |
852 | 0 | payload.len = rret; |
853 | 0 | } |
854 | 0 | H2O_PROBE_REQUEST(CONNECT_UDP_READ, self->src_req, payload.len); |
855 | | |
856 | | /* prepend Context ID (of zero, indicating UDP packet) if RFC 9298 */ |
857 | 0 | if (!self->udp.is_draft03) { |
858 | 0 | *--payload.base = 0; |
859 | 0 | payload.len += 1; |
860 | 0 | } |
861 | | |
862 | | /* forward UDP datagram as is; note that it might be zero-sized */ |
863 | 0 | if (self->src_req->forward_datagram.read_ != NULL) { |
864 | 0 | self->src_req->forward_datagram.read_(self->src_req, &payload, 1); |
865 | 0 | } else { |
866 | 0 | h2o_socket_read_stop(self->sock); |
867 | 0 | h2o_timer_unlink(&self->timeout); |
868 | 0 | { /* prepend Datagram Capsule length */ |
869 | 0 | uint8_t length_buf[8]; |
870 | 0 | size_t length_len = quicly_encodev(length_buf, payload.len) - length_buf; |
871 | 0 | memcpy(payload.base - length_len, length_buf, length_len); |
872 | 0 | payload.base -= length_len; |
873 | 0 | payload.len += length_len; |
874 | 0 | } |
875 | | /* prepend Datagram Capsule Type */ |
876 | 0 | *--payload.base = 0; |
877 | 0 | payload.len += 1; |
878 | 0 | assert(payload.base >= self->udp.ingress.buf); |
879 | 0 | h2o_send(self->src_req, &payload, 1, H2O_SEND_STATE_IN_PROGRESS); |
880 | 0 | } |
881 | 0 | } |
882 | | |
883 | | static void udp_on_proceed(h2o_generator_t *_self, h2o_req_t *req) |
884 | 0 | { |
885 | 0 | struct st_connect_generator_t *self = H2O_STRUCT_FROM_MEMBER(struct st_connect_generator_t, super, _self); |
886 | |
|
887 | 0 | if (self->sock != NULL) { |
888 | 0 | h2o_buffer_consume(&self->sock->input, self->sock->input->size); |
889 | 0 | reset_io_timeout(self); |
890 | 0 | h2o_socket_read_start(self->sock, udp_on_read); |
891 | 0 | } else { |
892 | 0 | self->read_closed = 1; |
893 | 0 | h2o_send(self->src_req, NULL, 0, H2O_SEND_STATE_FINAL); |
894 | 0 | } |
895 | 0 | } |
896 | | |
897 | | static int udp_connect(struct st_connect_generator_t *self, struct st_server_address_t *server_address) |
898 | 0 | { |
899 | 0 | int fd; |
900 | |
|
901 | 0 | assert(self->udp.egress.buf->size == 0); /* the handler does not call `proceed_req` until the connection becomes ready */ |
902 | | |
903 | 0 | H2O_PROBE_REQUEST(CONNECT_UDP_START, self->src_req, server_address->sa); |
904 | | /* connect */ |
905 | 0 | if ((fd = socket(server_address->sa->sa_family, SOCK_DGRAM, 0)) == -1 || |
906 | 0 | connect(fd, server_address->sa, server_address->salen) != 0) { |
907 | 0 | const char *err = h2o_socket_error_conn_fail; |
908 | 0 | if (fd != -1) { |
909 | 0 | err = h2o_socket_get_error_string(errno, err); |
910 | 0 | close(fd); |
911 | 0 | } |
912 | 0 | set_last_error(self, ERROR_CLASS_CONNECT, err); |
913 | 0 | return 0; |
914 | 0 | } |
915 | | |
916 | 0 | stop_eyeballs(self); |
917 | 0 | self->timeout.cb = on_io_timeout; |
918 | 0 | reset_io_timeout(self); |
919 | | |
920 | | /* setup, initiating transfer of early data */ |
921 | | #if H2O_USE_LIBUV |
922 | | self->sock = h2o_uv__poll_create(get_loop(self), fd, (uv_close_cb)free); |
923 | | #else |
924 | 0 | self->sock = h2o_evloop_socket_create(get_loop(self), fd, H2O_SOCKET_FLAG_DONT_READ); |
925 | 0 | #endif |
926 | 0 | assert(self->sock != NULL); |
927 | 0 | self->sock->data = self; |
928 | 0 | self->src_req->write_req.cb = udp_write_stream; |
929 | 0 | self->src_req->forward_datagram.write_ = udp_write_datagrams; |
930 | 0 | self->src_req->write_req.ctx = self; |
931 | |
|
932 | 0 | record_connect_success(self); |
933 | | |
934 | | /* build and submit success */ |
935 | 0 | if (self->src_req->version < 0x200 && !self->udp.is_draft03) { |
936 | 0 | assert(self->src_req->upgrade.base != NULL); |
937 | 0 | self->src_req->res.status = 101; |
938 | 0 | self->src_req->res.reason = "Switching Protocols"; |
939 | 0 | h2o_add_header(&self->src_req->pool, &self->src_req->res.headers, H2O_TOKEN_UPGRADE, NULL, H2O_STRLIT("connect-udp")); |
940 | 0 | } else { |
941 | 0 | self->src_req->res.status = 200; |
942 | 0 | } |
943 | 0 | if (!self->udp.is_draft03) |
944 | 0 | h2o_add_header_by_str(&self->src_req->pool, &self->src_req->res.headers, H2O_STRLIT("capsule-protocol"), 0, NULL, |
945 | 0 | H2O_STRLIT("?1")); |
946 | 0 | h2o_start_response(self->src_req, &self->super); |
947 | 0 | h2o_send(self->src_req, NULL, 0, H2O_SEND_STATE_IN_PROGRESS); |
948 | | |
949 | | /* write any data if provided, or just call the proceed_req callback */ |
950 | 0 | self->src_req->write_req.cb(self, self->no_req_streaming); |
951 | |
|
952 | 0 | return 1; |
953 | 0 | } |
954 | | |
955 | | static void on_stop(h2o_generator_t *_self, h2o_req_t *req) |
956 | 0 | { |
957 | 0 | struct st_connect_generator_t *self = H2O_STRUCT_FROM_MEMBER(struct st_connect_generator_t, super, _self); |
958 | 0 | dispose_generator(self); |
959 | 0 | } |
960 | | |
961 | | static void on_generator_dispose(void *_self) |
962 | 0 | { |
963 | 0 | struct st_connect_generator_t *self = _self; |
964 | 0 | H2O_PROBE_REQUEST0(CONNECT_DISPOSE, self->src_req); |
965 | 0 | dispose_generator(self); |
966 | 0 | } |
967 | | |
968 | | /** |
969 | | * expects "/host/port/" as input, where the preceding slash is optional |
970 | | */ |
971 | | static int masque_decode_hostport(h2o_mem_pool_t *pool, const char *_src, size_t _len, h2o_iovec_t *host, uint16_t *port) |
972 | 0 | { |
973 | 0 | char *src = (char *)_src; /* h2o_strtosizefwd takes non-const arg, so ... */ |
974 | 0 | const char *end = src + _len; |
975 | |
|
976 | 0 | if (src < end && src[0] == '/') |
977 | 0 | ++src; |
978 | |
|
979 | 0 | { /* extract host */ |
980 | 0 | size_t host_len; |
981 | 0 | if ((host_len = h2o_strstr(src, end - src, H2O_STRLIT("/"))) == SIZE_MAX || host_len == 0) |
982 | 0 | return 0; |
983 | 0 | if ((*host = h2o_uri_unescape(pool, src, host_len)).base == NULL) |
984 | 0 | return 0; |
985 | 0 | src += host_len + 1; |
986 | 0 | } |
987 | | |
988 | 0 | { /* parse port */ |
989 | 0 | size_t v; |
990 | 0 | if ((v = h2o_strtosizefwd(&src, end - src)) >= 65535) |
991 | 0 | return 0; |
992 | 0 | if (src == end || *src != '/') |
993 | 0 | return 0; |
994 | 0 | *port = (uint16_t)v; |
995 | 0 | } |
996 | | |
997 | 0 | return 1; |
998 | 0 | } |
999 | | |
1000 | | static int on_req_core(struct st_connect_handler_t *handler, h2o_req_t *req, h2o_iovec_t host, uint16_t port, int is_tcp, |
1001 | | int is_masque_draft03) |
1002 | 0 | { |
1003 | 0 | struct st_connect_generator_t *self; |
1004 | 0 | size_t sizeof_self = offsetof(struct st_connect_generator_t, tcp) + (is_tcp ? sizeof(self->tcp) : sizeof(self->udp)); |
1005 | 0 | self = h2o_mem_alloc_shared(&req->pool, sizeof_self, on_generator_dispose); |
1006 | 0 | memset(self, 0, sizeof_self); |
1007 | 0 | self->super.stop = on_stop; |
1008 | 0 | self->handler = handler; |
1009 | 0 | self->src_req = req; |
1010 | 0 | self->timeout.cb = on_connect_timeout; |
1011 | 0 | if (is_tcp) { |
1012 | 0 | self->is_tcp = 1; |
1013 | 0 | self->super.proceed = tcp_on_proceed; |
1014 | 0 | h2o_buffer_init(&self->tcp.sendbuf, &h2o_socket_buffer_prototype); |
1015 | 0 | } else { |
1016 | 0 | self->super.proceed = udp_on_proceed; |
1017 | 0 | h2o_buffer_init(&self->udp.egress.buf, &h2o_socket_buffer_prototype); |
1018 | 0 | self->udp.egress.delayed = (h2o_timer_t){.cb = udp_write_stream_complete_delayed}; |
1019 | 0 | self->udp.is_draft03 = is_masque_draft03; |
1020 | 0 | } |
1021 | 0 | h2o_timer_link(get_loop(self), handler->config.connect_timeout, &self->timeout); |
1022 | | |
1023 | | /* setup write_req now, so that the protocol handler would not provide additional data until we call `proceed_req` */ |
1024 | 0 | assert(req->entity.base != NULL && "CONNECT must indicate existence of payload"); |
1025 | 0 | self->src_req->write_req.cb = is_tcp ? tcp_write : udp_write_stream; |
1026 | 0 | self->src_req->write_req.ctx = self; |
1027 | 0 | if (self->src_req->proceed_req == NULL) |
1028 | 0 | self->no_req_streaming = 1; |
1029 | |
|
1030 | 0 | char port_str[sizeof(H2O_UINT16_LONGEST_STR)]; |
1031 | 0 | int port_strlen = sprintf(port_str, "%" PRIu16, port); |
1032 | |
|
1033 | 0 | self->getaddr_req.v6 = h2o_hostinfo_getaddr( |
1034 | 0 | &self->src_req->conn->ctx->receivers.hostinfo_getaddr, host, h2o_iovec_init(port_str, port_strlen), AF_INET6, |
1035 | 0 | is_tcp ? SOCK_STREAM : SOCK_DGRAM, is_tcp ? IPPROTO_TCP : IPPROTO_UDP, AI_ADDRCONFIG | AI_NUMERICSERV, on_getaddr, self); |
1036 | 0 | self->getaddr_req.v4 = h2o_hostinfo_getaddr( |
1037 | 0 | &self->src_req->conn->ctx->receivers.hostinfo_getaddr, host, h2o_iovec_init(port_str, port_strlen), AF_INET, |
1038 | 0 | is_tcp ? SOCK_STREAM : SOCK_DGRAM, is_tcp ? IPPROTO_TCP : IPPROTO_UDP, AI_ADDRCONFIG | AI_NUMERICSERV, on_getaddr, self); |
1039 | |
|
1040 | 0 | return 0; |
1041 | 0 | } |
1042 | | |
1043 | | static int on_req_classic_connect(h2o_handler_t *_handler, h2o_req_t *req) |
1044 | 0 | { |
1045 | 0 | struct st_connect_handler_t *handler = (void *)_handler; |
1046 | 0 | h2o_iovec_t host; |
1047 | 0 | uint16_t port; |
1048 | 0 | int is_tcp; |
1049 | |
|
1050 | 0 | if (req->upgrade.base != NULL) { |
1051 | 0 | return -1; |
1052 | 0 | } else if (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("CONNECT"))) { |
1053 | | /* old-style CONNECT */ |
1054 | 0 | is_tcp = 1; |
1055 | 0 | } else if (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("CONNECT-UDP"))) { |
1056 | | /* masque (draft 03); host and port are stored the same way as ordinary CONNECT |
1057 | | * TODO remove code once we drop support for draft-03 */ |
1058 | 0 | if (!handler->config.support_masque_draft_03) { |
1059 | 0 | h2o_send_error_405(req, "Method Not Allowed", "Method Not Allowed", H2O_SEND_ERROR_KEEP_HEADERS); |
1060 | 0 | return 0; |
1061 | 0 | } |
1062 | 0 | is_tcp = 0; |
1063 | 0 | } else { |
1064 | | /* it is not the task of this handler to handle non-CONNECT requests */ |
1065 | 0 | return -1; |
1066 | 0 | } |
1067 | | |
1068 | | /* parse host and port from authority, unless it is handled above in the case of extended connect */ |
1069 | 0 | if (h2o_url_parse_hostport(req->authority.base, req->authority.len, &host, &port) == NULL || port == 0 || port == 65535) { |
1070 | 0 | record_error(handler, req, NULL, "http_request_error", "invalid host:port", NULL); |
1071 | 0 | h2o_send_error_400(req, "Bad Request", "Bad Request", H2O_SEND_ERROR_KEEP_HEADERS); |
1072 | 0 | return 0; |
1073 | 0 | } |
1074 | | |
1075 | 0 | return on_req_core((void *)handler, req, host, port, is_tcp, 1); |
1076 | 0 | } |
1077 | | |
1078 | | /** |
1079 | | * handles RFC9298 requests |
1080 | | */ |
1081 | | static int on_req_connect_udp(h2o_handler_t *_handler, h2o_req_t *req) |
1082 | 0 | { |
1083 | 0 | struct st_connect_handler_t *handler = (void *)_handler; |
1084 | 0 | h2o_iovec_t host; |
1085 | 0 | uint16_t port; |
1086 | | |
1087 | | /* reject requests wo. upgrade: connect-udp */ |
1088 | 0 | if (!(req->upgrade.base != NULL && h2o_lcstris(req->upgrade.base, req->upgrade.len, H2O_STRLIT("connect-udp")))) |
1089 | 0 | return -1; |
1090 | | |
1091 | | /* check method */ |
1092 | 0 | if (!(req->version < 0x200 ? h2o_memis(req->method.base, req->method.len, H2O_STRLIT("GET")) |
1093 | 0 | : h2o_memis(req->method.base, req->method.len, H2O_STRLIT("CONNECT")))) |
1094 | 0 | return -1; |
1095 | | |
1096 | | /* masque (RFC 9298); parse host/port */ |
1097 | 0 | if (!masque_decode_hostport(&req->pool, req->path_normalized.base + req->pathconf->path.len, |
1098 | 0 | req->path_normalized.len - req->pathconf->path.len, &host, &port)) { |
1099 | 0 | record_error(handler, req, NULL, "http_request_error", "invalid URI", NULL); |
1100 | 0 | h2o_send_error_400(req, "Bad Request", "Bad Request", H2O_SEND_ERROR_KEEP_HEADERS); |
1101 | 0 | return 0; |
1102 | 0 | } |
1103 | | |
1104 | 0 | return on_req_core((void *)handler, req, host, port, 0, 0); |
1105 | 0 | } |
1106 | | |
1107 | | static void do_register(h2o_pathconf_t *pathconf, h2o_proxy_config_vars_t *config, h2o_connect_acl_entry_t *acl_entries, |
1108 | | size_t num_acl_entries, int (*on_req)(struct st_h2o_handler_t *self, h2o_req_t *req)) |
1109 | 0 | { |
1110 | 0 | assert(config->max_buffer_size != 0); |
1111 | | |
1112 | 0 | struct st_connect_handler_t *self = (void *)h2o_create_handler(pathconf, offsetof(struct st_connect_handler_t, acl.entries) + |
1113 | 0 | sizeof(*self->acl.entries) * num_acl_entries); |
1114 | |
|
1115 | 0 | self->super.on_req = on_req; |
1116 | 0 | self->super.supports_request_streaming = 1; |
1117 | 0 | self->config = *config; |
1118 | 0 | self->acl.count = num_acl_entries; |
1119 | 0 | memcpy(self->acl.entries, acl_entries, sizeof(self->acl.entries[0]) * num_acl_entries); |
1120 | 0 | } |
1121 | | |
1122 | | void h2o_connect_register(h2o_pathconf_t *pathconf, h2o_proxy_config_vars_t *config, h2o_connect_acl_entry_t *acl_entries, |
1123 | | size_t num_acl_entries) |
1124 | 0 | { |
1125 | 0 | do_register(pathconf, config, acl_entries, num_acl_entries, on_req_classic_connect); |
1126 | 0 | } |
1127 | | |
1128 | | void h2o_connect_udp_register(h2o_pathconf_t *pathconf, h2o_proxy_config_vars_t *config, h2o_connect_acl_entry_t *acl_entries, |
1129 | | size_t num_acl_entries) |
1130 | 0 | { |
1131 | 0 | do_register(pathconf, config, acl_entries, num_acl_entries, on_req_connect_udp); |
1132 | 0 | } |
1133 | | |
1134 | | /** |
1135 | | * Parses a decimal number at the beginning of [*s, end), advancing *s past the digits consumed. Returns the parsed value, or -1 if |
1136 | | * no digit is found or the value would not fit in a non-negative int. The caller is responsible for range-checking the result. |
1137 | | */ |
1138 | | static int parse_decimal(const char **s, const char *end) |
1139 | 0 | { |
1140 | 0 | const char *start = *s; |
1141 | 0 | int v = 0; |
1142 | |
|
1143 | 0 | for (; *s != end && '0' <= **s && **s <= '9'; ++*s) { |
1144 | 0 | int digit = **s - '0'; |
1145 | | /* reject if `v * 10 + digit` would overflow INT_MAX, checked before the multiply to avoid relying on a wider type */ |
1146 | 0 | if (v > (INT_MAX - digit) / 10) |
1147 | 0 | return -1; |
1148 | 0 | v = v * 10 + digit; |
1149 | 0 | } |
1150 | 0 | if (*s == start) |
1151 | 0 | return -1; |
1152 | 0 | return v; |
1153 | 0 | } |
1154 | | |
1155 | | /** |
1156 | | * Parse an ACL host/mask:port specification. |
1157 | | * Accepted format: host[/mask][:port[-port]] (the netmask may also appear after the port, e.g. host:port/mask, for backward |
1158 | | * compatibility). A port of `*` means any port, same as omitting it. |
1159 | | * Examples: 10.0.0.0/8:80-443, [::1]:443, *:25, 127.0.0.1/32, 127.0.0.1:25/24, 127.0.0.1:*, local |
1160 | | * Returns pointer to first unparsed character on success, NULL on error. |
1161 | | */ |
1162 | | static const char *parse_acl_hostport(const char *s, size_t len, h2o_iovec_t *host, size_t *addr_mask, uint16_t *port_min, |
1163 | | uint16_t *port_max) |
1164 | 0 | { |
1165 | 0 | const char *token_start = s, *token_end, *end = s + len; |
1166 | | |
1167 | | /* default to matching any port; overridden below if a port or range is given */ |
1168 | 0 | *port_min = 0; |
1169 | 0 | *port_max = 65535; |
1170 | 0 | *addr_mask = 0; |
1171 | |
|
1172 | 0 | if (token_start == end) |
1173 | 0 | return NULL; |
1174 | | |
1175 | | /* parse host */ |
1176 | 0 | if (*token_start == '[') { |
1177 | | /* IPv6 address in brackets */ |
1178 | 0 | ++token_start; |
1179 | 0 | if ((token_end = memchr(token_start, ']', end - token_start)) == NULL) |
1180 | 0 | return NULL; |
1181 | 0 | *host = h2o_iovec_init(token_start, token_end - token_start); |
1182 | 0 | token_start = token_end + 1; |
1183 | 0 | } else { |
1184 | 0 | for (token_end = token_start; !(token_end == end || *token_end == '/' || *token_end == ':'); ++token_end) |
1185 | 0 | ; |
1186 | 0 | *host = h2o_iovec_init(token_start, token_end - token_start); |
1187 | 0 | token_start = token_end; |
1188 | 0 | } |
1189 | | |
1190 | | /* disallow zero-length host */ |
1191 | 0 | if (host->len == 0) |
1192 | 0 | return NULL; |
1193 | | |
1194 | | /* parse optional /mask appearing before the port (e.g. 10.0.0.0/8:80) */ |
1195 | 0 | if (token_start != end && *token_start == '/') { |
1196 | 0 | int mask; |
1197 | 0 | ++token_start; |
1198 | 0 | if ((mask = parse_decimal(&token_start, end)) <= 0) |
1199 | 0 | return NULL; |
1200 | 0 | *addr_mask = mask; |
1201 | 0 | } |
1202 | | |
1203 | | /* parse optional :port, :port-port, or :* (any port) */ |
1204 | 0 | if (token_start != end && *token_start == ':') { |
1205 | 0 | ++token_start; |
1206 | 0 | if (token_start != end && *token_start == '*') { |
1207 | | /* `:*` means any port; leave the default full range in place */ |
1208 | 0 | ++token_start; |
1209 | 0 | } else { |
1210 | 0 | int p; |
1211 | 0 | if ((p = parse_decimal(&token_start, end)) < 0 || p > 65535) |
1212 | 0 | return NULL; |
1213 | 0 | *port_min = (uint16_t)p; |
1214 | 0 | if (token_start != end && *token_start == '-') { |
1215 | 0 | ++token_start; |
1216 | 0 | if ((p = parse_decimal(&token_start, end)) < 0 || p > 65535) |
1217 | 0 | return NULL; |
1218 | 0 | } |
1219 | 0 | *port_max = (uint16_t)p; |
1220 | 0 | } |
1221 | 0 | } |
1222 | | |
1223 | | /* parse optional /mask appearing after the port (legacy order, e.g. 10.0.0.0:80/8) */ |
1224 | 0 | if (token_start != end && *token_start == '/') { |
1225 | 0 | int mask; |
1226 | 0 | if (*addr_mask != 0) /* mask already given before the port */ |
1227 | 0 | return NULL; |
1228 | 0 | ++token_start; |
1229 | 0 | if ((mask = parse_decimal(&token_start, end)) <= 0) |
1230 | 0 | return NULL; |
1231 | 0 | *addr_mask = mask; |
1232 | 0 | } |
1233 | | |
1234 | 0 | return token_start; |
1235 | 0 | } |
1236 | | |
1237 | | const char *h2o_connect_parse_acl(h2o_connect_acl_entry_t *output, const char *input) |
1238 | 0 | { |
1239 | | /* type */ |
1240 | 0 | switch (input[0]) { |
1241 | 0 | case '+': |
1242 | 0 | output->allow_ = 1; |
1243 | 0 | break; |
1244 | 0 | case '-': |
1245 | 0 | output->allow_ = 0; |
1246 | 0 | break; |
1247 | 0 | default: |
1248 | 0 | return "ACL entry must begin with + or -"; |
1249 | 0 | } |
1250 | | |
1251 | | /* extract address, optional netmask, optional port or port range. Port ranges are INCLUSIVE on both min and max */ |
1252 | 0 | h2o_iovec_t host_vec; |
1253 | 0 | uint16_t port_min, port_max; |
1254 | 0 | const char *rest; |
1255 | 0 | if ((rest = parse_acl_hostport(input + 1, strlen(input + 1), &host_vec, &output->addr_mask, &port_min, &port_max)) == NULL) |
1256 | 0 | goto GenericParseError; |
1257 | 0 | if (*rest != '\0') |
1258 | 0 | goto GenericParseError; |
1259 | 0 | char *host = alloca(host_vec.len + 1); |
1260 | 0 | memcpy(host, host_vec.base, host_vec.len); |
1261 | 0 | host[host_vec.len] = '\0'; |
1262 | | |
1263 | | /* parse address */ |
1264 | 0 | struct in_addr v4addr; |
1265 | 0 | struct in6_addr v6addr; |
1266 | 0 | if (strcmp(host, "*") == 0) { |
1267 | 0 | output->addr_family = H2O_CONNECT_ACL_ADDRESS_ANY; |
1268 | 0 | if (output->addr_mask != 0) |
1269 | 0 | return "wildcard address (*) cannot have a netmask"; |
1270 | 0 | } else if (inet_pton(AF_INET, host, &v4addr) == 1) { |
1271 | 0 | output->addr_family = H2O_CONNECT_ACL_ADDRESS_V4; |
1272 | 0 | if (output->addr_mask == 0) { |
1273 | 0 | output->addr_mask = 32; |
1274 | 0 | } else if (output->addr_mask > 32) { |
1275 | 0 | return "invalid address mask"; |
1276 | 0 | } |
1277 | 0 | output->addr.v4 = ntohl(v4addr.s_addr) & TO_BITMASK(uint32_t, output->addr_mask); |
1278 | 0 | } else if (inet_pton(AF_INET6, host, &v6addr) == 1) { |
1279 | 0 | output->addr_family = H2O_CONNECT_ACL_ADDRESS_V6; |
1280 | 0 | if (output->addr_mask == 0) { |
1281 | 0 | output->addr_mask = 128; |
1282 | 0 | } else if (output->addr_mask > 128) { |
1283 | 0 | return "invalid address mask"; |
1284 | 0 | } |
1285 | 0 | size_t i; |
1286 | 0 | for (i = 0; i < output->addr_mask / 8; ++i) |
1287 | 0 | output->addr.v6[i] = v6addr.s6_addr[i]; |
1288 | 0 | if (output->addr_mask % 8 != 0) |
1289 | 0 | output->addr.v6[i] = v6addr.s6_addr[i] & TO_BITMASK(uint8_t, output->addr_mask % 8); |
1290 | 0 | for (++i; i < PTLS_ELEMENTSOF(output->addr.v6); ++i) |
1291 | 0 | output->addr.v6[i] = 0; |
1292 | 0 | } else { |
1293 | 0 | return "failed to parse address"; |
1294 | 0 | } |
1295 | | |
1296 | 0 | if (port_max < port_min) |
1297 | 0 | return "port range end must be >= start"; |
1298 | | |
1299 | 0 | output->port_min = port_min; |
1300 | 0 | output->port_max = port_max; |
1301 | |
|
1302 | 0 | return NULL; |
1303 | | |
1304 | 0 | GenericParseError: |
1305 | 0 | return "failed to parse input, expected format is: [+-]address[/netmask][:port[-port]]"; |
1306 | 0 | } |
1307 | | |
1308 | | int h2o_connect_lookup_acl(h2o_connect_acl_entry_t *acl_entries, size_t num_acl_entries, struct sockaddr *target) |
1309 | 0 | { |
1310 | 0 | uint32_t target_v4addr = 0; |
1311 | 0 | uint16_t target_port; |
1312 | | |
1313 | | /* reject anything other than v4/v6, as well as converting the values to native format */ |
1314 | 0 | switch (target->sa_family) { |
1315 | 0 | case AF_INET: { |
1316 | 0 | struct sockaddr_in *sin = (void *)target; |
1317 | 0 | target_v4addr = ntohl(sin->sin_addr.s_addr); |
1318 | 0 | target_port = ntohs(sin->sin_port); |
1319 | 0 | } break; |
1320 | 0 | case AF_INET6: |
1321 | 0 | target_port = htons(((struct sockaddr_in6 *)target)->sin6_port); |
1322 | 0 | break; |
1323 | 0 | default: |
1324 | 0 | return 0; |
1325 | 0 | } |
1326 | | |
1327 | | /* check each ACL entry */ |
1328 | 0 | for (size_t i = 0; i != num_acl_entries; ++i) { |
1329 | 0 | h2o_connect_acl_entry_t *entry = acl_entries + i; |
1330 | | /* check port */ |
1331 | 0 | if (target_port < entry->port_min || target_port > entry->port_max) |
1332 | 0 | goto Next; |
1333 | | /* check address */ |
1334 | 0 | switch (entry->addr_family) { |
1335 | 0 | case H2O_CONNECT_ACL_ADDRESS_ANY: |
1336 | 0 | break; |
1337 | 0 | case H2O_CONNECT_ACL_ADDRESS_V4: { |
1338 | 0 | if (target->sa_family != AF_INET) |
1339 | 0 | goto Next; |
1340 | 0 | if (entry->addr.v4 != (target_v4addr & TO_BITMASK(uint32_t, entry->addr_mask))) |
1341 | 0 | goto Next; |
1342 | 0 | } break; |
1343 | 0 | case H2O_CONNECT_ACL_ADDRESS_V6: { |
1344 | 0 | if (target->sa_family != AF_INET6) |
1345 | 0 | continue; |
1346 | 0 | uint8_t *target_v6addr = ((struct sockaddr_in6 *)target)->sin6_addr.s6_addr; |
1347 | 0 | size_t i; |
1348 | 0 | for (i = 0; i < entry->addr_mask / 8; ++i) |
1349 | 0 | if (entry->addr.v6[i] != target_v6addr[i]) |
1350 | 0 | goto Next; |
1351 | 0 | if (entry->addr_mask % 8 != 0 && entry->addr.v6[i] != (target_v6addr[i] & TO_BITMASK(uint8_t, entry->addr_mask % 8))) |
1352 | 0 | goto Next; |
1353 | 0 | } break; |
1354 | 0 | } |
1355 | | /* match */ |
1356 | 0 | return entry->allow_; |
1357 | 0 | Next:; |
1358 | 0 | } |
1359 | | |
1360 | | /* default rule is deny */ |
1361 | 0 | return 0; |
1362 | 0 | } |