1/*
2 * Copyright (C) 2004-2007  Internet Systems Consortium, Inc. ("ISC")
3 * Copyright (C) 2003  Internet Software Consortium.
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
10 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
12 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15 * PERFORMANCE OF THIS SOFTWARE.
16 */
17
18/* $Id: portlist.c,v 1.13 2007/06/19 23:47:16 tbox Exp $ */
19
20/*! \file */
21
22#include <config.h>
23
24#include <stdlib.h>
25
26#include <isc/magic.h>
27#include <isc/mem.h>
28#include <isc/mutex.h>
29#include <isc/net.h>
30#include <isc/refcount.h>
31#include <isc/result.h>
32#include <isc/string.h>
33#include <isc/types.h>
34#include <isc/util.h>
35
36#include <dns/types.h>
37#include <dns/portlist.h>
38
39#define DNS_PORTLIST_MAGIC	ISC_MAGIC('P','L','S','T')
40#define DNS_VALID_PORTLIST(p)	ISC_MAGIC_VALID(p, DNS_PORTLIST_MAGIC)
41
42typedef struct dns_element {
43	in_port_t	port;
44	isc_uint16_t	flags;
45} dns_element_t;
46
47struct dns_portlist {
48	unsigned int	magic;
49	isc_mem_t	*mctx;
50	isc_refcount_t	refcount;
51	isc_mutex_t	lock;
52	dns_element_t 	*list;
53	unsigned int	allocated;
54	unsigned int	active;
55};
56
57#define DNS_PL_INET	0x0001
58#define DNS_PL_INET6	0x0002
59#define DNS_PL_ALLOCATE	16
60
61static int
62compare(const void *arg1, const void *arg2) {
63	const dns_element_t *e1 = (const dns_element_t *)arg1;
64	const dns_element_t *e2 = (const dns_element_t *)arg2;
65
66	if (e1->port < e2->port)
67		return (-1);
68	if (e1->port > e2->port)
69		return (1);
70	return (0);
71}
72
73isc_result_t
74dns_portlist_create(isc_mem_t *mctx, dns_portlist_t **portlistp) {
75	dns_portlist_t *portlist;
76	isc_result_t result;
77
78	REQUIRE(portlistp != NULL && *portlistp == NULL);
79
80	portlist = isc_mem_get(mctx, sizeof(*portlist));
81	if (portlist == NULL)
82		return (ISC_R_NOMEMORY);
83        result = isc_mutex_init(&portlist->lock);
84	if (result != ISC_R_SUCCESS) {
85		isc_mem_put(mctx, portlist, sizeof(*portlist));
86		return (result);
87	}
88	result = isc_refcount_init(&portlist->refcount, 1);
89	if (result != ISC_R_SUCCESS) {
90		DESTROYLOCK(&portlist->lock);
91		isc_mem_put(mctx, portlist, sizeof(*portlist));
92		return (result);
93	}
94	portlist->list = NULL;
95	portlist->allocated = 0;
96	portlist->active = 0;
97	portlist->mctx = NULL;
98	isc_mem_attach(mctx, &portlist->mctx);
99	portlist->magic = DNS_PORTLIST_MAGIC;
100	*portlistp = portlist;
101	return (ISC_R_SUCCESS);
102}
103
104static dns_element_t *
105find_port(dns_element_t *list, unsigned int len, in_port_t port) {
106	unsigned int xtry = len / 2;
107	unsigned int min = 0;
108	unsigned int max = len - 1;
109	unsigned int last = len;
110
111	for (;;) {
112		if (list[xtry].port == port)
113			return (&list[xtry]);
114	        if (port > list[xtry].port) {
115			if (xtry == max)
116				break;
117			min = xtry;
118			xtry = xtry + (max - xtry + 1) / 2;
119			INSIST(xtry <= max);
120			if (xtry == last)
121				break;
122			last = min;
123		} else {
124			if (xtry == min)
125				break;
126			max = xtry;
127			xtry = xtry - (xtry - min + 1) / 2;
128			INSIST(xtry >= min);
129			if (xtry == last)
130				break;
131			last = max;
132		}
133	}
134	return (NULL);
135}
136
137isc_result_t
138dns_portlist_add(dns_portlist_t *portlist, int af, in_port_t port) {
139	dns_element_t *el;
140	isc_result_t result;
141
142	REQUIRE(DNS_VALID_PORTLIST(portlist));
143	REQUIRE(af == AF_INET || af == AF_INET6);
144
145	LOCK(&portlist->lock);
146	if (portlist->active != 0) {
147		el = find_port(portlist->list, portlist->active, port);
148		if (el != NULL) {
149			if (af == AF_INET)
150				el->flags |= DNS_PL_INET;
151			else
152				el->flags |= DNS_PL_INET6;
153			result = ISC_R_SUCCESS;
154			goto unlock;
155		}
156	}
157
158	if (portlist->allocated <= portlist->active) {
159		unsigned int allocated;
160		allocated = portlist->allocated + DNS_PL_ALLOCATE;
161		el = isc_mem_get(portlist->mctx, sizeof(*el) * allocated);
162		if (el == NULL) {
163			result = ISC_R_NOMEMORY;
164			goto unlock;
165		}
166		if (portlist->list != NULL) {
167			memcpy(el, portlist->list,
168			       portlist->allocated * sizeof(*el));
169			isc_mem_put(portlist->mctx, portlist->list,
170				    portlist->allocated * sizeof(*el));
171		}
172		portlist->list = el;
173		portlist->allocated = allocated;
174	}
175	portlist->list[portlist->active].port = port;
176	if (af == AF_INET)
177		portlist->list[portlist->active].flags = DNS_PL_INET;
178	else
179		portlist->list[portlist->active].flags = DNS_PL_INET6;
180	portlist->active++;
181	qsort(portlist->list, portlist->active, sizeof(*el), compare);
182	result = ISC_R_SUCCESS;
183 unlock:
184	UNLOCK(&portlist->lock);
185	return (result);
186}
187
188void
189dns_portlist_remove(dns_portlist_t *portlist, int af, in_port_t port) {
190	dns_element_t *el;
191
192	REQUIRE(DNS_VALID_PORTLIST(portlist));
193	REQUIRE(af == AF_INET || af == AF_INET6);
194
195	LOCK(&portlist->lock);
196	if (portlist->active != 0) {
197		el = find_port(portlist->list, portlist->active, port);
198		if (el != NULL) {
199			if (af == AF_INET)
200				el->flags &= ~DNS_PL_INET;
201			else
202				el->flags &= ~DNS_PL_INET6;
203			if (el->flags == 0) {
204				*el = portlist->list[portlist->active];
205				portlist->active--;
206				qsort(portlist->list, portlist->active,
207				      sizeof(*el), compare);
208			}
209		}
210	}
211	UNLOCK(&portlist->lock);
212}
213
214isc_boolean_t
215dns_portlist_match(dns_portlist_t *portlist, int af, in_port_t port) {
216	dns_element_t *el;
217	isc_boolean_t result = ISC_FALSE;
218
219	REQUIRE(DNS_VALID_PORTLIST(portlist));
220	REQUIRE(af == AF_INET || af == AF_INET6);
221	LOCK(&portlist->lock);
222	if (portlist->active != 0) {
223		el = find_port(portlist->list, portlist->active, port);
224		if (el != NULL) {
225			if (af == AF_INET && (el->flags & DNS_PL_INET) != 0)
226				result = ISC_TRUE;
227			if (af == AF_INET6 && (el->flags & DNS_PL_INET6) != 0)
228				result = ISC_TRUE;
229		}
230	}
231	UNLOCK(&portlist->lock);
232	return (result);
233}
234
235void
236dns_portlist_attach(dns_portlist_t *portlist, dns_portlist_t **portlistp) {
237
238	REQUIRE(DNS_VALID_PORTLIST(portlist));
239	REQUIRE(portlistp != NULL && *portlistp == NULL);
240
241	isc_refcount_increment(&portlist->refcount, NULL);
242	*portlistp = portlist;
243}
244
245void
246dns_portlist_detach(dns_portlist_t **portlistp) {
247	dns_portlist_t *portlist;
248	unsigned int count;
249
250	REQUIRE(portlistp != NULL);
251	portlist = *portlistp;
252	REQUIRE(DNS_VALID_PORTLIST(portlist));
253	*portlistp = NULL;
254	isc_refcount_decrement(&portlist->refcount, &count);
255	if (count == 0) {
256		portlist->magic = 0;
257		isc_refcount_destroy(&portlist->refcount);
258		if (portlist->list != NULL)
259			isc_mem_put(portlist->mctx, portlist->list,
260				    portlist->allocated *
261				    sizeof(*portlist->list));
262		DESTROYLOCK(&portlist->lock);
263		isc_mem_putanddetach(&portlist->mctx, portlist,
264				     sizeof(*portlist));
265	}
266}
267