/src/curl/lib/vdns/httpsrr.c
Line | Count | Source |
1 | | /*************************************************************************** |
2 | | * _ _ ____ _ |
3 | | * Project ___| | | | _ \| | |
4 | | * / __| | | | |_) | | |
5 | | * | (__| |_| | _ <| |___ |
6 | | * \___|\___/|_| \_\_____| |
7 | | * |
8 | | * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. |
9 | | * |
10 | | * This software is licensed as described in the file COPYING, which |
11 | | * you should have received as part of this distribution. The terms |
12 | | * are also available at https://curl.se/docs/copyright.html. |
13 | | * |
14 | | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
15 | | * copies of the Software, and permit persons to whom the Software is |
16 | | * furnished to do so, under the terms of the COPYING file. |
17 | | * |
18 | | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
19 | | * KIND, either express or implied. |
20 | | * |
21 | | * SPDX-License-Identifier: curl |
22 | | * |
23 | | ***************************************************************************/ |
24 | | #include "curl_setup.h" |
25 | | |
26 | | #ifdef USE_HTTPSRR |
27 | | |
28 | | #include "urldata.h" |
29 | | #include "connect.h" |
30 | | #include "curl_trc.h" |
31 | | #include "vdns/httpsrr.h" |
32 | | #include "curlx/strdup.h" |
33 | | #include "curlx/inet_ntop.h" |
34 | | |
35 | | static CURLcode httpsrr_decode_alpn(const uint8_t *cp, size_t len, |
36 | | unsigned char *alpns) |
37 | 274 | { |
38 | | /* |
39 | | * The wire-format value for "alpn" consists of at least one alpn-id |
40 | | * prefixed by its length as a single octet, and these length-value pairs |
41 | | * are concatenated to form the SvcParamValue. These pairs MUST exactly fill |
42 | | * the SvcParamValue; otherwise, the SvcParamValue is malformed. |
43 | | */ |
44 | 274 | int idnum = 0; |
45 | | |
46 | 24.4k | while(len > 0) { |
47 | 24.2k | size_t tlen = *cp++; |
48 | 24.2k | enum alpnid id; |
49 | 24.2k | len--; |
50 | 24.2k | if(tlen > len) |
51 | 66 | return CURLE_BAD_CONTENT_ENCODING; |
52 | | |
53 | | /* we only store ALPN ids we know about */ |
54 | 24.2k | id = Curl_alpn2alpnid(cp, tlen); |
55 | 24.2k | if(id != ALPN_none) { |
56 | 1.79k | if(idnum == MAX_HTTPSRR_ALPNS) |
57 | 0 | break; |
58 | 1.79k | if(idnum && memchr(alpns, id, idnum)) |
59 | | /* this ALPN id is already stored */ |
60 | 1.63k | ; |
61 | 161 | else |
62 | 161 | alpns[idnum++] = (unsigned char)id; |
63 | 1.79k | } |
64 | 24.2k | cp += tlen; |
65 | 24.2k | len -= tlen; |
66 | 24.2k | } |
67 | 208 | if(idnum < MAX_HTTPSRR_ALPNS) |
68 | 208 | alpns[idnum] = ALPN_none; /* terminate the list */ |
69 | 208 | return CURLE_OK; |
70 | 274 | } |
71 | | |
72 | | #ifdef CURLVERBOSE |
73 | | |
74 | | static CURLcode httpsrr_print_addr(struct dynbuf *dyn, |
75 | | int ai_family, |
76 | | const uint8_t *addr, |
77 | | size_t total_len) |
78 | 2.06k | { |
79 | 2.06k | char buf[MAX_IPADR_LEN]; |
80 | 2.06k | size_t i, alen = (ai_family == AF_INET6) ? 16 : 4; |
81 | 2.06k | const char *sep = ""; |
82 | 2.06k | CURLcode result = CURLE_OK; |
83 | | |
84 | 39.4k | for(i = 0; (i < (total_len / alen)) && !result; ++i) { |
85 | 37.3k | if(curlx_inet_ntop(ai_family, addr + (i * alen), buf, sizeof(buf))) |
86 | 0 | result = curlx_dyn_add(dyn, "<error parsing address>"); |
87 | 37.3k | else |
88 | 37.3k | result = curlx_dyn_addf(dyn, "%s%s", sep, buf); |
89 | 37.3k | sep = ","; |
90 | 37.3k | } |
91 | 2.06k | return result; |
92 | 2.06k | } |
93 | | |
94 | | void Curl_httpsrr_trace(struct Curl_easy *data, |
95 | | struct Curl_https_rrinfo *rr) |
96 | 3.25k | { |
97 | 3.25k | struct dynbuf tmp; |
98 | 3.25k | CURLcode result; |
99 | | |
100 | 3.25k | if(!rr) { |
101 | 0 | CURL_TRC_DNS(data, "[HTTPS] no record available"); |
102 | 0 | return; |
103 | 0 | } |
104 | 3.25k | curlx_dyn_init(&tmp, 1024); |
105 | 3.25k | result = Curl_httpsrr_print(&tmp, rr); |
106 | 3.25k | if(!result) |
107 | 2.76k | CURL_TRC_DNS(data, "[HTTPS] record: %s", curlx_dyn_ptr(&tmp)); |
108 | 483 | else |
109 | 483 | CURL_TRC_DNS(data, "[HTTPS] error printing information"); |
110 | 3.25k | curlx_dyn_free(&tmp); |
111 | 3.25k | } |
112 | | |
113 | | CURLcode Curl_httpsrr_print(struct dynbuf *tmp, |
114 | | struct Curl_https_rrinfo *rr) |
115 | 3.25k | { |
116 | 3.25k | CURLcode result; |
117 | 3.25k | int i; |
118 | | |
119 | 3.25k | curlx_dyn_reset(tmp); |
120 | 3.25k | result = curlx_dyn_addf(tmp, "%u %s", rr->priority, |
121 | 3.25k | rr->target ? rr->target : "."); |
122 | 3.25k | if(!result && rr->mandatory) |
123 | 462 | result = curlx_dyn_add(tmp, " mandatory-keys(ignored)"); |
124 | 3.25k | if(!result && rr->alpns[0]) { |
125 | 500 | const char *sep = "", *name; |
126 | 500 | result = curlx_dyn_add(tmp, " alpn="); |
127 | 2.50k | for(i = 0; !result && (i < 4); ++i) { |
128 | 2.00k | switch(rr->alpns[i]) { |
129 | 281 | case ALPN_h1: |
130 | 281 | name = "http/1.1"; |
131 | 281 | break; |
132 | 171 | case ALPN_h2: |
133 | 171 | name = "h2"; |
134 | 171 | break; |
135 | 176 | case ALPN_h3: |
136 | 176 | name = "h3"; |
137 | 176 | break; |
138 | 1.37k | default: |
139 | 1.37k | name = NULL; |
140 | 2.00k | } |
141 | 2.00k | if(name) { |
142 | 628 | result = curlx_dyn_addf(tmp, "%s%s", sep, name); |
143 | 628 | sep = ","; |
144 | 628 | } |
145 | 2.00k | } |
146 | 500 | } |
147 | 3.25k | if(!result && rr->port_set) { |
148 | 234 | result = curlx_dyn_addf(tmp, " port=%u", rr->port); |
149 | 234 | } |
150 | 3.25k | if(!result && rr->no_def_alpn) |
151 | 328 | result = curlx_dyn_add(tmp, " no-default-alpn"); |
152 | 3.25k | if(!result && rr->ipv6hints_len) { |
153 | 1.11k | result = curlx_dyn_add(tmp, " ipv6hint="); |
154 | 1.11k | if(!result) |
155 | 1.11k | result = httpsrr_print_addr( |
156 | 1.11k | tmp, AF_INET6, rr->ipv6hints, rr->ipv6hints_len); |
157 | 1.11k | } |
158 | 3.25k | if(!result && rr->ipv4hints_len) { |
159 | 1.01k | result = curlx_dyn_add(tmp, " ipv4hint="); |
160 | 1.01k | if(!result) |
161 | 953 | result = httpsrr_print_addr( |
162 | 953 | tmp, AF_INET, rr->ipv4hints, rr->ipv4hints_len); |
163 | 1.01k | } |
164 | 3.25k | if(!result && rr->echconfiglist_len) |
165 | 447 | result = curlx_dyn_addf(tmp, " ech=<%zu bytes>", rr->echconfiglist_len); |
166 | | |
167 | 3.25k | return result; |
168 | 3.25k | } |
169 | | |
170 | | #endif /* CURLVERBOSE */ |
171 | | |
172 | | CURLcode Curl_httpsrr_set(struct Curl_https_rrinfo *rr, |
173 | | uint16_t rrkey, const uint8_t *val, size_t vlen) |
174 | 3.37k | { |
175 | 3.37k | CURLcode result = CURLE_OK; |
176 | 3.37k | switch(rrkey) { |
177 | 81 | case HTTPS_RR_CODE_MANDATORY: |
178 | 81 | rr->mandatory = TRUE; |
179 | 81 | break; |
180 | 274 | case HTTPS_RR_CODE_ALPN: /* str_list */ |
181 | 274 | result = httpsrr_decode_alpn(val, vlen, rr->alpns); |
182 | 274 | break; |
183 | 42 | case HTTPS_RR_CODE_NO_DEF_ALPN: |
184 | 42 | if(vlen) /* no data */ |
185 | 4 | return CURLE_BAD_FUNCTION_ARGUMENT; |
186 | 38 | rr->no_def_alpn = TRUE; |
187 | 38 | break; |
188 | 179 | case HTTPS_RR_CODE_IPV4: /* addr4 list */ |
189 | 179 | if(!vlen || (vlen & 3)) /* the size must be 4-byte aligned */ |
190 | 16 | return CURLE_BAD_FUNCTION_ARGUMENT; |
191 | 163 | curlx_free(rr->ipv4hints); |
192 | 163 | rr->ipv4hints = curlx_memdup(val, vlen); |
193 | 163 | if(!rr->ipv4hints) |
194 | 0 | return CURLE_OUT_OF_MEMORY; |
195 | 163 | rr->ipv4hints_len = vlen; |
196 | 163 | break; |
197 | 72 | case HTTPS_RR_CODE_ECH: |
198 | 72 | if(!vlen) |
199 | 1 | return CURLE_BAD_FUNCTION_ARGUMENT; |
200 | 71 | curlx_free(rr->echconfiglist); |
201 | 71 | rr->echconfiglist = curlx_memdup(val, vlen); |
202 | 71 | if(!rr->echconfiglist) |
203 | 0 | return CURLE_OUT_OF_MEMORY; |
204 | 71 | rr->echconfiglist_len = vlen; |
205 | 71 | break; |
206 | 205 | case HTTPS_RR_CODE_IPV6: /* addr6 list */ |
207 | 205 | if(!vlen || (vlen & 15)) /* the size must be 16-byte aligned */ |
208 | 15 | return CURLE_BAD_FUNCTION_ARGUMENT; |
209 | 190 | curlx_free(rr->ipv6hints); |
210 | 190 | rr->ipv6hints = curlx_memdup(val, vlen); |
211 | 190 | if(!rr->ipv6hints) |
212 | 0 | return CURLE_OUT_OF_MEMORY; |
213 | 190 | rr->ipv6hints_len = vlen; |
214 | 190 | break; |
215 | 46 | case HTTPS_RR_CODE_PORT: |
216 | 46 | if(vlen != 2) |
217 | 19 | return CURLE_BAD_FUNCTION_ARGUMENT; |
218 | 27 | rr->port = (uint16_t)((val[0] << 8) | val[1]); |
219 | 27 | rr->port_set = TRUE; |
220 | 27 | break; |
221 | 2.47k | default: |
222 | | /* unknown code */ |
223 | 2.47k | break; |
224 | 3.37k | } |
225 | 3.31k | return result; |
226 | 3.37k | } |
227 | | |
228 | | void Curl_httpsrr_destroy(struct Curl_https_rrinfo *rrinfo) |
229 | 538k | { |
230 | 538k | if(rrinfo) { |
231 | 941 | curlx_free(rrinfo->target); |
232 | 941 | curlx_free(rrinfo->echconfiglist); |
233 | 941 | curlx_free(rrinfo->ipv4hints); |
234 | 941 | curlx_free(rrinfo->ipv6hints); |
235 | 941 | curlx_free(rrinfo); |
236 | 941 | } |
237 | 538k | } |
238 | | |
239 | | bool Curl_httpsrr_applicable(struct Curl_easy *data, |
240 | | const struct Curl_https_rrinfo *rr) |
241 | 265k | { |
242 | 265k | if(!data->conn) |
243 | 0 | return FALSE; |
244 | 265k | return Curl_httpsrr_is_for_peer(data->conn->origin, rr); |
245 | 265k | } |
246 | | |
247 | | bool Curl_httpsrr_is_for_peer(struct Curl_peer *peer, |
248 | | const struct Curl_https_rrinfo *rr) |
249 | 266k | { |
250 | 266k | if(!peer || !rr) |
251 | 266k | return FALSE; |
252 | 0 | return (!rr->target || !rr->target[0] || |
253 | 0 | (rr->target[0] == '.' && !rr->target[1])) && |
254 | 0 | (!rr->port_set || rr->port == peer->port); |
255 | 266k | } |
256 | | |
257 | | #ifdef USE_ARES |
258 | | |
259 | | static CURLcode httpsrr_opt(const ares_dns_rr_t *rr, |
260 | | ares_dns_rr_key_t key, size_t idx, |
261 | | struct Curl_https_rrinfo *hinfo) |
262 | | { |
263 | | const unsigned char *val = NULL; |
264 | | unsigned short code; |
265 | | size_t len = 0; |
266 | | |
267 | | code = ares_dns_rr_get_opt(rr, key, idx, &val, &len); |
268 | | return Curl_httpsrr_set(hinfo, code, val, len); |
269 | | } |
270 | | |
271 | | CURLcode Curl_httpsrr_from_ares(const ares_dns_record_t *dnsrec, |
272 | | struct Curl_https_rrinfo **phinfo) |
273 | | { |
274 | | struct Curl_https_rrinfo *hinfo = NULL; |
275 | | CURLcode result = CURLE_OUT_OF_MEMORY; |
276 | | size_t i; |
277 | | |
278 | | hinfo = curlx_calloc(1, sizeof(*hinfo)); |
279 | | if(!hinfo) |
280 | | goto out; |
281 | | |
282 | | for(i = 0; i < ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); i++) { |
283 | | const char *target; |
284 | | size_t opt; |
285 | | const ares_dns_rr_t *rr = |
286 | | ares_dns_record_rr_get_const(dnsrec, ARES_SECTION_ANSWER, i); |
287 | | if(ares_dns_rr_get_type(rr) != ARES_REC_TYPE_HTTPS) |
288 | | continue; |
289 | | /* When SvcPriority is 0, the SVCB record is in AliasMode. Otherwise, it |
290 | | is in ServiceMode */ |
291 | | target = ares_dns_rr_get_str(rr, ARES_RR_HTTPS_TARGET); |
292 | | if(target && target[0]) { |
293 | | curlx_free(hinfo->target); |
294 | | hinfo->target = curlx_strdup(target); |
295 | | if(!hinfo->target) { |
296 | | result = CURLE_OUT_OF_MEMORY; |
297 | | goto out; |
298 | | } |
299 | | } |
300 | | hinfo->priority = ares_dns_rr_get_u16(rr, ARES_RR_HTTPS_PRIORITY); |
301 | | for(opt = 0; opt < ares_dns_rr_get_opt_cnt(rr, ARES_RR_HTTPS_PARAMS); |
302 | | opt++) { |
303 | | result = httpsrr_opt(rr, ARES_RR_HTTPS_PARAMS, opt, hinfo); |
304 | | if(result) |
305 | | goto out; |
306 | | } |
307 | | } |
308 | | result = CURLE_OK; |
309 | | |
310 | | out: |
311 | | if(result) { |
312 | | *phinfo = NULL; |
313 | | Curl_httpsrr_destroy(hinfo); |
314 | | } |
315 | | else { |
316 | | DEBUGASSERT(hinfo); |
317 | | *phinfo = hinfo; |
318 | | } |
319 | | return result; |
320 | | } |
321 | | |
322 | | #endif /* USE_ARES */ |
323 | | |
324 | | #endif /* USE_HTTPSRR */ |