]>
Commit | Line | Data |
---|---|---|
507f26f6 TB |
1 | /* |
2 | * Copyright (C) 2008 Tobias Brunner | |
ce5b1708 | 3 | * Copyright (C) 2005-2008 Martin Willi |
507f26f6 TB |
4 | * Hochschule fuer Technik Rapperswil |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms of the GNU General Public License as published by the | |
8 | * Free Software Foundation; either version 2 of the License, or (at your | |
9 | * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, but | |
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | |
13 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
14 | * for more details. | |
507f26f6 TB |
15 | */ |
16 | ||
d266e895 TE |
17 | /* |
18 | * Copyright (C) 2010 secunet Security Networks AG | |
19 | * Copyright (C) 2010 Thomas Egerer | |
20 | * | |
21 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
22 | * of this software and associated documentation files (the "Software"), to deal | |
23 | * in the Software without restriction, including without limitation the rights | |
24 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
25 | * copies of the Software, and to permit persons to whom the Software is | |
26 | * furnished to do so, subject to the following conditions: | |
27 | * | |
28 | * The above copyright notice and this permission notice shall be included in | |
29 | * all copies or substantial portions of the Software. | |
30 | * | |
31 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
32 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
33 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
34 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
35 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
36 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
37 | * THE SOFTWARE. | |
38 | */ | |
39 | ||
507f26f6 TB |
40 | #include <sys/socket.h> |
41 | #include <linux/netlink.h> | |
42 | #include <linux/rtnetlink.h> | |
507f26f6 TB |
43 | #include <unistd.h> |
44 | #include <errno.h> | |
45 | #include <net/if.h> | |
46 | ||
47 | #include "kernel_netlink_net.h" | |
48 | #include "kernel_netlink_shared.h" | |
49 | ||
c5f7146b | 50 | #include <hydra.h> |
062a6022 | 51 | #include <debug.h> |
4a5a5dd2 TB |
52 | #include <threading/thread.h> |
53 | #include <threading/condvar.h> | |
eba64cef | 54 | #include <threading/mutex.h> |
507f26f6 TB |
55 | #include <utils/linked_list.h> |
56 | #include <processing/jobs/callback_job.h> | |
507f26f6 | 57 | |
ba26508d | 58 | /** delay before firing roam events (ms) */ |
507f26f6 TB |
59 | #define ROAM_DELAY 100 |
60 | ||
507f26f6 TB |
61 | typedef struct addr_entry_t addr_entry_t; |
62 | ||
63 | /** | |
64 | * IP address in an inface_entry_t | |
65 | */ | |
66 | struct addr_entry_t { | |
7daf5226 | 67 | |
507f26f6 TB |
68 | /** The ip address */ |
69 | host_t *ip; | |
7daf5226 | 70 | |
507f26f6 TB |
71 | /** virtual IP managed by us */ |
72 | bool virtual; | |
7daf5226 | 73 | |
507f26f6 TB |
74 | /** scope of the address */ |
75 | u_char scope; | |
7daf5226 | 76 | |
507f26f6 TB |
77 | /** Number of times this IP is used, if virtual */ |
78 | u_int refcount; | |
79 | }; | |
80 | ||
81 | /** | |
82 | * destroy a addr_entry_t object | |
83 | */ | |
84 | static void addr_entry_destroy(addr_entry_t *this) | |
85 | { | |
86 | this->ip->destroy(this->ip); | |
87 | free(this); | |
88 | } | |
89 | ||
90 | typedef struct iface_entry_t iface_entry_t; | |
91 | ||
92 | /** | |
93 | * A network interface on this system, containing addr_entry_t's | |
94 | */ | |
95 | struct iface_entry_t { | |
7daf5226 | 96 | |
507f26f6 TB |
97 | /** interface index */ |
98 | int ifindex; | |
7daf5226 | 99 | |
507f26f6 TB |
100 | /** name of the interface */ |
101 | char ifname[IFNAMSIZ]; | |
7daf5226 | 102 | |
507f26f6 TB |
103 | /** interface flags, as in netdevice(7) SIOCGIFFLAGS */ |
104 | u_int flags; | |
7daf5226 | 105 | |
507f26f6 TB |
106 | /** list of addresses as host_t */ |
107 | linked_list_t *addrs; | |
108 | }; | |
109 | ||
110 | /** | |
111 | * destroy an interface entry | |
112 | */ | |
113 | static void iface_entry_destroy(iface_entry_t *this) | |
114 | { | |
115 | this->addrs->destroy_function(this->addrs, (void*)addr_entry_destroy); | |
116 | free(this); | |
117 | } | |
118 | ||
119 | typedef struct private_kernel_netlink_net_t private_kernel_netlink_net_t; | |
120 | ||
121 | /** | |
122 | * Private variables and functions of kernel_netlink_net class. | |
123 | */ | |
124 | struct private_kernel_netlink_net_t { | |
125 | /** | |
126 | * Public part of the kernel_netlink_net_t object. | |
127 | */ | |
128 | kernel_netlink_net_t public; | |
7daf5226 | 129 | |
507f26f6 TB |
130 | /** |
131 | * mutex to lock access to various lists | |
132 | */ | |
3ac5a0db | 133 | mutex_t *mutex; |
7daf5226 | 134 | |
507f26f6 TB |
135 | /** |
136 | * condition variable to signal virtual IP add/removal | |
137 | */ | |
3ac5a0db | 138 | condvar_t *condvar; |
7daf5226 | 139 | |
507f26f6 TB |
140 | /** |
141 | * Cached list of interfaces and its addresses (iface_entry_t) | |
142 | */ | |
143 | linked_list_t *ifaces; | |
7daf5226 | 144 | |
507f26f6 TB |
145 | /** |
146 | * job receiving netlink events | |
147 | */ | |
148 | callback_job_t *job; | |
7daf5226 | 149 | |
507f26f6 TB |
150 | /** |
151 | * netlink rt socket (routing) | |
152 | */ | |
153 | netlink_socket_t *socket; | |
7daf5226 | 154 | |
507f26f6 TB |
155 | /** |
156 | * Netlink rt socket to receive address change events | |
157 | */ | |
158 | int socket_events; | |
7daf5226 | 159 | |
507f26f6 | 160 | /** |
ba26508d | 161 | * time of the last roam event |
507f26f6 | 162 | */ |
de578445 | 163 | timeval_t last_roam; |
7daf5226 | 164 | |
507f26f6 TB |
165 | /** |
166 | * routing table to install routes | |
167 | */ | |
168 | int routing_table; | |
7daf5226 | 169 | |
507f26f6 TB |
170 | /** |
171 | * priority of used routing table | |
172 | */ | |
173 | int routing_table_prio; | |
7daf5226 | 174 | |
507f26f6 TB |
175 | /** |
176 | * whether to react to RTM_NEWROUTE or RTM_DELROUTE events | |
177 | */ | |
178 | bool process_route; | |
7daf5226 | 179 | |
9474a0d9 MW |
180 | /** |
181 | * whether to actually install virtual IPs | |
182 | */ | |
183 | bool install_virtual_ip; | |
d266e895 TE |
184 | |
185 | /** | |
186 | * list with routing tables to be excluded from route lookup | |
187 | */ | |
188 | linked_list_t *rt_exclude; | |
507f26f6 TB |
189 | }; |
190 | ||
191 | /** | |
192 | * get the refcount of a virtual ip | |
193 | */ | |
194 | static int get_vip_refcount(private_kernel_netlink_net_t *this, host_t* ip) | |
195 | { | |
196 | iterator_t *ifaces, *addrs; | |
197 | iface_entry_t *iface; | |
198 | addr_entry_t *addr; | |
199 | int refcount = 0; | |
7daf5226 | 200 | |
507f26f6 TB |
201 | ifaces = this->ifaces->create_iterator(this->ifaces, TRUE); |
202 | while (ifaces->iterate(ifaces, (void**)&iface)) | |
203 | { | |
204 | addrs = iface->addrs->create_iterator(iface->addrs, TRUE); | |
205 | while (addrs->iterate(addrs, (void**)&addr)) | |
206 | { | |
207 | if (addr->virtual && (iface->flags & IFF_UP) && | |
208 | ip->ip_equals(ip, addr->ip)) | |
209 | { | |
210 | refcount = addr->refcount; | |
211 | break; | |
212 | } | |
213 | } | |
214 | addrs->destroy(addrs); | |
215 | if (refcount) | |
216 | { | |
217 | break; | |
218 | } | |
219 | } | |
220 | ifaces->destroy(ifaces); | |
7daf5226 | 221 | |
507f26f6 TB |
222 | return refcount; |
223 | } | |
224 | ||
225 | /** | |
ba26508d | 226 | * callback function that raises the delayed roam event |
507f26f6 | 227 | */ |
ba26508d TB |
228 | static job_requeue_t roam_event(uintptr_t address) |
229 | { | |
f6659688 | 230 | hydra->kernel_interface->roam(hydra->kernel_interface, address != 0); |
ba26508d TB |
231 | return JOB_REQUEUE_NONE; |
232 | } | |
233 | ||
234 | /** | |
235 | * fire a roaming event. we delay it for a bit and fire only one event | |
236 | * for multiple calls. otherwise we would create too many events. | |
237 | */ | |
238 | static void fire_roam_event(private_kernel_netlink_net_t *this, bool address) | |
507f26f6 | 239 | { |
de578445 | 240 | timeval_t now; |
ba26508d | 241 | job_t *job; |
7daf5226 | 242 | |
de578445 MW |
243 | time_monotonic(&now); |
244 | if (timercmp(&now, &this->last_roam, >)) | |
507f26f6 | 245 | { |
de578445 MW |
246 | now.tv_usec += ROAM_DELAY * 1000; |
247 | while (now.tv_usec > 1000000) | |
507f26f6 | 248 | { |
de578445 MW |
249 | now.tv_sec++; |
250 | now.tv_usec -= 1000000; | |
507f26f6 | 251 | } |
de578445 | 252 | this->last_roam = now; |
ba26508d TB |
253 | |
254 | job = (job_t*)callback_job_create((callback_job_cb_t)roam_event, | |
255 | (void*)(uintptr_t)(address ? 1 : 0), | |
256 | NULL, NULL); | |
bb381e26 | 257 | lib->scheduler->schedule_job_ms(lib->scheduler, job, ROAM_DELAY); |
507f26f6 TB |
258 | } |
259 | } | |
260 | ||
261 | /** | |
262 | * process RTM_NEWLINK/RTM_DELLINK from kernel | |
263 | */ | |
264 | static void process_link(private_kernel_netlink_net_t *this, | |
265 | struct nlmsghdr *hdr, bool event) | |
266 | { | |
267 | struct ifinfomsg* msg = (struct ifinfomsg*)(NLMSG_DATA(hdr)); | |
268 | struct rtattr *rta = IFLA_RTA(msg); | |
269 | size_t rtasize = IFLA_PAYLOAD (hdr); | |
e13389a7 | 270 | enumerator_t *enumerator; |
507f26f6 TB |
271 | iface_entry_t *current, *entry = NULL; |
272 | char *name = NULL; | |
273 | bool update = FALSE; | |
7daf5226 | 274 | |
507f26f6 TB |
275 | while(RTA_OK(rta, rtasize)) |
276 | { | |
277 | switch (rta->rta_type) | |
278 | { | |
279 | case IFLA_IFNAME: | |
280 | name = RTA_DATA(rta); | |
281 | break; | |
282 | } | |
283 | rta = RTA_NEXT(rta, rtasize); | |
284 | } | |
285 | if (!name) | |
286 | { | |
287 | name = "(unknown)"; | |
288 | } | |
7daf5226 | 289 | |
3ac5a0db | 290 | this->mutex->lock(this->mutex); |
507f26f6 TB |
291 | switch (hdr->nlmsg_type) |
292 | { | |
293 | case RTM_NEWLINK: | |
294 | { | |
295 | if (msg->ifi_flags & IFF_LOOPBACK) | |
296 | { /* ignore loopback interfaces */ | |
297 | break; | |
298 | } | |
e13389a7 MW |
299 | enumerator = this->ifaces->create_enumerator(this->ifaces); |
300 | while (enumerator->enumerate(enumerator, ¤t)) | |
507f26f6 TB |
301 | { |
302 | if (current->ifindex == msg->ifi_index) | |
303 | { | |
304 | entry = current; | |
305 | break; | |
306 | } | |
307 | } | |
e13389a7 | 308 | enumerator->destroy(enumerator); |
507f26f6 TB |
309 | if (!entry) |
310 | { | |
311 | entry = malloc_thing(iface_entry_t); | |
312 | entry->ifindex = msg->ifi_index; | |
313 | entry->flags = 0; | |
314 | entry->addrs = linked_list_create(); | |
315 | this->ifaces->insert_last(this->ifaces, entry); | |
316 | } | |
317 | memcpy(entry->ifname, name, IFNAMSIZ); | |
318 | entry->ifname[IFNAMSIZ-1] = '\0'; | |
319 | if (event) | |
320 | { | |
321 | if (!(entry->flags & IFF_UP) && (msg->ifi_flags & IFF_UP)) | |
322 | { | |
323 | update = TRUE; | |
324 | DBG1(DBG_KNL, "interface %s activated", name); | |
325 | } | |
326 | if ((entry->flags & IFF_UP) && !(msg->ifi_flags & IFF_UP)) | |
327 | { | |
328 | update = TRUE; | |
329 | DBG1(DBG_KNL, "interface %s deactivated", name); | |
330 | } | |
331 | } | |
332 | entry->flags = msg->ifi_flags; | |
507f26f6 TB |
333 | break; |
334 | } | |
335 | case RTM_DELLINK: | |
336 | { | |
e13389a7 MW |
337 | enumerator = this->ifaces->create_enumerator(this->ifaces); |
338 | while (enumerator->enumerate(enumerator, ¤t)) | |
507f26f6 TB |
339 | { |
340 | if (current->ifindex == msg->ifi_index) | |
341 | { | |
7daf5226 | 342 | /* we do not remove it, as an address may be added to a |
507f26f6 TB |
343 | * "down" interface and we wan't to know that. */ |
344 | current->flags = msg->ifi_flags; | |
345 | break; | |
346 | } | |
347 | } | |
e13389a7 | 348 | enumerator->destroy(enumerator); |
507f26f6 TB |
349 | break; |
350 | } | |
351 | } | |
3ac5a0db | 352 | this->mutex->unlock(this->mutex); |
7daf5226 | 353 | |
507f26f6 TB |
354 | /* send an update to all IKE_SAs */ |
355 | if (update && event) | |
356 | { | |
ba26508d | 357 | fire_roam_event(this, TRUE); |
507f26f6 TB |
358 | } |
359 | } | |
360 | ||
361 | /** | |
362 | * process RTM_NEWADDR/RTM_DELADDR from kernel | |
363 | */ | |
364 | static void process_addr(private_kernel_netlink_net_t *this, | |
365 | struct nlmsghdr *hdr, bool event) | |
366 | { | |
367 | struct ifaddrmsg* msg = (struct ifaddrmsg*)(NLMSG_DATA(hdr)); | |
368 | struct rtattr *rta = IFA_RTA(msg); | |
369 | size_t rtasize = IFA_PAYLOAD (hdr); | |
370 | host_t *host = NULL; | |
e13389a7 | 371 | enumerator_t *ifaces, *addrs; |
507f26f6 TB |
372 | iface_entry_t *iface; |
373 | addr_entry_t *addr; | |
374 | chunk_t local = chunk_empty, address = chunk_empty; | |
375 | bool update = FALSE, found = FALSE, changed = FALSE; | |
7daf5226 | 376 | |
507f26f6 TB |
377 | while(RTA_OK(rta, rtasize)) |
378 | { | |
379 | switch (rta->rta_type) | |
380 | { | |
381 | case IFA_LOCAL: | |
382 | local.ptr = RTA_DATA(rta); | |
383 | local.len = RTA_PAYLOAD(rta); | |
384 | break; | |
385 | case IFA_ADDRESS: | |
386 | address.ptr = RTA_DATA(rta); | |
387 | address.len = RTA_PAYLOAD(rta); | |
388 | break; | |
389 | } | |
390 | rta = RTA_NEXT(rta, rtasize); | |
391 | } | |
7daf5226 | 392 | |
507f26f6 TB |
393 | /* For PPP interfaces, we need the IFA_LOCAL address, |
394 | * IFA_ADDRESS is the peers address. But IFA_LOCAL is | |
395 | * not included in all cases (IPv6?), so fallback to IFA_ADDRESS. */ | |
396 | if (local.ptr) | |
397 | { | |
398 | host = host_create_from_chunk(msg->ifa_family, local, 0); | |
399 | } | |
400 | else if (address.ptr) | |
401 | { | |
402 | host = host_create_from_chunk(msg->ifa_family, address, 0); | |
403 | } | |
7daf5226 | 404 | |
507f26f6 TB |
405 | if (host == NULL) |
406 | { /* bad family? */ | |
407 | return; | |
408 | } | |
7daf5226 | 409 | |
3ac5a0db | 410 | this->mutex->lock(this->mutex); |
e13389a7 MW |
411 | ifaces = this->ifaces->create_enumerator(this->ifaces); |
412 | while (ifaces->enumerate(ifaces, &iface)) | |
507f26f6 TB |
413 | { |
414 | if (iface->ifindex == msg->ifa_index) | |
415 | { | |
e13389a7 MW |
416 | addrs = iface->addrs->create_enumerator(iface->addrs); |
417 | while (addrs->enumerate(addrs, &addr)) | |
507f26f6 TB |
418 | { |
419 | if (host->ip_equals(host, addr->ip)) | |
420 | { | |
421 | found = TRUE; | |
422 | if (hdr->nlmsg_type == RTM_DELADDR) | |
423 | { | |
e13389a7 | 424 | iface->addrs->remove_at(iface->addrs, addrs); |
507f26f6 TB |
425 | if (!addr->virtual) |
426 | { | |
427 | changed = TRUE; | |
428 | DBG1(DBG_KNL, "%H disappeared from %s", | |
429 | host, iface->ifname); | |
430 | } | |
431 | addr_entry_destroy(addr); | |
432 | } | |
433 | else if (hdr->nlmsg_type == RTM_NEWADDR && addr->virtual) | |
434 | { | |
435 | addr->refcount = 1; | |
436 | } | |
437 | } | |
438 | } | |
439 | addrs->destroy(addrs); | |
7daf5226 | 440 | |
507f26f6 TB |
441 | if (hdr->nlmsg_type == RTM_NEWADDR) |
442 | { | |
443 | if (!found) | |
444 | { | |
445 | found = TRUE; | |
446 | changed = TRUE; | |
447 | addr = malloc_thing(addr_entry_t); | |
448 | addr->ip = host->clone(host); | |
449 | addr->virtual = FALSE; | |
450 | addr->refcount = 1; | |
451 | addr->scope = msg->ifa_scope; | |
7daf5226 | 452 | |
507f26f6 TB |
453 | iface->addrs->insert_last(iface->addrs, addr); |
454 | if (event) | |
455 | { | |
456 | DBG1(DBG_KNL, "%H appeared on %s", host, iface->ifname); | |
457 | } | |
458 | } | |
459 | } | |
460 | if (found && (iface->flags & IFF_UP)) | |
461 | { | |
462 | update = TRUE; | |
463 | } | |
464 | break; | |
465 | } | |
466 | } | |
467 | ifaces->destroy(ifaces); | |
3ac5a0db | 468 | this->mutex->unlock(this->mutex); |
507f26f6 | 469 | host->destroy(host); |
7daf5226 | 470 | |
507f26f6 TB |
471 | /* send an update to all IKE_SAs */ |
472 | if (update && event && changed) | |
473 | { | |
ba26508d | 474 | fire_roam_event(this, TRUE); |
507f26f6 TB |
475 | } |
476 | } | |
477 | ||
478 | /** | |
479 | * process RTM_NEWROUTE and RTM_DELROUTE from kernel | |
480 | */ | |
481 | static void process_route(private_kernel_netlink_net_t *this, struct nlmsghdr *hdr) | |
482 | { | |
483 | struct rtmsg* msg = (struct rtmsg*)(NLMSG_DATA(hdr)); | |
484 | struct rtattr *rta = RTM_RTA(msg); | |
485 | size_t rtasize = RTM_PAYLOAD(hdr); | |
486 | host_t *host = NULL; | |
7daf5226 | 487 | |
85be7e5b MW |
488 | /* ignore routes added by us */ |
489 | if (msg->rtm_table && msg->rtm_table == this->routing_table) | |
490 | { | |
491 | return; | |
492 | } | |
7daf5226 | 493 | |
507f26f6 TB |
494 | while (RTA_OK(rta, rtasize)) |
495 | { | |
496 | switch (rta->rta_type) | |
497 | { | |
498 | case RTA_PREFSRC: | |
499 | host = host_create_from_chunk(msg->rtm_family, | |
500 | chunk_create(RTA_DATA(rta), RTA_PAYLOAD(rta)), 0); | |
501 | break; | |
502 | } | |
503 | rta = RTA_NEXT(rta, rtasize); | |
504 | } | |
505 | if (host) | |
506 | { | |
3ac5a0db | 507 | this->mutex->lock(this->mutex); |
507f26f6 TB |
508 | if (!get_vip_refcount(this, host)) |
509 | { /* ignore routes added for virtual IPs */ | |
ba26508d | 510 | fire_roam_event(this, FALSE); |
507f26f6 | 511 | } |
3ac5a0db | 512 | this->mutex->unlock(this->mutex); |
507f26f6 TB |
513 | host->destroy(host); |
514 | } | |
515 | } | |
516 | ||
517 | /** | |
518 | * Receives events from kernel | |
519 | */ | |
520 | static job_requeue_t receive_events(private_kernel_netlink_net_t *this) | |
521 | { | |
522 | char response[1024]; | |
523 | struct nlmsghdr *hdr = (struct nlmsghdr*)response; | |
524 | struct sockaddr_nl addr; | |
525 | socklen_t addr_len = sizeof(addr); | |
4a5a5dd2 TB |
526 | int len; |
527 | bool oldstate; | |
507f26f6 | 528 | |
4a5a5dd2 | 529 | oldstate = thread_cancelability(TRUE); |
507f26f6 TB |
530 | len = recvfrom(this->socket_events, response, sizeof(response), 0, |
531 | (struct sockaddr*)&addr, &addr_len); | |
4a5a5dd2 | 532 | thread_cancelability(oldstate); |
7daf5226 | 533 | |
507f26f6 TB |
534 | if (len < 0) |
535 | { | |
536 | switch (errno) | |
537 | { | |
538 | case EINTR: | |
539 | /* interrupted, try again */ | |
540 | return JOB_REQUEUE_DIRECT; | |
541 | case EAGAIN: | |
542 | /* no data ready, select again */ | |
543 | return JOB_REQUEUE_DIRECT; | |
544 | default: | |
545 | DBG1(DBG_KNL, "unable to receive from rt event socket"); | |
546 | sleep(1); | |
547 | return JOB_REQUEUE_FAIR; | |
548 | } | |
549 | } | |
7daf5226 | 550 | |
507f26f6 TB |
551 | if (addr.nl_pid != 0) |
552 | { /* not from kernel. not interested, try another one */ | |
553 | return JOB_REQUEUE_DIRECT; | |
554 | } | |
7daf5226 | 555 | |
507f26f6 TB |
556 | while (NLMSG_OK(hdr, len)) |
557 | { | |
558 | /* looks good so far, dispatch netlink message */ | |
559 | switch (hdr->nlmsg_type) | |
560 | { | |
561 | case RTM_NEWADDR: | |
562 | case RTM_DELADDR: | |
563 | process_addr(this, hdr, TRUE); | |
3ac5a0db | 564 | this->condvar->broadcast(this->condvar); |
507f26f6 TB |
565 | break; |
566 | case RTM_NEWLINK: | |
567 | case RTM_DELLINK: | |
568 | process_link(this, hdr, TRUE); | |
3ac5a0db | 569 | this->condvar->broadcast(this->condvar); |
507f26f6 TB |
570 | break; |
571 | case RTM_NEWROUTE: | |
572 | case RTM_DELROUTE: | |
573 | if (this->process_route) | |
574 | { | |
575 | process_route(this, hdr); | |
576 | } | |
577 | break; | |
578 | default: | |
579 | break; | |
580 | } | |
581 | hdr = NLMSG_NEXT(hdr, len); | |
582 | } | |
583 | return JOB_REQUEUE_DIRECT; | |
584 | } | |
585 | ||
586 | /** enumerator over addresses */ | |
587 | typedef struct { | |
588 | private_kernel_netlink_net_t* this; | |
589 | /** whether to enumerate down interfaces */ | |
590 | bool include_down_ifaces; | |
7daf5226 | 591 | /** whether to enumerate virtual ip addresses */ |
507f26f6 TB |
592 | bool include_virtual_ips; |
593 | } address_enumerator_t; | |
594 | ||
595 | /** | |
596 | * cleanup function for address enumerator | |
597 | */ | |
598 | static void address_enumerator_destroy(address_enumerator_t *data) | |
599 | { | |
3ac5a0db | 600 | data->this->mutex->unlock(data->this->mutex); |
507f26f6 TB |
601 | free(data); |
602 | } | |
603 | ||
604 | /** | |
605 | * filter for addresses | |
606 | */ | |
607 | static bool filter_addresses(address_enumerator_t *data, addr_entry_t** in, host_t** out) | |
608 | { | |
609 | if (!data->include_virtual_ips && (*in)->virtual) | |
610 | { /* skip virtual interfaces added by us */ | |
611 | return FALSE; | |
612 | } | |
613 | if ((*in)->scope >= RT_SCOPE_LINK) | |
614 | { /* skip addresses with a unusable scope */ | |
615 | return FALSE; | |
616 | } | |
617 | *out = (*in)->ip; | |
618 | return TRUE; | |
619 | } | |
620 | ||
621 | /** | |
622 | * enumerator constructor for interfaces | |
623 | */ | |
624 | static enumerator_t *create_iface_enumerator(iface_entry_t *iface, address_enumerator_t *data) | |
625 | { | |
626 | return enumerator_create_filter(iface->addrs->create_enumerator(iface->addrs), | |
627 | (void*)filter_addresses, data, NULL); | |
628 | } | |
629 | ||
630 | /** | |
631 | * filter for interfaces | |
632 | */ | |
633 | static bool filter_interfaces(address_enumerator_t *data, iface_entry_t** in, iface_entry_t** out) | |
634 | { | |
635 | if (!data->include_down_ifaces && !((*in)->flags & IFF_UP)) | |
636 | { /* skip interfaces not up */ | |
637 | return FALSE; | |
638 | } | |
639 | *out = *in; | |
640 | return TRUE; | |
641 | } | |
642 | ||
643 | /** | |
644 | * implementation of kernel_net_t.create_address_enumerator | |
645 | */ | |
646 | static enumerator_t *create_address_enumerator(private_kernel_netlink_net_t *this, | |
647 | bool include_down_ifaces, bool include_virtual_ips) | |
648 | { | |
649 | address_enumerator_t *data = malloc_thing(address_enumerator_t); | |
650 | data->this = this; | |
651 | data->include_down_ifaces = include_down_ifaces; | |
652 | data->include_virtual_ips = include_virtual_ips; | |
7daf5226 | 653 | |
3ac5a0db | 654 | this->mutex->lock(this->mutex); |
507f26f6 TB |
655 | return enumerator_create_nested( |
656 | enumerator_create_filter(this->ifaces->create_enumerator(this->ifaces), | |
657 | (void*)filter_interfaces, data, NULL), | |
658 | (void*)create_iface_enumerator, data, (void*)address_enumerator_destroy); | |
659 | } | |
660 | ||
661 | /** | |
662 | * implementation of kernel_net_t.get_interface_name | |
663 | */ | |
664 | static char *get_interface_name(private_kernel_netlink_net_t *this, host_t* ip) | |
665 | { | |
e13389a7 | 666 | enumerator_t *ifaces, *addrs; |
507f26f6 TB |
667 | iface_entry_t *iface; |
668 | addr_entry_t *addr; | |
669 | char *name = NULL; | |
7daf5226 | 670 | |
507f26f6 | 671 | DBG2(DBG_KNL, "getting interface name for %H", ip); |
7daf5226 | 672 | |
3ac5a0db | 673 | this->mutex->lock(this->mutex); |
e13389a7 MW |
674 | ifaces = this->ifaces->create_enumerator(this->ifaces); |
675 | while (ifaces->enumerate(ifaces, &iface)) | |
507f26f6 | 676 | { |
e13389a7 MW |
677 | addrs = iface->addrs->create_enumerator(iface->addrs); |
678 | while (addrs->enumerate(addrs, &addr)) | |
507f26f6 TB |
679 | { |
680 | if (ip->ip_equals(ip, addr->ip)) | |
681 | { | |
682 | name = strdup(iface->ifname); | |
683 | break; | |
684 | } | |
685 | } | |
686 | addrs->destroy(addrs); | |
687 | if (name) | |
688 | { | |
689 | break; | |
690 | } | |
691 | } | |
692 | ifaces->destroy(ifaces); | |
3ac5a0db | 693 | this->mutex->unlock(this->mutex); |
7daf5226 | 694 | |
507f26f6 TB |
695 | if (name) |
696 | { | |
697 | DBG2(DBG_KNL, "%H is on interface %s", ip, name); | |
698 | } | |
699 | else | |
700 | { | |
701 | DBG2(DBG_KNL, "%H is not a local address", ip); | |
702 | } | |
703 | return name; | |
704 | } | |
705 | ||
706 | /** | |
707 | * get the index of an interface by name | |
708 | */ | |
709 | static int get_interface_index(private_kernel_netlink_net_t *this, char* name) | |
710 | { | |
e13389a7 | 711 | enumerator_t *ifaces; |
507f26f6 TB |
712 | iface_entry_t *iface; |
713 | int ifindex = 0; | |
7daf5226 | 714 | |
507f26f6 | 715 | DBG2(DBG_KNL, "getting iface index for %s", name); |
7daf5226 | 716 | |
3ac5a0db | 717 | this->mutex->lock(this->mutex); |
e13389a7 MW |
718 | ifaces = this->ifaces->create_enumerator(this->ifaces); |
719 | while (ifaces->enumerate(ifaces, &iface)) | |
507f26f6 TB |
720 | { |
721 | if (streq(name, iface->ifname)) | |
722 | { | |
723 | ifindex = iface->ifindex; | |
724 | break; | |
725 | } | |
726 | } | |
727 | ifaces->destroy(ifaces); | |
3ac5a0db | 728 | this->mutex->unlock(this->mutex); |
507f26f6 TB |
729 | |
730 | if (ifindex == 0) | |
731 | { | |
732 | DBG1(DBG_KNL, "unable to get interface index for %s", name); | |
733 | } | |
734 | return ifindex; | |
735 | } | |
736 | ||
0ac6d2e6 TB |
737 | /** |
738 | * get the first non-virtual ip address on the given interface. | |
739 | * returned host is a clone, has to be freed by caller. | |
740 | */ | |
741 | static host_t *get_interface_address(private_kernel_netlink_net_t *this, | |
742 | int ifindex, int family) | |
743 | { | |
744 | enumerator_t *ifaces, *addrs; | |
745 | iface_entry_t *iface; | |
746 | addr_entry_t *addr; | |
747 | host_t *ip = NULL; | |
748 | ||
749 | this->mutex->lock(this->mutex); | |
750 | ifaces = this->ifaces->create_enumerator(this->ifaces); | |
751 | while (ifaces->enumerate(ifaces, &iface)) | |
752 | { | |
753 | if (iface->ifindex == ifindex) | |
754 | { | |
755 | addrs = iface->addrs->create_enumerator(iface->addrs); | |
756 | while (addrs->enumerate(addrs, &addr)) | |
757 | { | |
758 | if (!addr->virtual && addr->ip->get_family(addr->ip) == family) | |
759 | { | |
760 | ip = addr->ip->clone(addr->ip); | |
761 | break; | |
762 | } | |
763 | } | |
764 | addrs->destroy(addrs); | |
765 | break; | |
766 | } | |
767 | } | |
768 | ifaces->destroy(ifaces); | |
769 | this->mutex->unlock(this->mutex); | |
770 | return ip; | |
771 | } | |
772 | ||
fb6c8591 MW |
773 | /** |
774 | * Check if an interface with a given index is up | |
775 | */ | |
776 | static bool is_interface_up(private_kernel_netlink_net_t *this, int index) | |
777 | { | |
778 | enumerator_t *ifaces; | |
779 | iface_entry_t *iface; | |
32f59c56 MW |
780 | /* default to TRUE for interface we do not monitor (e.g. lo) */ |
781 | bool up = TRUE; | |
7daf5226 | 782 | |
fb6c8591 MW |
783 | ifaces = this->ifaces->create_enumerator(this->ifaces); |
784 | while (ifaces->enumerate(ifaces, &iface)) | |
785 | { | |
786 | if (iface->ifindex == index) | |
787 | { | |
788 | up = iface->flags & IFF_UP; | |
789 | break; | |
790 | } | |
791 | } | |
792 | ifaces->destroy(ifaces); | |
793 | return up; | |
794 | } | |
795 | ||
507f26f6 TB |
796 | /** |
797 | * check if an address (chunk) addr is in subnet (net with net_len net bits) | |
798 | */ | |
799 | static bool addr_in_subnet(chunk_t addr, chunk_t net, int net_len) | |
800 | { | |
03d5f411 AS |
801 | static const u_char mask[] = { 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe }; |
802 | int byte = 0; | |
7daf5226 | 803 | |
d1769942 MW |
804 | if (net_len == 0) |
805 | { /* any address matches a /0 network */ | |
806 | return TRUE; | |
807 | } | |
03d5f411 | 808 | if (addr.len != net.len || net_len > 8 * net.len ) |
507f26f6 TB |
809 | { |
810 | return FALSE; | |
811 | } | |
03d5f411 AS |
812 | /* scan through all bytes in network order */ |
813 | while (net_len > 0) | |
507f26f6 | 814 | { |
03d5f411 | 815 | if (net_len < 8) |
507f26f6 | 816 | { |
03d5f411 AS |
817 | return (mask[net_len] & addr.ptr[byte]) == (mask[net_len] & net.ptr[byte]); |
818 | } | |
819 | else | |
820 | { | |
821 | if (addr.ptr[byte] != net.ptr[byte]) | |
507f26f6 TB |
822 | { |
823 | return FALSE; | |
824 | } | |
03d5f411 AS |
825 | byte++; |
826 | net_len -= 8; | |
507f26f6 TB |
827 | } |
828 | } | |
829 | return TRUE; | |
830 | } | |
831 | ||
832 | /** | |
833 | * Get a route: If "nexthop", the nexthop is returned. source addr otherwise. | |
834 | */ | |
835 | static host_t *get_route(private_kernel_netlink_net_t *this, host_t *dest, | |
ce5b1708 | 836 | bool nexthop, host_t *candidate) |
507f26f6 | 837 | { |
21bf86f7 | 838 | netlink_buf_t request; |
507f26f6 TB |
839 | struct nlmsghdr *hdr, *out, *current; |
840 | struct rtmsg *msg; | |
841 | chunk_t chunk; | |
842 | size_t len; | |
843 | int best = -1; | |
d266e895 | 844 | enumerator_t *enumerator; |
507f26f6 | 845 | host_t *src = NULL, *gtw = NULL; |
7daf5226 | 846 | |
507f26f6 | 847 | DBG2(DBG_KNL, "getting address to reach %H", dest); |
7daf5226 | 848 | |
507f26f6 TB |
849 | memset(&request, 0, sizeof(request)); |
850 | ||
851 | hdr = (struct nlmsghdr*)request; | |
5be75c2c MW |
852 | hdr->nlmsg_flags = NLM_F_REQUEST; |
853 | if (dest->get_family(dest) == AF_INET) | |
854 | { | |
855 | /* We dump all addresses for IPv4, as we want to ignore IPsec specific | |
856 | * routes installed by us. But the kernel does not return source | |
857 | * addresses in a IPv6 dump, so fall back to get() for v6 routes. */ | |
858 | hdr->nlmsg_flags |= NLM_F_ROOT | NLM_F_DUMP; | |
859 | } | |
507f26f6 TB |
860 | hdr->nlmsg_type = RTM_GETROUTE; |
861 | hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); | |
862 | ||
863 | msg = (struct rtmsg*)NLMSG_DATA(hdr); | |
864 | msg->rtm_family = dest->get_family(dest); | |
ce5b1708 MW |
865 | if (candidate) |
866 | { | |
867 | chunk = candidate->get_address(candidate); | |
868 | netlink_add_attribute(hdr, RTA_PREFSRC, chunk, sizeof(request)); | |
869 | } | |
d1769942 MW |
870 | chunk = dest->get_address(dest); |
871 | netlink_add_attribute(hdr, RTA_DST, chunk, sizeof(request)); | |
7daf5226 | 872 | |
507f26f6 TB |
873 | if (this->socket->send(this->socket, hdr, &out, &len) != SUCCESS) |
874 | { | |
875 | DBG1(DBG_KNL, "getting address to %H failed", dest); | |
876 | return NULL; | |
877 | } | |
3ac5a0db | 878 | this->mutex->lock(this->mutex); |
36b7ba5e MW |
879 | |
880 | for (current = out; NLMSG_OK(current, len); | |
881 | current = NLMSG_NEXT(current, len)) | |
507f26f6 TB |
882 | { |
883 | switch (current->nlmsg_type) | |
884 | { | |
885 | case NLMSG_DONE: | |
886 | break; | |
887 | case RTM_NEWROUTE: | |
888 | { | |
889 | struct rtattr *rta; | |
890 | size_t rtasize; | |
891 | chunk_t rta_gtw, rta_src, rta_dst; | |
892 | u_int32_t rta_oif = 0; | |
d1769942 | 893 | host_t *new_src, *new_gtw; |
d266e895 TE |
894 | bool cont = FALSE; |
895 | uintptr_t table; | |
7daf5226 | 896 | |
507f26f6 TB |
897 | rta_gtw = rta_src = rta_dst = chunk_empty; |
898 | msg = (struct rtmsg*)(NLMSG_DATA(current)); | |
899 | rta = RTM_RTA(msg); | |
900 | rtasize = RTM_PAYLOAD(current); | |
901 | while (RTA_OK(rta, rtasize)) | |
902 | { | |
903 | switch (rta->rta_type) | |
904 | { | |
905 | case RTA_PREFSRC: | |
906 | rta_src = chunk_create(RTA_DATA(rta), RTA_PAYLOAD(rta)); | |
907 | break; | |
908 | case RTA_GATEWAY: | |
909 | rta_gtw = chunk_create(RTA_DATA(rta), RTA_PAYLOAD(rta)); | |
910 | break; | |
911 | case RTA_DST: | |
912 | rta_dst = chunk_create(RTA_DATA(rta), RTA_PAYLOAD(rta)); | |
913 | break; | |
914 | case RTA_OIF: | |
915 | if (RTA_PAYLOAD(rta) == sizeof(rta_oif)) | |
916 | { | |
917 | rta_oif = *(u_int32_t*)RTA_DATA(rta); | |
918 | } | |
919 | break; | |
920 | } | |
921 | rta = RTA_NEXT(rta, rtasize); | |
922 | } | |
d1769942 MW |
923 | if (msg->rtm_dst_len <= best) |
924 | { /* not better than a previous one */ | |
36b7ba5e | 925 | continue; |
fb6c8591 | 926 | } |
d266e895 TE |
927 | enumerator = this->rt_exclude->create_enumerator(this->rt_exclude); |
928 | while (enumerator->enumerate(enumerator, &table)) | |
929 | { | |
930 | if (table == msg->rtm_table) | |
931 | { | |
932 | cont = TRUE; | |
933 | break; | |
934 | } | |
935 | } | |
936 | enumerator->destroy(enumerator); | |
937 | if (cont) | |
938 | { | |
939 | continue; | |
940 | } | |
fb6c8591 MW |
941 | if (this->routing_table != 0 && |
942 | msg->rtm_table == this->routing_table) | |
943 | { /* route is from our own ipsec routing table */ | |
36b7ba5e | 944 | continue; |
fb6c8591 | 945 | } |
d1769942 MW |
946 | if (rta_oif && !is_interface_up(this, rta_oif)) |
947 | { /* interface is down */ | |
36b7ba5e | 948 | continue; |
fb6c8591 | 949 | } |
d1769942 MW |
950 | if (!addr_in_subnet(chunk, rta_dst, msg->rtm_dst_len)) |
951 | { /* route destination does not contain dest */ | |
36b7ba5e | 952 | continue; |
fb6c8591 | 953 | } |
7daf5226 | 954 | |
fb6c8591 | 955 | if (nexthop) |
507f26f6 | 956 | { |
4a03e85b MW |
957 | /* nexthop lookup, return gateway if any */ |
958 | DESTROY_IF(gtw); | |
959 | gtw = host_create_from_chunk(msg->rtm_family, rta_gtw, 0); | |
960 | best = msg->rtm_dst_len; | |
36b7ba5e | 961 | continue; |
fb6c8591 MW |
962 | } |
963 | if (rta_src.ptr) | |
0ac6d2e6 | 964 | { /* got a source address */ |
d1769942 | 965 | new_src = host_create_from_chunk(msg->rtm_family, rta_src, 0); |
0406ed7a | 966 | if (new_src) |
d1769942 | 967 | { |
0406ed7a MW |
968 | if (get_vip_refcount(this, new_src)) |
969 | { /* skip source address if it is installed by us */ | |
970 | new_src->destroy(new_src); | |
971 | } | |
972 | else | |
973 | { | |
974 | DESTROY_IF(src); | |
975 | src = new_src; | |
976 | best = msg->rtm_dst_len; | |
977 | } | |
507f26f6 | 978 | } |
36b7ba5e | 979 | continue; |
fb6c8591 | 980 | } |
0ac6d2e6 TB |
981 | if (rta_oif) |
982 | { /* no source, but an interface. Get address from it. */ | |
983 | new_src = get_interface_address(this, rta_oif, | |
984 | msg->rtm_family); | |
985 | if (new_src) | |
986 | { | |
987 | DESTROY_IF(src); | |
988 | src = new_src; | |
989 | best = msg->rtm_dst_len; | |
990 | } | |
991 | continue; | |
992 | } | |
d1769942 MW |
993 | if (rta_gtw.ptr) |
994 | { /* no source, but a gateway. Lookup source to reach gtw. */ | |
995 | new_gtw = host_create_from_chunk(msg->rtm_family, rta_gtw, 0); | |
996 | new_src = get_route(this, new_gtw, FALSE, candidate); | |
997 | new_gtw->destroy(new_gtw); | |
998 | if (new_src) | |
507f26f6 | 999 | { |
d1769942 MW |
1000 | DESTROY_IF(src); |
1001 | src = new_src; | |
1002 | best = msg->rtm_dst_len; | |
507f26f6 | 1003 | } |
36b7ba5e | 1004 | continue; |
507f26f6 | 1005 | } |
36b7ba5e | 1006 | continue; |
507f26f6 TB |
1007 | } |
1008 | default: | |
507f26f6 TB |
1009 | continue; |
1010 | } | |
1011 | break; | |
1012 | } | |
1013 | free(out); | |
3ac5a0db | 1014 | this->mutex->unlock(this->mutex); |
7daf5226 | 1015 | |
507f26f6 TB |
1016 | if (nexthop) |
1017 | { | |
1018 | if (gtw) | |
1019 | { | |
1020 | return gtw; | |
1021 | } | |
1022 | return dest->clone(dest); | |
1023 | } | |
1024 | return src; | |
1025 | } | |
1026 | ||
1027 | /** | |
1028 | * Implementation of kernel_net_t.get_source_addr. | |
1029 | */ | |
ce5b1708 MW |
1030 | static host_t* get_source_addr(private_kernel_netlink_net_t *this, |
1031 | host_t *dest, host_t *src) | |
507f26f6 | 1032 | { |
ce5b1708 | 1033 | return get_route(this, dest, FALSE, src); |
507f26f6 TB |
1034 | } |
1035 | ||
1036 | /** | |
1037 | * Implementation of kernel_net_t.get_nexthop. | |
1038 | */ | |
1039 | static host_t* get_nexthop(private_kernel_netlink_net_t *this, host_t *dest) | |
1040 | { | |
ce5b1708 | 1041 | return get_route(this, dest, TRUE, NULL); |
507f26f6 TB |
1042 | } |
1043 | ||
1044 | /** | |
1045 | * Manages the creation and deletion of ip addresses on an interface. | |
1046 | * By setting the appropriate nlmsg_type, the ip will be set or unset. | |
1047 | */ | |
1048 | static status_t manage_ipaddr(private_kernel_netlink_net_t *this, int nlmsg_type, | |
1049 | int flags, int if_index, host_t *ip) | |
1050 | { | |
21bf86f7 | 1051 | netlink_buf_t request; |
507f26f6 TB |
1052 | struct nlmsghdr *hdr; |
1053 | struct ifaddrmsg *msg; | |
1054 | chunk_t chunk; | |
7daf5226 | 1055 | |
507f26f6 | 1056 | memset(&request, 0, sizeof(request)); |
7daf5226 | 1057 | |
507f26f6 | 1058 | chunk = ip->get_address(ip); |
7daf5226 | 1059 | |
323f9f99 | 1060 | hdr = (struct nlmsghdr*)request; |
507f26f6 | 1061 | hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | flags; |
7daf5226 | 1062 | hdr->nlmsg_type = nlmsg_type; |
507f26f6 | 1063 | hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); |
7daf5226 | 1064 | |
507f26f6 | 1065 | msg = (struct ifaddrmsg*)NLMSG_DATA(hdr); |
323f9f99 MW |
1066 | msg->ifa_family = ip->get_family(ip); |
1067 | msg->ifa_flags = 0; | |
1068 | msg->ifa_prefixlen = 8 * chunk.len; | |
1069 | msg->ifa_scope = RT_SCOPE_UNIVERSE; | |
1070 | msg->ifa_index = if_index; | |
7daf5226 | 1071 | |
507f26f6 TB |
1072 | netlink_add_attribute(hdr, IFA_LOCAL, chunk, sizeof(request)); |
1073 | ||
1074 | return this->socket->send_ack(this->socket, hdr); | |
1075 | } | |
1076 | ||
1077 | /** | |
1078 | * Implementation of kernel_net_t.add_ip. | |
1079 | */ | |
7daf5226 | 1080 | static status_t add_ip(private_kernel_netlink_net_t *this, |
507f26f6 TB |
1081 | host_t *virtual_ip, host_t *iface_ip) |
1082 | { | |
1083 | iface_entry_t *iface; | |
1084 | addr_entry_t *addr; | |
e13389a7 | 1085 | enumerator_t *addrs, *ifaces; |
507f26f6 | 1086 | int ifindex; |
7daf5226 | 1087 | |
9474a0d9 MW |
1088 | if (!this->install_virtual_ip) |
1089 | { /* disabled by config */ | |
1090 | return SUCCESS; | |
1091 | } | |
7daf5226 | 1092 | |
507f26f6 | 1093 | DBG2(DBG_KNL, "adding virtual IP %H", virtual_ip); |
7daf5226 | 1094 | |
3ac5a0db | 1095 | this->mutex->lock(this->mutex); |
e13389a7 MW |
1096 | ifaces = this->ifaces->create_enumerator(this->ifaces); |
1097 | while (ifaces->enumerate(ifaces, &iface)) | |
507f26f6 TB |
1098 | { |
1099 | bool iface_found = FALSE; | |
7daf5226 | 1100 | |
e13389a7 MW |
1101 | addrs = iface->addrs->create_enumerator(iface->addrs); |
1102 | while (addrs->enumerate(addrs, &addr)) | |
507f26f6 TB |
1103 | { |
1104 | if (iface_ip->ip_equals(iface_ip, addr->ip)) | |
1105 | { | |
1106 | iface_found = TRUE; | |
1107 | } | |
1108 | else if (virtual_ip->ip_equals(virtual_ip, addr->ip)) | |
1109 | { | |
1110 | addr->refcount++; | |
1111 | DBG2(DBG_KNL, "virtual IP %H already installed on %s", | |
1112 | virtual_ip, iface->ifname); | |
1113 | addrs->destroy(addrs); | |
1114 | ifaces->destroy(ifaces); | |
3ac5a0db | 1115 | this->mutex->unlock(this->mutex); |
507f26f6 TB |
1116 | return SUCCESS; |
1117 | } | |
1118 | } | |
1119 | addrs->destroy(addrs); | |
7daf5226 | 1120 | |
507f26f6 TB |
1121 | if (iface_found) |
1122 | { | |
1123 | ifindex = iface->ifindex; | |
1124 | addr = malloc_thing(addr_entry_t); | |
1125 | addr->ip = virtual_ip->clone(virtual_ip); | |
1126 | addr->refcount = 0; | |
1127 | addr->virtual = TRUE; | |
1128 | addr->scope = RT_SCOPE_UNIVERSE; | |
1129 | iface->addrs->insert_last(iface->addrs, addr); | |
7daf5226 | 1130 | |
507f26f6 TB |
1131 | if (manage_ipaddr(this, RTM_NEWADDR, NLM_F_CREATE | NLM_F_EXCL, |
1132 | ifindex, virtual_ip) == SUCCESS) | |
1133 | { | |
1134 | while (get_vip_refcount(this, virtual_ip) == 0) | |
1135 | { /* wait until address appears */ | |
3ac5a0db | 1136 | this->condvar->wait(this->condvar, this->mutex); |
507f26f6 TB |
1137 | } |
1138 | ifaces->destroy(ifaces); | |
3ac5a0db | 1139 | this->mutex->unlock(this->mutex); |
507f26f6 TB |
1140 | return SUCCESS; |
1141 | } | |
1142 | ifaces->destroy(ifaces); | |
3ac5a0db | 1143 | this->mutex->unlock(this->mutex); |
507f26f6 TB |
1144 | DBG1(DBG_KNL, "adding virtual IP %H failed", virtual_ip); |
1145 | return FAILED; | |
1146 | } | |
1147 | } | |
1148 | ifaces->destroy(ifaces); | |
3ac5a0db | 1149 | this->mutex->unlock(this->mutex); |
7daf5226 | 1150 | |
507f26f6 TB |
1151 | DBG1(DBG_KNL, "interface address %H not found, unable to install" |
1152 | "virtual IP %H", iface_ip, virtual_ip); | |
1153 | return FAILED; | |
1154 | } | |
1155 | ||
1156 | /** | |
1157 | * Implementation of kernel_net_t.del_ip. | |
1158 | */ | |
1159 | static status_t del_ip(private_kernel_netlink_net_t *this, host_t *virtual_ip) | |
1160 | { | |
1161 | iface_entry_t *iface; | |
1162 | addr_entry_t *addr; | |
e13389a7 | 1163 | enumerator_t *addrs, *ifaces; |
507f26f6 TB |
1164 | status_t status; |
1165 | int ifindex; | |
7daf5226 | 1166 | |
9474a0d9 MW |
1167 | if (!this->install_virtual_ip) |
1168 | { /* disabled by config */ | |
1169 | return SUCCESS; | |
1170 | } | |
7daf5226 | 1171 | |
507f26f6 | 1172 | DBG2(DBG_KNL, "deleting virtual IP %H", virtual_ip); |
7daf5226 | 1173 | |
3ac5a0db | 1174 | this->mutex->lock(this->mutex); |
e13389a7 MW |
1175 | ifaces = this->ifaces->create_enumerator(this->ifaces); |
1176 | while (ifaces->enumerate(ifaces, &iface)) | |
507f26f6 | 1177 | { |
e13389a7 MW |
1178 | addrs = iface->addrs->create_enumerator(iface->addrs); |
1179 | while (addrs->enumerate(addrs, &addr)) | |
507f26f6 TB |
1180 | { |
1181 | if (virtual_ip->ip_equals(virtual_ip, addr->ip)) | |
1182 | { | |
1183 | ifindex = iface->ifindex; | |
1184 | if (addr->refcount == 1) | |
1185 | { | |
1186 | status = manage_ipaddr(this, RTM_DELADDR, 0, | |
b9b8a98f | 1187 | ifindex, virtual_ip); |
507f26f6 TB |
1188 | if (status == SUCCESS) |
1189 | { /* wait until the address is really gone */ | |
1190 | while (get_vip_refcount(this, virtual_ip) > 0) | |
1191 | { | |
3ac5a0db | 1192 | this->condvar->wait(this->condvar, this->mutex); |
507f26f6 TB |
1193 | } |
1194 | } | |
1195 | addrs->destroy(addrs); | |
1196 | ifaces->destroy(ifaces); | |
3ac5a0db | 1197 | this->mutex->unlock(this->mutex); |
507f26f6 TB |
1198 | return status; |
1199 | } | |
1200 | else | |
1201 | { | |
1202 | addr->refcount--; | |
1203 | } | |
1204 | DBG2(DBG_KNL, "virtual IP %H used by other SAs, not deleting", | |
1205 | virtual_ip); | |
1206 | addrs->destroy(addrs); | |
1207 | ifaces->destroy(ifaces); | |
3ac5a0db | 1208 | this->mutex->unlock(this->mutex); |
507f26f6 TB |
1209 | return SUCCESS; |
1210 | } | |
1211 | } | |
1212 | addrs->destroy(addrs); | |
1213 | } | |
1214 | ifaces->destroy(ifaces); | |
3ac5a0db | 1215 | this->mutex->unlock(this->mutex); |
7daf5226 | 1216 | |
507f26f6 TB |
1217 | DBG2(DBG_KNL, "virtual IP %H not cached, unable to delete", virtual_ip); |
1218 | return FAILED; | |
1219 | } | |
1220 | ||
1221 | /** | |
1222 | * Manages source routes in the routing table. | |
1223 | * By setting the appropriate nlmsg_type, the route gets added or removed. | |
1224 | */ | |
1225 | static status_t manage_srcroute(private_kernel_netlink_net_t *this, int nlmsg_type, | |
1226 | int flags, chunk_t dst_net, u_int8_t prefixlen, | |
1227 | host_t *gateway, host_t *src_ip, char *if_name) | |
1228 | { | |
21bf86f7 | 1229 | netlink_buf_t request; |
507f26f6 TB |
1230 | struct nlmsghdr *hdr; |
1231 | struct rtmsg *msg; | |
1232 | int ifindex; | |
1233 | chunk_t chunk; | |
1234 | ||
1235 | /* if route is 0.0.0.0/0, we can't install it, as it would | |
1236 | * overwrite the default route. Instead, we add two routes: | |
1237 | * 0.0.0.0/1 and 128.0.0.0/1 */ | |
1238 | if (this->routing_table == 0 && prefixlen == 0) | |
1239 | { | |
1240 | chunk_t half_net; | |
1241 | u_int8_t half_prefixlen; | |
1242 | status_t status; | |
7daf5226 | 1243 | |
507f26f6 TB |
1244 | half_net = chunk_alloca(dst_net.len); |
1245 | memset(half_net.ptr, 0, half_net.len); | |
1246 | half_prefixlen = 1; | |
7daf5226 | 1247 | |
507f26f6 TB |
1248 | status = manage_srcroute(this, nlmsg_type, flags, half_net, half_prefixlen, |
1249 | gateway, src_ip, if_name); | |
1250 | half_net.ptr[0] |= 0x80; | |
1251 | status = manage_srcroute(this, nlmsg_type, flags, half_net, half_prefixlen, | |
1252 | gateway, src_ip, if_name); | |
1253 | return status; | |
1254 | } | |
7daf5226 | 1255 | |
507f26f6 TB |
1256 | memset(&request, 0, sizeof(request)); |
1257 | ||
1258 | hdr = (struct nlmsghdr*)request; | |
1259 | hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | flags; | |
1260 | hdr->nlmsg_type = nlmsg_type; | |
1261 | hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); | |
1262 | ||
1263 | msg = (struct rtmsg*)NLMSG_DATA(hdr); | |
1264 | msg->rtm_family = src_ip->get_family(src_ip); | |
1265 | msg->rtm_dst_len = prefixlen; | |
1266 | msg->rtm_table = this->routing_table; | |
1267 | msg->rtm_protocol = RTPROT_STATIC; | |
1268 | msg->rtm_type = RTN_UNICAST; | |
1269 | msg->rtm_scope = RT_SCOPE_UNIVERSE; | |
7daf5226 | 1270 | |
507f26f6 TB |
1271 | netlink_add_attribute(hdr, RTA_DST, dst_net, sizeof(request)); |
1272 | chunk = src_ip->get_address(src_ip); | |
1273 | netlink_add_attribute(hdr, RTA_PREFSRC, chunk, sizeof(request)); | |
5be75c2c MW |
1274 | if (gateway && gateway->get_family(gateway) == src_ip->get_family(src_ip)) |
1275 | { | |
1276 | chunk = gateway->get_address(gateway); | |
1277 | netlink_add_attribute(hdr, RTA_GATEWAY, chunk, sizeof(request)); | |
1278 | } | |
507f26f6 TB |
1279 | ifindex = get_interface_index(this, if_name); |
1280 | chunk.ptr = (char*)&ifindex; | |
1281 | chunk.len = sizeof(ifindex); | |
1282 | netlink_add_attribute(hdr, RTA_OIF, chunk, sizeof(request)); | |
1283 | ||
1284 | return this->socket->send_ack(this->socket, hdr); | |
1285 | } | |
1286 | ||
1287 | /** | |
1288 | * Implementation of kernel_net_t.add_route. | |
1289 | */ | |
d24a74c5 | 1290 | static status_t add_route(private_kernel_netlink_net_t *this, chunk_t dst_net, |
507f26f6 TB |
1291 | u_int8_t prefixlen, host_t *gateway, host_t *src_ip, char *if_name) |
1292 | { | |
1293 | return manage_srcroute(this, RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, | |
1294 | dst_net, prefixlen, gateway, src_ip, if_name); | |
1295 | } | |
7daf5226 | 1296 | |
507f26f6 TB |
1297 | /** |
1298 | * Implementation of kernel_net_t.del_route. | |
1299 | */ | |
d24a74c5 | 1300 | static status_t del_route(private_kernel_netlink_net_t *this, chunk_t dst_net, |
507f26f6 TB |
1301 | u_int8_t prefixlen, host_t *gateway, host_t *src_ip, char *if_name) |
1302 | { | |
1303 | return manage_srcroute(this, RTM_DELROUTE, 0, dst_net, prefixlen, | |
1304 | gateway, src_ip, if_name); | |
1305 | } | |
1306 | ||
1307 | /** | |
1308 | * Initialize a list of local addresses. | |
1309 | */ | |
1310 | static status_t init_address_list(private_kernel_netlink_net_t *this) | |
1311 | { | |
21bf86f7 | 1312 | netlink_buf_t request; |
507f26f6 TB |
1313 | struct nlmsghdr *out, *current, *in; |
1314 | struct rtgenmsg *msg; | |
1315 | size_t len; | |
e13389a7 | 1316 | enumerator_t *ifaces, *addrs; |
507f26f6 TB |
1317 | iface_entry_t *iface; |
1318 | addr_entry_t *addr; | |
7daf5226 | 1319 | |
507f26f6 | 1320 | DBG1(DBG_KNL, "listening on interfaces:"); |
7daf5226 | 1321 | |
507f26f6 TB |
1322 | memset(&request, 0, sizeof(request)); |
1323 | ||
1324 | in = (struct nlmsghdr*)&request; | |
1325 | in->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)); | |
1326 | in->nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH | NLM_F_ROOT; | |
1327 | msg = (struct rtgenmsg*)NLMSG_DATA(in); | |
1328 | msg->rtgen_family = AF_UNSPEC; | |
7daf5226 | 1329 | |
507f26f6 TB |
1330 | /* get all links */ |
1331 | in->nlmsg_type = RTM_GETLINK; | |
1332 | if (this->socket->send(this->socket, in, &out, &len) != SUCCESS) | |
1333 | { | |
1334 | return FAILED; | |
1335 | } | |
1336 | current = out; | |
1337 | while (NLMSG_OK(current, len)) | |
1338 | { | |
1339 | switch (current->nlmsg_type) | |
1340 | { | |
1341 | case NLMSG_DONE: | |
1342 | break; | |
1343 | case RTM_NEWLINK: | |
1344 | process_link(this, current, FALSE); | |
1345 | /* fall through */ | |
1346 | default: | |
1347 | current = NLMSG_NEXT(current, len); | |
1348 | continue; | |
1349 | } | |
1350 | break; | |
1351 | } | |
1352 | free(out); | |
7daf5226 | 1353 | |
507f26f6 TB |
1354 | /* get all interface addresses */ |
1355 | in->nlmsg_type = RTM_GETADDR; | |
1356 | if (this->socket->send(this->socket, in, &out, &len) != SUCCESS) | |
1357 | { | |
1358 | return FAILED; | |
1359 | } | |
1360 | current = out; | |
1361 | while (NLMSG_OK(current, len)) | |
1362 | { | |
1363 | switch (current->nlmsg_type) | |
1364 | { | |
1365 | case NLMSG_DONE: | |
1366 | break; | |
1367 | case RTM_NEWADDR: | |
1368 | process_addr(this, current, FALSE); | |
1369 | /* fall through */ | |
1370 | default: | |
1371 | current = NLMSG_NEXT(current, len); | |
1372 | continue; | |
1373 | } | |
1374 | break; | |
1375 | } | |
1376 | free(out); | |
7daf5226 | 1377 | |
3ac5a0db | 1378 | this->mutex->lock(this->mutex); |
e13389a7 MW |
1379 | ifaces = this->ifaces->create_enumerator(this->ifaces); |
1380 | while (ifaces->enumerate(ifaces, &iface)) | |
507f26f6 TB |
1381 | { |
1382 | if (iface->flags & IFF_UP) | |
1383 | { | |
1384 | DBG1(DBG_KNL, " %s", iface->ifname); | |
e13389a7 MW |
1385 | addrs = iface->addrs->create_enumerator(iface->addrs); |
1386 | while (addrs->enumerate(addrs, (void**)&addr)) | |
507f26f6 TB |
1387 | { |
1388 | DBG1(DBG_KNL, " %H", addr->ip); | |
1389 | } | |
1390 | addrs->destroy(addrs); | |
1391 | } | |
1392 | } | |
1393 | ifaces->destroy(ifaces); | |
3ac5a0db | 1394 | this->mutex->unlock(this->mutex); |
507f26f6 TB |
1395 | return SUCCESS; |
1396 | } | |
1397 | ||
1398 | /** | |
1399 | * create or delete a rule to use our routing table | |
1400 | */ | |
1401 | static status_t manage_rule(private_kernel_netlink_net_t *this, int nlmsg_type, | |
5be75c2c | 1402 | int family, u_int32_t table, u_int32_t prio) |
507f26f6 | 1403 | { |
21bf86f7 | 1404 | netlink_buf_t request; |
507f26f6 TB |
1405 | struct nlmsghdr *hdr; |
1406 | struct rtmsg *msg; | |
1407 | chunk_t chunk; | |
1408 | ||
7daf5226 | 1409 | memset(&request, 0, sizeof(request)); |
507f26f6 TB |
1410 | hdr = (struct nlmsghdr*)request; |
1411 | hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; | |
7daf5226 | 1412 | hdr->nlmsg_type = nlmsg_type; |
507f26f6 TB |
1413 | if (nlmsg_type == RTM_NEWRULE) |
1414 | { | |
1415 | hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; | |
1416 | } | |
1417 | hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); | |
1418 | ||
1419 | msg = (struct rtmsg*)NLMSG_DATA(hdr); | |
1420 | msg->rtm_table = table; | |
5be75c2c | 1421 | msg->rtm_family = family; |
507f26f6 TB |
1422 | msg->rtm_protocol = RTPROT_BOOT; |
1423 | msg->rtm_scope = RT_SCOPE_UNIVERSE; | |
1424 | msg->rtm_type = RTN_UNICAST; | |
1425 | ||
1426 | chunk = chunk_from_thing(prio); | |
1427 | netlink_add_attribute(hdr, RTA_PRIORITY, chunk, sizeof(request)); | |
1428 | ||
1429 | return this->socket->send_ack(this->socket, hdr); | |
1430 | } | |
1431 | ||
1432 | /** | |
1433 | * Implementation of kernel_netlink_net_t.destroy. | |
1434 | */ | |
1435 | static void destroy(private_kernel_netlink_net_t *this) | |
1436 | { | |
1437 | if (this->routing_table) | |
1438 | { | |
5be75c2c MW |
1439 | manage_rule(this, RTM_DELRULE, AF_INET, this->routing_table, |
1440 | this->routing_table_prio); | |
1441 | manage_rule(this, RTM_DELRULE, AF_INET6, this->routing_table, | |
507f26f6 TB |
1442 | this->routing_table_prio); |
1443 | } | |
d6a27ec6 MW |
1444 | if (this->job) |
1445 | { | |
1446 | this->job->cancel(this->job); | |
1447 | } | |
1448 | if (this->socket_events > 0) | |
1449 | { | |
1450 | close(this->socket_events); | |
1451 | } | |
1452 | DESTROY_IF(this->socket); | |
507f26f6 | 1453 | this->ifaces->destroy_function(this->ifaces, (void*)iface_entry_destroy); |
d266e895 | 1454 | this->rt_exclude->destroy(this->rt_exclude); |
3ac5a0db MW |
1455 | this->condvar->destroy(this->condvar); |
1456 | this->mutex->destroy(this->mutex); | |
507f26f6 TB |
1457 | free(this); |
1458 | } | |
1459 | ||
1460 | /* | |
1461 | * Described in header. | |
1462 | */ | |
1463 | kernel_netlink_net_t *kernel_netlink_net_create() | |
1464 | { | |
1465 | private_kernel_netlink_net_t *this = malloc_thing(private_kernel_netlink_net_t); | |
1466 | struct sockaddr_nl addr; | |
d266e895 TE |
1467 | enumerator_t *enumerator; |
1468 | char *exclude; | |
7daf5226 | 1469 | |
507f26f6 TB |
1470 | /* public functions */ |
1471 | this->public.interface.get_interface = (char*(*)(kernel_net_t*,host_t*))get_interface_name; | |
1472 | this->public.interface.create_address_enumerator = (enumerator_t*(*)(kernel_net_t*,bool,bool))create_address_enumerator; | |
ce5b1708 | 1473 | this->public.interface.get_source_addr = (host_t*(*)(kernel_net_t*, host_t *dest, host_t *src))get_source_addr; |
507f26f6 TB |
1474 | this->public.interface.get_nexthop = (host_t*(*)(kernel_net_t*, host_t *dest))get_nexthop; |
1475 | this->public.interface.add_ip = (status_t(*)(kernel_net_t*,host_t*,host_t*)) add_ip; | |
1476 | this->public.interface.del_ip = (status_t(*)(kernel_net_t*,host_t*)) del_ip; | |
1477 | this->public.interface.add_route = (status_t(*)(kernel_net_t*,chunk_t,u_int8_t,host_t*,host_t*,char*)) add_route; | |
1478 | this->public.interface.del_route = (status_t(*)(kernel_net_t*,chunk_t,u_int8_t,host_t*,host_t*,char*)) del_route; | |
1479 | this->public.interface.destroy = (void(*)(kernel_net_t*)) destroy; | |
1480 | ||
1481 | /* private members */ | |
1482 | this->ifaces = linked_list_create(); | |
d1769942 | 1483 | this->mutex = mutex_create(MUTEX_TYPE_RECURSIVE); |
3901937d | 1484 | this->condvar = condvar_create(CONDVAR_TYPE_DEFAULT); |
507f26f6 TB |
1485 | timerclear(&this->last_roam); |
1486 | this->routing_table = lib->settings->get_int(lib->settings, | |
06cdeac2 | 1487 | "%s.routing_table", ROUTING_TABLE, hydra->daemon); |
507f26f6 | 1488 | this->routing_table_prio = lib->settings->get_int(lib->settings, |
06cdeac2 | 1489 | "%s.routing_table_prio", ROUTING_TABLE_PRIO, hydra->daemon); |
507f26f6 | 1490 | this->process_route = lib->settings->get_bool(lib->settings, |
06cdeac2 | 1491 | "%s.process_route", TRUE, hydra->daemon); |
9474a0d9 | 1492 | this->install_virtual_ip = lib->settings->get_bool(lib->settings, |
06cdeac2 | 1493 | "%s.install_virtual_ip", TRUE, hydra->daemon); |
7daf5226 | 1494 | |
d266e895 TE |
1495 | this->rt_exclude = linked_list_create(); |
1496 | exclude = lib->settings->get_str(lib->settings, | |
06cdeac2 | 1497 | "%s.ignore_routing_tables", NULL, hydra->daemon); |
d266e895 TE |
1498 | if (exclude) |
1499 | { | |
1500 | char *token; | |
1501 | uintptr_t table; | |
1502 | ||
1503 | enumerator = enumerator_create_token(exclude, " ", " "); | |
1504 | while (enumerator->enumerate(enumerator, &token)) | |
1505 | { | |
1506 | errno = 0; | |
1507 | table = strtoul(token, NULL, 10); | |
1508 | ||
1509 | if (errno == 0) | |
1510 | { | |
1511 | this->rt_exclude->insert_last(this->rt_exclude, (void*)table); | |
1512 | } | |
1513 | } | |
1514 | enumerator->destroy(enumerator); | |
1515 | } | |
1516 | ||
507f26f6 | 1517 | this->socket = netlink_socket_create(NETLINK_ROUTE); |
d6a27ec6 | 1518 | this->job = NULL; |
7daf5226 | 1519 | |
507f26f6 TB |
1520 | memset(&addr, 0, sizeof(addr)); |
1521 | addr.nl_family = AF_NETLINK; | |
7daf5226 | 1522 | |
507f26f6 TB |
1523 | /* create and bind RT socket for events (address/interface/route changes) */ |
1524 | this->socket_events = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); | |
d6a27ec6 | 1525 | if (this->socket_events < 0) |
507f26f6 | 1526 | { |
d6a27ec6 MW |
1527 | DBG1(DBG_KNL, "unable to create RT event socket"); |
1528 | destroy(this); | |
1529 | return NULL; | |
507f26f6 | 1530 | } |
7daf5226 | 1531 | addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | |
507f26f6 TB |
1532 | RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_ROUTE | RTMGRP_LINK; |
1533 | if (bind(this->socket_events, (struct sockaddr*)&addr, sizeof(addr))) | |
1534 | { | |
d6a27ec6 MW |
1535 | DBG1(DBG_KNL, "unable to bind RT event socket"); |
1536 | destroy(this); | |
1537 | return NULL; | |
507f26f6 | 1538 | } |
7daf5226 | 1539 | |
507f26f6 TB |
1540 | this->job = callback_job_create((callback_job_cb_t)receive_events, |
1541 | this, NULL, NULL); | |
bb381e26 | 1542 | lib->processor->queue_job(lib->processor, (job_t*)this->job); |
7daf5226 | 1543 | |
507f26f6 TB |
1544 | if (init_address_list(this) != SUCCESS) |
1545 | { | |
d6a27ec6 MW |
1546 | DBG1(DBG_KNL, "unable to get interface list"); |
1547 | destroy(this); | |
1548 | return NULL; | |
507f26f6 | 1549 | } |
7daf5226 | 1550 | |
507f26f6 TB |
1551 | if (this->routing_table) |
1552 | { | |
5be75c2c MW |
1553 | if (manage_rule(this, RTM_NEWRULE, AF_INET, this->routing_table, |
1554 | this->routing_table_prio) != SUCCESS) | |
1555 | { | |
1556 | DBG1(DBG_KNL, "unable to create IPv4 routing table rule"); | |
1557 | } | |
1558 | if (manage_rule(this, RTM_NEWRULE, AF_INET6, this->routing_table, | |
507f26f6 TB |
1559 | this->routing_table_prio) != SUCCESS) |
1560 | { | |
5be75c2c | 1561 | DBG1(DBG_KNL, "unable to create IPv6 routing table rule"); |
507f26f6 TB |
1562 | } |
1563 | } | |
7daf5226 | 1564 | |
507f26f6 TB |
1565 | return &this->public; |
1566 | } |