1 /*
2  * virportallocator.c: Allocate & track TCP port allocations
3  *
4  * Copyright (C) 2013-2014 Red Hat, Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library.  If not, see
18  * <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 #include <config.h>
23 
24 #include <unistd.h>
25 
26 #include "virsocket.h"
27 #include "viralloc.h"
28 #include "virbitmap.h"
29 #include "virportallocator.h"
30 #include "virthread.h"
31 #include "virerror.h"
32 #include "virfile.h"
33 #include "virstring.h"
34 #include "virutil.h"
35 
36 #define VIR_FROM_THIS VIR_FROM_NONE
37 
38 #define VIR_PORT_ALLOCATOR_NUM_PORTS 65536
39 
40 typedef struct _virPortAllocator virPortAllocator;
41 struct _virPortAllocator {
42     virObjectLockable parent;
43     virBitmap *bitmap;
44 };
45 
46 struct _virPortAllocatorRange {
47     char *name;
48 
49     unsigned short start;
50     unsigned short end;
51 };
52 
53 static virClass *virPortAllocatorClass;
54 static virPortAllocator *virPortAllocatorInstance;
55 
56 static void
virPortAllocatorDispose(void * obj)57 virPortAllocatorDispose(void *obj)
58 {
59     virPortAllocator *pa = obj;
60 
61     virBitmapFree(pa->bitmap);
62 }
63 
64 static virPortAllocator *
virPortAllocatorNew(void)65 virPortAllocatorNew(void)
66 {
67     virPortAllocator *pa;
68 
69     if (!(pa = virObjectLockableNew(virPortAllocatorClass)))
70         return NULL;
71 
72     pa->bitmap = virBitmapNew(VIR_PORT_ALLOCATOR_NUM_PORTS);
73 
74     return pa;
75 }
76 
77 static int
virPortAllocatorOnceInit(void)78 virPortAllocatorOnceInit(void)
79 {
80     if (!VIR_CLASS_NEW(virPortAllocator, virClassForObjectLockable()))
81         return -1;
82 
83     if (!(virPortAllocatorInstance = virPortAllocatorNew()))
84         return -1;
85 
86     return 0;
87 }
88 
89 VIR_ONCE_GLOBAL_INIT(virPortAllocator);
90 
91 virPortAllocatorRange *
virPortAllocatorRangeNew(const char * name,unsigned short start,unsigned short end)92 virPortAllocatorRangeNew(const char *name,
93                          unsigned short start,
94                          unsigned short end)
95 {
96     virPortAllocatorRange *range;
97 
98     if (start >= end) {
99         virReportInvalidArg(start, "start port %d must be less than end port %d",
100                             start, end);
101         return NULL;
102     }
103 
104     range = g_new0(virPortAllocatorRange, 1);
105 
106     range->start = start;
107     range->end = end;
108     range->name = g_strdup(name);
109 
110     return range;
111 }
112 
113 void
virPortAllocatorRangeFree(virPortAllocatorRange * range)114 virPortAllocatorRangeFree(virPortAllocatorRange *range)
115 {
116     if (!range)
117         return;
118 
119     g_free(range->name);
120     g_free(range);
121 }
122 
123 static int
virPortAllocatorBindToPort(bool * used,unsigned short port,int family)124 virPortAllocatorBindToPort(bool *used,
125                            unsigned short port,
126                            int family)
127 {
128     struct sockaddr_in6 addr6 = {
129         .sin6_family = AF_INET6,
130         .sin6_port = htons(port),
131         .sin6_addr = in6addr_any
132     };
133     struct sockaddr_in addr4 = {
134         .sin_family = AF_INET,
135         .sin_port = htons(port),
136         .sin_addr.s_addr = htonl(INADDR_ANY)
137     };
138     struct sockaddr* addr;
139     size_t addrlen;
140     int v6only = 1;
141     int ret = -1;
142     int fd = -1;
143     bool ipv6 = false;
144 
145     if (family == AF_INET6) {
146         addr = (struct sockaddr*)&addr6;
147         addrlen = sizeof(addr6);
148         ipv6 = true;
149     } else if (family == AF_INET) {
150         addr = (struct sockaddr*)&addr4;
151         addrlen = sizeof(addr4);
152     } else {
153         virReportError(VIR_ERR_INTERNAL_ERROR, _("Unknown family %d"), family);
154         return -1;
155     }
156 
157     *used = false;
158 
159     fd = socket(family, SOCK_STREAM, 0);
160     if (fd < 0) {
161         if (errno == EAFNOSUPPORT)
162             return 0;
163         virReportSystemError(errno, "%s", _("Unable to open test socket"));
164         goto cleanup;
165     }
166 
167     if (virSetSockReuseAddr(fd, true) < 0)
168         goto cleanup;
169 
170     if (ipv6 && setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&v6only,
171                            sizeof(v6only)) < 0) {
172         virReportSystemError(errno, "%s",
173                              _("Unable to set IPV6_V6ONLY flag"));
174         goto cleanup;
175     }
176 
177     if (bind(fd, addr, addrlen) < 0) {
178         if (errno == EADDRINUSE) {
179             *used = true;
180             ret = 0;
181         } else {
182             virReportSystemError(errno, _("Unable to bind to port %d"), port);
183         }
184         goto cleanup;
185     }
186 
187     ret = 0;
188  cleanup:
189     if (fd != -1)
190         closesocket(fd);
191     return ret;
192 }
193 
194 static virPortAllocator *
virPortAllocatorGet(void)195 virPortAllocatorGet(void)
196 {
197     if (virPortAllocatorInitialize() < 0)
198         return NULL;
199 
200     return virPortAllocatorInstance;
201 }
202 
203 int
virPortAllocatorAcquire(const virPortAllocatorRange * range,unsigned short * port)204 virPortAllocatorAcquire(const virPortAllocatorRange *range,
205                         unsigned short *port)
206 {
207     int ret = -1;
208     size_t i;
209     virPortAllocator *pa = virPortAllocatorGet();
210 
211     *port = 0;
212 
213     if (!pa)
214         return -1;
215 
216     virObjectLock(pa);
217 
218     for (i = range->start; i <= range->end && !*port; i++) {
219         bool used = false, v6used = false;
220 
221         if (virBitmapIsBitSet(pa->bitmap, i))
222             continue;
223 
224         if (virPortAllocatorBindToPort(&v6used, i, AF_INET6) < 0 ||
225             virPortAllocatorBindToPort(&used, i, AF_INET) < 0)
226             goto cleanup;
227 
228         if (!used && !v6used) {
229             /* Add port to bitmap of reserved ports */
230             if (virBitmapSetBit(pa->bitmap, i) < 0) {
231                 virReportError(VIR_ERR_INTERNAL_ERROR,
232                                _("Failed to reserve port %zu"), i);
233                 goto cleanup;
234             }
235             *port = i;
236             ret = 0;
237         }
238     }
239 
240     if (*port == 0) {
241         virReportError(VIR_ERR_INTERNAL_ERROR,
242                        _("Unable to find an unused port in range '%s' (%d-%d)"),
243                        range->name, range->start, range->end);
244     }
245  cleanup:
246     virObjectUnlock(pa);
247     return ret;
248 }
249 
250 int
virPortAllocatorRelease(unsigned short port)251 virPortAllocatorRelease(unsigned short port)
252 {
253     int ret = -1;
254     virPortAllocator *pa = virPortAllocatorGet();
255 
256     if (!pa)
257         return -1;
258 
259     if (!port)
260         return 0;
261 
262     virObjectLock(pa);
263 
264     if (virBitmapClearBit(pa->bitmap, port) < 0) {
265         virReportError(VIR_ERR_INTERNAL_ERROR,
266                        _("Failed to release port %d"),
267                        port);
268         goto cleanup;
269     }
270 
271     ret = 0;
272  cleanup:
273     virObjectUnlock(pa);
274     return ret;
275 }
276 
277 int
virPortAllocatorSetUsed(unsigned short port)278 virPortAllocatorSetUsed(unsigned short port)
279 {
280     int ret = -1;
281     virPortAllocator *pa = virPortAllocatorGet();
282 
283     if (!pa)
284         return -1;
285 
286     if (!port)
287         return 0;
288 
289     virObjectLock(pa);
290 
291     if (virBitmapIsBitSet(pa->bitmap, port) ||
292         virBitmapSetBit(pa->bitmap, port) < 0) {
293         virReportError(VIR_ERR_INTERNAL_ERROR,
294                        _("Failed to reserve port %d"), port);
295         goto cleanup;
296     }
297 
298     ret = 0;
299  cleanup:
300     virObjectUnlock(pa);
301     return ret;
302 }
303