1 /*
2  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3  *
4  * This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7  *
8  * See the COPYRIGHT file distributed with this work for additional
9  * information regarding copyright ownership.
10  */
11 
12 /*! \file */
13 
14 #include <inttypes.h>
15 #include <stdbool.h>
16 #include <stdlib.h>
17 
18 #include <isc/magic.h>
19 #include <isc/mem.h>
20 #include <isc/mutex.h>
21 #include <isc/net.h>
22 #include <isc/refcount.h>
23 #include <isc/result.h>
24 #include <isc/string.h>
25 #include <isc/types.h>
26 #include <isc/util.h>
27 
28 #include <dns/portlist.h>
29 #include <dns/types.h>
30 
31 #define DNS_PORTLIST_MAGIC    ISC_MAGIC('P', 'L', 'S', 'T')
32 #define DNS_VALID_PORTLIST(p) ISC_MAGIC_VALID(p, DNS_PORTLIST_MAGIC)
33 
34 typedef struct dns_element {
35 	in_port_t port;
36 	uint16_t flags;
37 } dns_element_t;
38 
39 struct dns_portlist {
40 	unsigned int magic;
41 	isc_mem_t *mctx;
42 	isc_refcount_t refcount;
43 	isc_mutex_t lock;
44 	dns_element_t *list;
45 	unsigned int allocated;
46 	unsigned int active;
47 };
48 
49 #define DNS_PL_INET	0x0001
50 #define DNS_PL_INET6	0x0002
51 #define DNS_PL_ALLOCATE 16
52 
53 static int
compare(const void * arg1,const void * arg2)54 compare(const void *arg1, const void *arg2) {
55 	const dns_element_t *e1 = (const dns_element_t *)arg1;
56 	const dns_element_t *e2 = (const dns_element_t *)arg2;
57 
58 	if (e1->port < e2->port) {
59 		return (-1);
60 	}
61 	if (e1->port > e2->port) {
62 		return (1);
63 	}
64 	return (0);
65 }
66 
67 isc_result_t
dns_portlist_create(isc_mem_t * mctx,dns_portlist_t ** portlistp)68 dns_portlist_create(isc_mem_t *mctx, dns_portlist_t **portlistp) {
69 	dns_portlist_t *portlist;
70 
71 	REQUIRE(portlistp != NULL && *portlistp == NULL);
72 
73 	portlist = isc_mem_get(mctx, sizeof(*portlist));
74 	isc_mutex_init(&portlist->lock);
75 	isc_refcount_init(&portlist->refcount, 1);
76 	portlist->list = NULL;
77 	portlist->allocated = 0;
78 	portlist->active = 0;
79 	portlist->mctx = NULL;
80 	isc_mem_attach(mctx, &portlist->mctx);
81 	portlist->magic = DNS_PORTLIST_MAGIC;
82 	*portlistp = portlist;
83 	return (ISC_R_SUCCESS);
84 }
85 
86 static dns_element_t *
find_port(dns_element_t * list,unsigned int len,in_port_t port)87 find_port(dns_element_t *list, unsigned int len, in_port_t port) {
88 	unsigned int xtry = len / 2;
89 	unsigned int min = 0;
90 	unsigned int max = len - 1;
91 	unsigned int last = len;
92 
93 	for (;;) {
94 		if (list[xtry].port == port) {
95 			return (&list[xtry]);
96 		}
97 		if (port > list[xtry].port) {
98 			if (xtry == max) {
99 				break;
100 			}
101 			min = xtry;
102 			xtry = xtry + (max - xtry + 1) / 2;
103 			INSIST(xtry <= max);
104 			if (xtry == last) {
105 				break;
106 			}
107 			last = min;
108 		} else {
109 			if (xtry == min) {
110 				break;
111 			}
112 			max = xtry;
113 			xtry = xtry - (xtry - min + 1) / 2;
114 			INSIST(xtry >= min);
115 			if (xtry == last) {
116 				break;
117 			}
118 			last = max;
119 		}
120 	}
121 	return (NULL);
122 }
123 
124 isc_result_t
dns_portlist_add(dns_portlist_t * portlist,int af,in_port_t port)125 dns_portlist_add(dns_portlist_t *portlist, int af, in_port_t port) {
126 	dns_element_t *el;
127 	isc_result_t result;
128 
129 	REQUIRE(DNS_VALID_PORTLIST(portlist));
130 	REQUIRE(af == AF_INET || af == AF_INET6);
131 
132 	LOCK(&portlist->lock);
133 	if (portlist->active != 0) {
134 		el = find_port(portlist->list, portlist->active, port);
135 		if (el != NULL) {
136 			if (af == AF_INET) {
137 				el->flags |= DNS_PL_INET;
138 			} else {
139 				el->flags |= DNS_PL_INET6;
140 			}
141 			result = ISC_R_SUCCESS;
142 			goto unlock;
143 		}
144 	}
145 
146 	if (portlist->allocated <= portlist->active) {
147 		unsigned int allocated;
148 		allocated = portlist->allocated + DNS_PL_ALLOCATE;
149 		el = isc_mem_get(portlist->mctx, sizeof(*el) * allocated);
150 		if (portlist->list != NULL) {
151 			memmove(el, portlist->list,
152 				portlist->allocated * sizeof(*el));
153 			isc_mem_put(portlist->mctx, portlist->list,
154 				    portlist->allocated * sizeof(*el));
155 		}
156 		portlist->list = el;
157 		portlist->allocated = allocated;
158 	}
159 	portlist->list[portlist->active].port = port;
160 	if (af == AF_INET) {
161 		portlist->list[portlist->active].flags = DNS_PL_INET;
162 	} else {
163 		portlist->list[portlist->active].flags = DNS_PL_INET6;
164 	}
165 	portlist->active++;
166 	qsort(portlist->list, portlist->active, sizeof(*el), compare);
167 	result = ISC_R_SUCCESS;
168 unlock:
169 	UNLOCK(&portlist->lock);
170 	return (result);
171 }
172 
173 void
dns_portlist_remove(dns_portlist_t * portlist,int af,in_port_t port)174 dns_portlist_remove(dns_portlist_t *portlist, int af, in_port_t port) {
175 	dns_element_t *el;
176 
177 	REQUIRE(DNS_VALID_PORTLIST(portlist));
178 	REQUIRE(af == AF_INET || af == AF_INET6);
179 
180 	LOCK(&portlist->lock);
181 	if (portlist->active != 0) {
182 		el = find_port(portlist->list, portlist->active, port);
183 		if (el != NULL) {
184 			if (af == AF_INET) {
185 				el->flags &= ~DNS_PL_INET;
186 			} else {
187 				el->flags &= ~DNS_PL_INET6;
188 			}
189 			if (el->flags == 0) {
190 				*el = portlist->list[portlist->active];
191 				portlist->active--;
192 				qsort(portlist->list, portlist->active,
193 				      sizeof(*el), compare);
194 			}
195 		}
196 	}
197 	UNLOCK(&portlist->lock);
198 }
199 
200 bool
dns_portlist_match(dns_portlist_t * portlist,int af,in_port_t port)201 dns_portlist_match(dns_portlist_t *portlist, int af, in_port_t port) {
202 	dns_element_t *el;
203 	bool result = false;
204 
205 	REQUIRE(DNS_VALID_PORTLIST(portlist));
206 	REQUIRE(af == AF_INET || af == AF_INET6);
207 	LOCK(&portlist->lock);
208 	if (portlist->active != 0) {
209 		el = find_port(portlist->list, portlist->active, port);
210 		if (el != NULL) {
211 			if (af == AF_INET && (el->flags & DNS_PL_INET) != 0) {
212 				result = true;
213 			}
214 			if (af == AF_INET6 && (el->flags & DNS_PL_INET6) != 0) {
215 				result = true;
216 			}
217 		}
218 	}
219 	UNLOCK(&portlist->lock);
220 	return (result);
221 }
222 
223 void
dns_portlist_attach(dns_portlist_t * portlist,dns_portlist_t ** portlistp)224 dns_portlist_attach(dns_portlist_t *portlist, dns_portlist_t **portlistp) {
225 	REQUIRE(DNS_VALID_PORTLIST(portlist));
226 	REQUIRE(portlistp != NULL && *portlistp == NULL);
227 
228 	isc_refcount_increment(&portlist->refcount);
229 	*portlistp = portlist;
230 }
231 
232 void
dns_portlist_detach(dns_portlist_t ** portlistp)233 dns_portlist_detach(dns_portlist_t **portlistp) {
234 	REQUIRE(portlistp != NULL && DNS_VALID_PORTLIST(*portlistp));
235 	dns_portlist_t *portlist = *portlistp;
236 	*portlistp = NULL;
237 
238 	if (isc_refcount_decrement(&portlist->refcount) == 1) {
239 		portlist->magic = 0;
240 		isc_refcount_destroy(&portlist->refcount);
241 		if (portlist->list != NULL) {
242 			isc_mem_put(portlist->mctx, portlist->list,
243 				    portlist->allocated *
244 					    sizeof(*portlist->list));
245 		}
246 		isc_mutex_destroy(&portlist->lock);
247 		isc_mem_putanddetach(&portlist->mctx, portlist,
248 				     sizeof(*portlist));
249 	}
250 }
251