diff options
Diffstat (limited to 'lib/dns/portlist.c')
-rw-r--r-- | lib/dns/portlist.c | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/lib/dns/portlist.c b/lib/dns/portlist.c new file mode 100644 index 0000000..c1d523c --- /dev/null +++ b/lib/dns/portlist.c @@ -0,0 +1,252 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> + +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/mutex.h> +#include <isc/net.h> +#include <isc/refcount.h> +#include <isc/result.h> +#include <isc/string.h> +#include <isc/types.h> +#include <isc/util.h> + +#include <dns/portlist.h> +#include <dns/types.h> + +#define DNS_PORTLIST_MAGIC ISC_MAGIC('P', 'L', 'S', 'T') +#define DNS_VALID_PORTLIST(p) ISC_MAGIC_VALID(p, DNS_PORTLIST_MAGIC) + +typedef struct dns_element { + in_port_t port; + uint16_t flags; +} dns_element_t; + +struct dns_portlist { + unsigned int magic; + isc_mem_t *mctx; + isc_refcount_t refcount; + isc_mutex_t lock; + dns_element_t *list; + unsigned int allocated; + unsigned int active; +}; + +#define DNS_PL_INET 0x0001 +#define DNS_PL_INET6 0x0002 +#define DNS_PL_ALLOCATE 16 + +static int +compare(const void *arg1, const void *arg2) { + const dns_element_t *e1 = (const dns_element_t *)arg1; + const dns_element_t *e2 = (const dns_element_t *)arg2; + + if (e1->port < e2->port) { + return (-1); + } + if (e1->port > e2->port) { + return (1); + } + return (0); +} + +isc_result_t +dns_portlist_create(isc_mem_t *mctx, dns_portlist_t **portlistp) { + dns_portlist_t *portlist; + + REQUIRE(portlistp != NULL && *portlistp == NULL); + + portlist = isc_mem_get(mctx, sizeof(*portlist)); + isc_mutex_init(&portlist->lock); + isc_refcount_init(&portlist->refcount, 1); + portlist->list = NULL; + portlist->allocated = 0; + portlist->active = 0; + portlist->mctx = NULL; + isc_mem_attach(mctx, &portlist->mctx); + portlist->magic = DNS_PORTLIST_MAGIC; + *portlistp = portlist; + return (ISC_R_SUCCESS); +} + +static dns_element_t * +find_port(dns_element_t *list, unsigned int len, in_port_t port) { + unsigned int xtry = len / 2; + unsigned int min = 0; + unsigned int max = len - 1; + unsigned int last = len; + + for (;;) { + if (list[xtry].port == port) { + return (&list[xtry]); + } + if (port > list[xtry].port) { + if (xtry == max) { + break; + } + min = xtry; + xtry = xtry + (max - xtry + 1) / 2; + INSIST(xtry <= max); + if (xtry == last) { + break; + } + last = min; + } else { + if (xtry == min) { + break; + } + max = xtry; + xtry = xtry - (xtry - min + 1) / 2; + INSIST(xtry >= min); + if (xtry == last) { + break; + } + last = max; + } + } + return (NULL); +} + +isc_result_t +dns_portlist_add(dns_portlist_t *portlist, int af, in_port_t port) { + dns_element_t *el; + isc_result_t result; + + REQUIRE(DNS_VALID_PORTLIST(portlist)); + REQUIRE(af == AF_INET || af == AF_INET6); + + LOCK(&portlist->lock); + if (portlist->active != 0) { + el = find_port(portlist->list, portlist->active, port); + if (el != NULL) { + if (af == AF_INET) { + el->flags |= DNS_PL_INET; + } else { + el->flags |= DNS_PL_INET6; + } + result = ISC_R_SUCCESS; + goto unlock; + } + } + + if (portlist->allocated <= portlist->active) { + unsigned int allocated; + allocated = portlist->allocated + DNS_PL_ALLOCATE; + el = isc_mem_get(portlist->mctx, sizeof(*el) * allocated); + if (portlist->list != NULL) { + memmove(el, portlist->list, + portlist->allocated * sizeof(*el)); + isc_mem_put(portlist->mctx, portlist->list, + portlist->allocated * sizeof(*el)); + } + portlist->list = el; + portlist->allocated = allocated; + } + portlist->list[portlist->active].port = port; + if (af == AF_INET) { + portlist->list[portlist->active].flags = DNS_PL_INET; + } else { + portlist->list[portlist->active].flags = DNS_PL_INET6; + } + portlist->active++; + qsort(portlist->list, portlist->active, sizeof(*el), compare); + result = ISC_R_SUCCESS; +unlock: + UNLOCK(&portlist->lock); + return (result); +} + +void +dns_portlist_remove(dns_portlist_t *portlist, int af, in_port_t port) { + dns_element_t *el; + + REQUIRE(DNS_VALID_PORTLIST(portlist)); + REQUIRE(af == AF_INET || af == AF_INET6); + + LOCK(&portlist->lock); + if (portlist->active != 0) { + el = find_port(portlist->list, portlist->active, port); + if (el != NULL) { + if (af == AF_INET) { + el->flags &= ~DNS_PL_INET; + } else { + el->flags &= ~DNS_PL_INET6; + } + if (el->flags == 0) { + *el = portlist->list[portlist->active]; + portlist->active--; + qsort(portlist->list, portlist->active, + sizeof(*el), compare); + } + } + } + UNLOCK(&portlist->lock); +} + +bool +dns_portlist_match(dns_portlist_t *portlist, int af, in_port_t port) { + dns_element_t *el; + bool result = false; + + REQUIRE(DNS_VALID_PORTLIST(portlist)); + REQUIRE(af == AF_INET || af == AF_INET6); + LOCK(&portlist->lock); + if (portlist->active != 0) { + el = find_port(portlist->list, portlist->active, port); + if (el != NULL) { + if (af == AF_INET && (el->flags & DNS_PL_INET) != 0) { + result = true; + } + if (af == AF_INET6 && (el->flags & DNS_PL_INET6) != 0) { + result = true; + } + } + } + UNLOCK(&portlist->lock); + return (result); +} + +void +dns_portlist_attach(dns_portlist_t *portlist, dns_portlist_t **portlistp) { + REQUIRE(DNS_VALID_PORTLIST(portlist)); + REQUIRE(portlistp != NULL && *portlistp == NULL); + + isc_refcount_increment(&portlist->refcount); + *portlistp = portlist; +} + +void +dns_portlist_detach(dns_portlist_t **portlistp) { + REQUIRE(portlistp != NULL && DNS_VALID_PORTLIST(*portlistp)); + dns_portlist_t *portlist = *portlistp; + *portlistp = NULL; + + if (isc_refcount_decrement(&portlist->refcount) == 1) { + portlist->magic = 0; + isc_refcount_destroy(&portlist->refcount); + if (portlist->list != NULL) { + isc_mem_put(portlist->mctx, portlist->list, + portlist->allocated * + sizeof(*portlist->list)); + } + isc_mutex_destroy(&portlist->lock); + isc_mem_putanddetach(&portlist->mctx, portlist, + sizeof(*portlist)); + } +} |