/src/openssl41/ssl/dtls_conn_lookup.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2026 The OpenSSL Project Authors. All Rights Reserved. |
3 | | * |
4 | | * Licensed under the Apache License 2.0 (the "License"). You may not use |
5 | | * this file except in compliance with the License. You can obtain a copy |
6 | | * in the file LICENSE in the source distribution or at |
7 | | * https://www.openssl.org/source/license.html |
8 | | */ |
9 | | |
10 | | #include <openssl/crypto.h> |
11 | | #include <openssl/bio.h> |
12 | | #include <openssl/ssl.h> |
13 | | #include <openssl/rand.h> |
14 | | #include "internal/common.h" |
15 | | #include "internal/dgram_conn_lookup.h" |
16 | | #include "crypto/siphash.h" |
17 | | |
18 | | #ifndef OPENSSL_NO_DTLS |
19 | | |
20 | | /* |
21 | | * Internal entry structure for address-based connection lookup. |
22 | | * Stores a connection keyed by peer address. |
23 | | */ |
24 | | typedef struct dgram_conn_entry_st { |
25 | | BIO_ADDR peer; /* Peer address (key) */ |
26 | | SSL *ssl; /* The SSL connection object */ |
27 | | uint8_t *hashkey; /* Pre-built hash key (family + port + addr) */ |
28 | | size_t hashkey_len; /* Length of the hash key */ |
29 | | uint64_t *siphash_key; /* Pointer to shared SipHash key (hash-flooding defense) */ |
30 | | } DGRAM_CONN_ENTRY; |
31 | | |
32 | | DEFINE_LHASH_OF_EX(DGRAM_CONN_ENTRY); |
33 | | |
34 | | /* |
35 | | * Wrapper structure for address-based lookup implementation data. |
36 | | * Contains the hash table and SipHash key for hash-flooding defense. |
37 | | */ |
38 | | typedef struct addr_lookup_data_st { |
39 | | LHASH_OF(DGRAM_CONN_ENTRY) *htable; |
40 | | uint64_t hash_key[2]; /* 128-bit SipHash key */ |
41 | | } ADDR_LOOKUP_DATA; |
42 | | |
43 | | /* BIO_ADDR contains the raw address for every supported address family. */ |
44 | | #define ADDR_HASHKEY_MAX_LEN (sizeof(int) + sizeof(uint16_t) + sizeof(BIO_ADDR)) |
45 | | |
46 | | /* |
47 | | * Build a hash key from a BIO_ADDR in caller-provided storage. |
48 | | * The key format is: family (int) + port (uint16_t) + raw address bytes. |
49 | | * |
50 | | * Returns 1 on success, 0 on failure. |
51 | | */ |
52 | | static int build_hashkey_into(const BIO_ADDR *peer, uint8_t *hashkey, |
53 | | size_t capacity, size_t *out_len) |
54 | 0 | { |
55 | 0 | size_t addr_len = 0; |
56 | 0 | size_t header_len = sizeof(int) + sizeof(uint16_t); |
57 | 0 | int family = BIO_ADDR_family(peer); |
58 | 0 | uint16_t port; |
59 | | |
60 | | /* For AF_UNSPEC (no peer address), use a minimal key */ |
61 | 0 | if (family == AF_UNSPEC) { |
62 | 0 | if (capacity < sizeof(family)) |
63 | 0 | return 0; |
64 | 0 | memcpy(hashkey, &family, sizeof(family)); |
65 | 0 | *out_len = sizeof(family); |
66 | 0 | return 1; |
67 | 0 | } |
68 | | |
69 | 0 | if (!BIO_ADDR_rawaddress(peer, NULL, &addr_len) |
70 | 0 | || capacity < header_len || addr_len > capacity - header_len) |
71 | 0 | return 0; |
72 | | |
73 | 0 | port = BIO_ADDR_rawport(peer); |
74 | 0 | memcpy(hashkey, &family, sizeof(family)); |
75 | 0 | memcpy(hashkey + sizeof(family), &port, sizeof(port)); |
76 | 0 | if (!BIO_ADDR_rawaddress(peer, hashkey + header_len, NULL)) |
77 | 0 | return 0; |
78 | | |
79 | 0 | *out_len = header_len + addr_len; |
80 | 0 | return 1; |
81 | 0 | } |
82 | | |
83 | | /* Return an allocated hash key for callers which need owned storage. */ |
84 | | static uint8_t *build_hashkey(const BIO_ADDR *peer, size_t *out_len) |
85 | 0 | { |
86 | 0 | uint8_t hashkey[ADDR_HASHKEY_MAX_LEN]; |
87 | |
|
88 | 0 | if (!build_hashkey_into(peer, hashkey, sizeof(hashkey), out_len)) |
89 | 0 | return NULL; |
90 | 0 | return OPENSSL_memdup(hashkey, *out_len); |
91 | 0 | } |
92 | | |
93 | | /* |
94 | | * Hash function for DGRAM_CONN_ENTRY. |
95 | | * Uses SipHash with a per-instance random key to defend against |
96 | | * hash-flooding CPU DoS attacks |
97 | | */ |
98 | | static unsigned long conn_entry_hash(const DGRAM_CONN_ENTRY *e) |
99 | 0 | { |
100 | 0 | SIPHASH siphash = { 0 }; |
101 | 0 | unsigned long hashval = 0; |
102 | |
|
103 | 0 | if (e->hashkey == NULL || e->hashkey_len == 0 || e->siphash_key == NULL) |
104 | 0 | return 0; |
105 | | |
106 | 0 | if (!SipHash_set_hash_size(&siphash, sizeof(unsigned long))) |
107 | 0 | return 0; |
108 | 0 | if (!SipHash_Init(&siphash, (const unsigned char *)e->siphash_key, 0, 0)) |
109 | 0 | return 0; |
110 | 0 | SipHash_Update(&siphash, e->hashkey, e->hashkey_len); |
111 | 0 | if (!SipHash_Final(&siphash, (unsigned char *)&hashval, sizeof(unsigned long))) |
112 | 0 | return 0; |
113 | | |
114 | 0 | return hashval; |
115 | 0 | } |
116 | | |
117 | | /* |
118 | | * Compare function for DGRAM_CONN_ENTRY - compares pre-built hashkeys. |
119 | | * Returns 0 if equal, non-zero otherwise. |
120 | | */ |
121 | | static int conn_entry_cmp(const DGRAM_CONN_ENTRY *a, const DGRAM_CONN_ENTRY *b) |
122 | 0 | { |
123 | 0 | if (a->hashkey_len != b->hashkey_len) |
124 | 0 | return 1; |
125 | 0 | if (a->hashkey == NULL || b->hashkey == NULL) |
126 | 0 | return (a->hashkey == b->hashkey) ? 0 : 1; |
127 | 0 | return memcmp(a->hashkey, b->hashkey, a->hashkey_len); |
128 | 0 | } |
129 | | |
130 | | /* |
131 | | * Free a connection entry (but not the connection itself). |
132 | | */ |
133 | | static void conn_entry_free(DGRAM_CONN_ENTRY *e) |
134 | 0 | { |
135 | 0 | if (e == NULL) |
136 | 0 | return; |
137 | 0 | OPENSSL_free(e->hashkey); |
138 | 0 | OPENSSL_free(e); |
139 | 0 | } |
140 | | |
141 | | /* |
142 | | * Callback for lh_DGRAM_CONN_ENTRY_doall to free all entries. |
143 | | */ |
144 | | static void conn_entry_free_cb(DGRAM_CONN_ENTRY *e) |
145 | 0 | { |
146 | 0 | conn_entry_free(e); |
147 | 0 | } |
148 | | |
149 | | /* |
150 | | * Lookup a connection by peer address from URXE. |
151 | | */ |
152 | | static SSL *addr_lookup(DGRAM_CONN_LOOKUP *lookup, const DGRAM_URXE *e) |
153 | 0 | { |
154 | 0 | ADDR_LOOKUP_DATA *data; |
155 | 0 | DGRAM_CONN_ENTRY key; |
156 | 0 | DGRAM_CONN_ENTRY *result; |
157 | |
|
158 | 0 | if (lookup == NULL || lookup->impl_data == NULL || e == NULL) |
159 | 0 | return NULL; |
160 | | |
161 | 0 | data = (ADDR_LOOKUP_DATA *)lookup->impl_data; |
162 | |
|
163 | 0 | memset(&key, 0, sizeof(key)); |
164 | 0 | BIO_ADDR_copy(&key.peer, &e->peer); |
165 | | |
166 | | /* Build hashkey for the lookup key */ |
167 | 0 | key.hashkey = build_hashkey(&e->peer, &key.hashkey_len); |
168 | 0 | if (key.hashkey == NULL) |
169 | 0 | return NULL; |
170 | | |
171 | | /* Set SipHash key pointer for hash function */ |
172 | 0 | key.siphash_key = data->hash_key; |
173 | |
|
174 | 0 | result = lh_DGRAM_CONN_ENTRY_retrieve(data->htable, &key); |
175 | |
|
176 | 0 | OPENSSL_free(key.hashkey); |
177 | |
|
178 | 0 | if (result == NULL) |
179 | 0 | return NULL; |
180 | | |
181 | 0 | return result->ssl; |
182 | 0 | } |
183 | | |
184 | | /* |
185 | | * Register a connection with peer address from URXE. |
186 | | */ |
187 | | static int addr_register_conn(DGRAM_CONN_LOOKUP *lookup, const DGRAM_URXE *e, |
188 | | SSL *ssl) |
189 | 0 | { |
190 | 0 | ADDR_LOOKUP_DATA *data; |
191 | 0 | DGRAM_CONN_ENTRY *entry, *old; |
192 | |
|
193 | 0 | if (lookup == NULL || lookup->impl_data == NULL || e == NULL || ssl == NULL) |
194 | 0 | return 0; |
195 | | |
196 | 0 | data = (ADDR_LOOKUP_DATA *)lookup->impl_data; |
197 | |
|
198 | 0 | entry = OPENSSL_zalloc(sizeof(*entry)); |
199 | 0 | if (entry == NULL) |
200 | 0 | return 0; |
201 | | |
202 | 0 | BIO_ADDR_copy(&entry->peer, &e->peer); |
203 | 0 | entry->ssl = ssl; |
204 | 0 | entry->hashkey = build_hashkey(&e->peer, &entry->hashkey_len); |
205 | 0 | if (entry->hashkey == NULL) { |
206 | 0 | OPENSSL_free(entry); |
207 | 0 | return 0; |
208 | 0 | } |
209 | | |
210 | | /* Set SipHash key pointer for hash function */ |
211 | 0 | entry->siphash_key = data->hash_key; |
212 | |
|
213 | 0 | old = lh_DGRAM_CONN_ENTRY_insert(data->htable, entry); |
214 | | |
215 | | /* Check if insert failed due to allocation error */ |
216 | 0 | if (lh_DGRAM_CONN_ENTRY_error(data->htable)) { |
217 | 0 | conn_entry_free(entry); |
218 | | /* Don't free old since it is still in the hash table since insert failed */ |
219 | 0 | return 0; |
220 | 0 | } |
221 | | |
222 | | /* |
223 | | * A non-NULL return from lh_DGRAM_CONN_ENTRY_insert means an entry with the |
224 | | * same key (peer address) was already present and has just been replaced. |
225 | | * This must not happen: callers always look up an address and only register |
226 | | * when no entry exists, all while holding the listener mutex, so the same peer |
227 | | * is never registered twice. We therefore expect old == NULL here. |
228 | | * |
229 | | * We deliberately do NOT free old->ssl: this lookup table is an |
230 | | * ownership-agnostic index and does not own the SSL objects it stores. |
231 | | * The owning layer (the DTLS listener) is responsible for the lifecycle of |
232 | | * those SSL objects. Freeing one here would be a use-after-free for the |
233 | | * non-owning established_conns table. |
234 | | */ |
235 | 0 | if (!ossl_assert(old == NULL)) |
236 | 0 | conn_entry_free(old); |
237 | |
|
238 | 0 | return 1; |
239 | 0 | } |
240 | | |
241 | | /* |
242 | | * Register a connection by peer address from BIO_ADDR directly. |
243 | | * This is used when we don't have a URXE available. |
244 | | */ |
245 | | static int addr_register_conn_addr(DGRAM_CONN_LOOKUP *lookup, const BIO_ADDR *peer, |
246 | | SSL *ssl) |
247 | 0 | { |
248 | 0 | ADDR_LOOKUP_DATA *data; |
249 | 0 | DGRAM_CONN_ENTRY *entry, *old; |
250 | |
|
251 | 0 | if (lookup == NULL || lookup->impl_data == NULL || peer == NULL || ssl == NULL) |
252 | 0 | return 0; |
253 | | |
254 | 0 | data = (ADDR_LOOKUP_DATA *)lookup->impl_data; |
255 | |
|
256 | 0 | entry = OPENSSL_zalloc(sizeof(*entry)); |
257 | 0 | if (entry == NULL) |
258 | 0 | return 0; |
259 | | |
260 | 0 | BIO_ADDR_copy(&entry->peer, peer); |
261 | 0 | entry->ssl = ssl; |
262 | 0 | entry->hashkey = build_hashkey(peer, &entry->hashkey_len); |
263 | 0 | if (entry->hashkey == NULL) { |
264 | 0 | OPENSSL_free(entry); |
265 | 0 | return 0; |
266 | 0 | } |
267 | | |
268 | | /* Set SipHash key pointer for hash function */ |
269 | 0 | entry->siphash_key = data->hash_key; |
270 | |
|
271 | 0 | old = lh_DGRAM_CONN_ENTRY_insert(data->htable, entry); |
272 | | |
273 | | /* Check if insert failed due to allocation error */ |
274 | 0 | if (lh_DGRAM_CONN_ENTRY_error(data->htable)) { |
275 | 0 | conn_entry_free(entry); |
276 | | /* Don't free old since it is still in the hash table since insert failed */ |
277 | 0 | return 0; |
278 | 0 | } |
279 | | |
280 | | /* |
281 | | * A non-NULL return from lh_DGRAM_CONN_ENTRY_insert means an entry with the |
282 | | * same key(peer address) was already present and has just been replaced. This |
283 | | * must not happen: callers always look up an address and only register when no |
284 | | * entry exists, all while holding the listener mutex, so the same peer is |
285 | | * never registered twice. We therefore expect old == NULL here. |
286 | | * |
287 | | * We deliberately do NOT free old->ssl: this lookup table is an |
288 | | * ownership-agnostic index and does not own the SSL objects it stores. |
289 | | * The owning layer (the DTLS listener) is responsible for the lifecycle of |
290 | | * those SSL objects. Freeing one here would be a use-after-free for the |
291 | | * non-owning established_conns table. |
292 | | */ |
293 | 0 | if (!ossl_assert(old == NULL)) |
294 | 0 | conn_entry_free(old); |
295 | |
|
296 | 0 | return 1; |
297 | 0 | } |
298 | | |
299 | | /* |
300 | | * Unregister a connection by peer address. |
301 | | */ |
302 | | static int addr_unregister_conn(DGRAM_CONN_LOOKUP *lookup, const BIO_ADDR *peer) |
303 | 0 | { |
304 | 0 | ADDR_LOOKUP_DATA *data; |
305 | 0 | DGRAM_CONN_ENTRY lookup_key; |
306 | 0 | DGRAM_CONN_ENTRY *removed; |
307 | 0 | uint8_t hashkey[ADDR_HASHKEY_MAX_LEN]; |
308 | |
|
309 | 0 | if (lookup == NULL || lookup->impl_data == NULL || peer == NULL) |
310 | 0 | return 0; |
311 | | |
312 | 0 | data = (ADDR_LOOKUP_DATA *)lookup->impl_data; |
313 | |
|
314 | 0 | memset(&lookup_key, 0, sizeof(lookup_key)); |
315 | 0 | BIO_ADDR_copy(&lookup_key.peer, peer); |
316 | | |
317 | | /* Cleanup must not depend on allocating a temporary lookup key. */ |
318 | 0 | if (!build_hashkey_into(peer, hashkey, sizeof(hashkey), |
319 | 0 | &lookup_key.hashkey_len)) |
320 | 0 | return 0; |
321 | 0 | lookup_key.hashkey = hashkey; |
322 | | |
323 | | /* Set SipHash key pointer for hash function */ |
324 | 0 | lookup_key.siphash_key = data->hash_key; |
325 | |
|
326 | 0 | removed = lh_DGRAM_CONN_ENTRY_delete(data->htable, &lookup_key); |
327 | |
|
328 | 0 | if (removed != NULL) { |
329 | 0 | conn_entry_free(removed); |
330 | 0 | return 1; |
331 | 0 | } |
332 | | |
333 | 0 | return 0; |
334 | 0 | } |
335 | | |
336 | | /* |
337 | | * Free the lookup structure and all entries. |
338 | | */ |
339 | | static void addr_free(DGRAM_CONN_LOOKUP *lookup) |
340 | 0 | { |
341 | 0 | ADDR_LOOKUP_DATA *data; |
342 | |
|
343 | 0 | if (lookup == NULL) |
344 | 0 | return; |
345 | | |
346 | 0 | if (lookup->impl_data != NULL) { |
347 | 0 | data = (ADDR_LOOKUP_DATA *)lookup->impl_data; |
348 | 0 | if (data->htable != NULL) { |
349 | 0 | lh_DGRAM_CONN_ENTRY_doall(data->htable, conn_entry_free_cb); |
350 | 0 | lh_DGRAM_CONN_ENTRY_free(data->htable); |
351 | 0 | } |
352 | 0 | OPENSSL_free(data); |
353 | 0 | } |
354 | |
|
355 | 0 | OPENSSL_free(lookup); |
356 | 0 | } |
357 | | |
358 | | /* |
359 | | * Context structure for iteration callback. |
360 | | */ |
361 | | typedef struct { |
362 | | ossl_dgram_conn_lookup_iter_fn user_cb; |
363 | | void *user_arg; |
364 | | } ADDR_FOREACH_CTX; |
365 | | |
366 | | /* |
367 | | * Internal callback for lh_doall_arg that invokes the user's callback. |
368 | | */ |
369 | | static void addr_foreach_cb(DGRAM_CONN_ENTRY *e, void *arg) |
370 | 0 | { |
371 | 0 | ADDR_FOREACH_CTX *ctx = arg; |
372 | |
|
373 | 0 | if (ctx->user_cb != NULL) |
374 | 0 | ctx->user_cb(e->ssl, &e->peer, ctx->user_arg); |
375 | 0 | } |
376 | | |
377 | | IMPLEMENT_LHASH_DOALL_ARG(DGRAM_CONN_ENTRY, void); |
378 | | |
379 | | /* |
380 | | * Iterate over all connections, calling the callback for each. |
381 | | */ |
382 | | static void addr_foreach(DGRAM_CONN_LOOKUP *lookup, |
383 | | ossl_dgram_conn_lookup_iter_fn cb, void *arg) |
384 | 0 | { |
385 | 0 | ADDR_LOOKUP_DATA *data; |
386 | 0 | ADDR_FOREACH_CTX ctx; |
387 | |
|
388 | 0 | if (lookup == NULL || lookup->impl_data == NULL || cb == NULL) |
389 | 0 | return; |
390 | | |
391 | 0 | data = (ADDR_LOOKUP_DATA *)lookup->impl_data; |
392 | 0 | ctx.user_cb = cb; |
393 | 0 | ctx.user_arg = arg; |
394 | |
|
395 | 0 | lh_DGRAM_CONN_ENTRY_doall_void(data->htable, addr_foreach_cb, &ctx); |
396 | 0 | } |
397 | | |
398 | | static size_t addr_num_items(const DGRAM_CONN_LOOKUP *lookup) |
399 | 0 | { |
400 | 0 | ADDR_LOOKUP_DATA *data; |
401 | |
|
402 | 0 | if (lookup == NULL || lookup->impl_data == NULL) |
403 | 0 | return 0; |
404 | | |
405 | 0 | data = (ADDR_LOOKUP_DATA *)lookup->impl_data; |
406 | 0 | return lh_DGRAM_CONN_ENTRY_num_items(data->htable); |
407 | 0 | } |
408 | | |
409 | | static const DGRAM_CONN_LOOKUP_METHODS addr_methods = { |
410 | | addr_lookup, |
411 | | addr_register_conn, |
412 | | addr_register_conn_addr, |
413 | | addr_unregister_conn, |
414 | | addr_foreach, |
415 | | addr_free, |
416 | | addr_num_items |
417 | | }; |
418 | | |
419 | | /* |
420 | | * Create a new address-based connection lookup for DTLS. |
421 | | */ |
422 | | DGRAM_CONN_LOOKUP *ossl_dgram_conn_lookup_new_addr(void) |
423 | 0 | { |
424 | 0 | DGRAM_CONN_LOOKUP *lookup; |
425 | 0 | ADDR_LOOKUP_DATA *data; |
426 | |
|
427 | 0 | lookup = OPENSSL_zalloc(sizeof(*lookup)); |
428 | 0 | if (lookup == NULL) |
429 | 0 | return NULL; |
430 | | |
431 | 0 | data = OPENSSL_zalloc(sizeof(*data)); |
432 | 0 | if (data == NULL) { |
433 | 0 | OPENSSL_free(lookup); |
434 | 0 | return NULL; |
435 | 0 | } |
436 | | |
437 | | /* Generate random SipHash key for hash-flooding defense */ |
438 | 0 | if (RAND_priv_bytes((unsigned char *)data->hash_key, |
439 | 0 | sizeof(data->hash_key)) |
440 | 0 | <= 0) { |
441 | 0 | OPENSSL_free(data); |
442 | 0 | OPENSSL_free(lookup); |
443 | 0 | return NULL; |
444 | 0 | } |
445 | | |
446 | 0 | data->htable = lh_DGRAM_CONN_ENTRY_new(conn_entry_hash, conn_entry_cmp); |
447 | 0 | if (data->htable == NULL) { |
448 | 0 | OPENSSL_free(data); |
449 | 0 | OPENSSL_free(lookup); |
450 | 0 | return NULL; |
451 | 0 | } |
452 | | |
453 | 0 | lookup->methods = &addr_methods; |
454 | 0 | lookup->impl_data = data; |
455 | |
|
456 | 0 | return lookup; |
457 | 0 | } |
458 | | |
459 | | /* |
460 | | * Public API wrappers - call through methods table. |
461 | | */ |
462 | | SSL *ossl_dgram_conn_lookup_find(DGRAM_CONN_LOOKUP *lookup, const DGRAM_URXE *e) |
463 | 0 | { |
464 | 0 | if (lookup == NULL || lookup->methods == NULL |
465 | 0 | || lookup->methods->lookup == NULL) |
466 | 0 | return NULL; |
467 | 0 | return lookup->methods->lookup(lookup, e); |
468 | 0 | } |
469 | | |
470 | | int ossl_dgram_conn_lookup_register(DGRAM_CONN_LOOKUP *lookup, |
471 | | const DGRAM_URXE *e, SSL *ssl) |
472 | 0 | { |
473 | 0 | if (lookup == NULL || lookup->methods == NULL |
474 | 0 | || lookup->methods->register_conn == NULL) |
475 | 0 | return 0; |
476 | 0 | return lookup->methods->register_conn(lookup, e, ssl); |
477 | 0 | } |
478 | | |
479 | | int ossl_dgram_conn_lookup_register_addr(DGRAM_CONN_LOOKUP *lookup, |
480 | | const BIO_ADDR *peer, SSL *ssl) |
481 | 0 | { |
482 | 0 | if (lookup == NULL || lookup->methods == NULL |
483 | 0 | || lookup->methods->register_conn_addr == NULL) |
484 | 0 | return 0; |
485 | 0 | return lookup->methods->register_conn_addr(lookup, peer, ssl); |
486 | 0 | } |
487 | | |
488 | | int ossl_dgram_conn_lookup_unregister(DGRAM_CONN_LOOKUP *lookup, |
489 | | const BIO_ADDR *peer) |
490 | 0 | { |
491 | 0 | if (lookup == NULL || lookup->methods == NULL |
492 | 0 | || lookup->methods->unregister_conn == NULL) |
493 | 0 | return 0; |
494 | 0 | return lookup->methods->unregister_conn(lookup, peer); |
495 | 0 | } |
496 | | |
497 | | void ossl_dgram_conn_lookup_foreach(DGRAM_CONN_LOOKUP *lookup, |
498 | | ossl_dgram_conn_lookup_iter_fn cb, void *arg) |
499 | 0 | { |
500 | 0 | if (lookup == NULL || lookup->methods == NULL |
501 | 0 | || lookup->methods->foreach == NULL) |
502 | 0 | return; |
503 | 0 | lookup->methods->foreach(lookup, cb, arg); |
504 | 0 | } |
505 | | |
506 | | void ossl_dgram_conn_lookup_free(DGRAM_CONN_LOOKUP *lookup) |
507 | 0 | { |
508 | 0 | if (lookup == NULL || lookup->methods == NULL |
509 | 0 | || lookup->methods->free == NULL) |
510 | 0 | return; |
511 | 0 | lookup->methods->free(lookup); |
512 | 0 | } |
513 | | |
514 | | /* |
515 | | * Return the number of entries currently registered. |
516 | | * |
517 | | * The lookup is not internally synchronised: like the other lookup |
518 | | * operations, the caller must hold the lock that serialises access to it. |
519 | | * Reading the count without that lock held races with concurrent |
520 | | * register/unregister. |
521 | | */ |
522 | | size_t ossl_dgram_conn_lookup_num_items(const DGRAM_CONN_LOOKUP *lookup) |
523 | 0 | { |
524 | 0 | if (lookup == NULL || lookup->methods == NULL |
525 | 0 | || lookup->methods->num_items == NULL) |
526 | 0 | return 0; |
527 | 0 | return lookup->methods->num_items(lookup); |
528 | 0 | } |
529 | | |
530 | | #endif /* OPENSSL_NO_DTLS */ |