]> git.ipfire.org Git - thirdparty/strongswan.git/blob - src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.c
Update copyright headers after acquisition by secunet
[thirdparty/strongswan.git] / src / libcharon / plugins / kernel_netlink / kernel_netlink_shared.c
1 /*
2 * Copyright (C) 2014 Martin Willi
3 * Copyright (C) 2008-2020 Tobias Brunner
4 *
5 * Copyright (C) secunet Security Networks AG
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; either version 2 of the License, or (at your
10 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 * for more details.
16 */
17
18 /*
19 * Copyright (C) 2016 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
40 #include <sys/socket.h>
41 #include <linux/netlink.h>
42 #include <linux/rtnetlink.h>
43 #include <linux/xfrm.h>
44 #include <errno.h>
45 #include <unistd.h>
46
47 #include "kernel_netlink_shared.h"
48
49 #include <utils/debug.h>
50 #include <threading/mutex.h>
51 #include <threading/condvar.h>
52 #include <collections/array.h>
53 #include <collections/hashtable.h>
54
55 typedef struct private_netlink_socket_t private_netlink_socket_t;
56
57 /**
58 * Private variables and functions of netlink_socket_t class.
59 */
60 struct private_netlink_socket_t {
61
62 /**
63 * public part of the netlink_socket_t object.
64 */
65 netlink_socket_t public;
66
67 /**
68 * mutex to lock access entries
69 */
70 mutex_t *mutex;
71
72 /**
73 * Netlink request entries currently active, uintptr_t seq => entry_t
74 */
75 hashtable_t *entries;
76
77 /**
78 * Current sequence number for Netlink requests
79 */
80 refcount_t seq;
81
82 /**
83 * netlink socket
84 */
85 int socket;
86
87 /**
88 * Netlink protocol
89 */
90 int protocol;
91
92 /**
93 * Enum names for Netlink messages
94 */
95 enum_name_t *names;
96
97 /**
98 * Timeout for Netlink replies, in ms
99 */
100 u_int timeout;
101
102 /**
103 * Number of times to repeat timed out queries
104 */
105 u_int retries;
106
107 /**
108 * Buffer size for received Netlink messages
109 */
110 u_int buflen;
111
112 /**
113 * Use parallel netlink queries
114 */
115 bool parallel;
116
117 /**
118 * Ignore errors potentially resulting from a retransmission
119 */
120 bool ignore_retransmit_errors;
121 };
122
123 /**
124 * #definable hook to simulate request message loss
125 */
126 #ifdef NETLINK_MSG_LOSS_HOOK
127 bool NETLINK_MSG_LOSS_HOOK(struct nlmsghdr *msg);
128 #define msg_loss_hook(msg) NETLINK_MSG_LOSS_HOOK(msg)
129 #else
130 #define msg_loss_hook(msg) FALSE
131 #endif
132
133 /**
134 * Request entry the answer for a waiting thread is collected in
135 */
136 typedef struct {
137 /** Condition variable thread is waiting */
138 condvar_t *condvar;
139 /** Array of hdrs in a multi-message response, as struct nlmsghdr* */
140 array_t *hdrs;
141 /** All response messages received? */
142 bool complete;
143 } entry_t;
144
145 /**
146 * Clean up a thread waiting entry
147 */
148 static void destroy_entry(entry_t *entry)
149 {
150 entry->condvar->destroy(entry->condvar);
151 array_destroy_function(entry->hdrs, (void*)free, NULL);
152 free(entry);
153 }
154
155 /**
156 * Write a Netlink message to socket
157 */
158 static bool write_msg(private_netlink_socket_t *this, struct nlmsghdr *msg)
159 {
160 struct sockaddr_nl addr = {
161 .nl_family = AF_NETLINK,
162 };
163 int len;
164
165 if (msg_loss_hook(msg))
166 {
167 return TRUE;
168 }
169
170 while (TRUE)
171 {
172 len = sendto(this->socket, msg, msg->nlmsg_len, 0,
173 (struct sockaddr*)&addr, sizeof(addr));
174 if (len != msg->nlmsg_len)
175 {
176 if (errno == EINTR)
177 {
178 continue;
179 }
180 DBG1(DBG_KNL, "netlink write error: %s", strerror(errno));
181 return FALSE;
182 }
183 return TRUE;
184 }
185 }
186
187 /**
188 * Read a single Netlink message from socket, return 0 on error, -1 on timeout
189 */
190 static ssize_t read_msg(private_netlink_socket_t *this,
191 char *buf, size_t buflen, bool block)
192 {
193 ssize_t len;
194
195 if (block)
196 {
197 fd_set set;
198 timeval_t tv = {};
199
200 FD_ZERO(&set);
201 FD_SET(this->socket, &set);
202 timeval_add_ms(&tv, this->timeout);
203
204 if (select(this->socket + 1, &set, NULL, NULL,
205 this->timeout ? &tv : NULL) <= 0)
206 {
207 return -1;
208 }
209 }
210 len = recv(this->socket, buf, buflen, MSG_TRUNC|(block ? 0 : MSG_DONTWAIT));
211 if (len > buflen)
212 {
213 DBG1(DBG_KNL, "netlink response exceeds buffer size");
214 return 0;
215 }
216 if (len < 0)
217 {
218 if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
219 {
220 DBG1(DBG_KNL, "netlink read error: %s", strerror(errno));
221 }
222 return 0;
223 }
224 return len;
225 }
226
227 /**
228 * Queue received response message
229 */
230 static bool queue(private_netlink_socket_t *this, struct nlmsghdr *buf)
231 {
232 struct nlmsghdr *hdr;
233 entry_t *entry;
234 uintptr_t seq;
235
236 seq = (uintptr_t)buf->nlmsg_seq;
237
238 this->mutex->lock(this->mutex);
239 entry = this->entries->get(this->entries, (void*)seq);
240 if (entry)
241 {
242 hdr = malloc(buf->nlmsg_len);
243 memcpy(hdr, buf, buf->nlmsg_len);
244 array_insert(entry->hdrs, ARRAY_TAIL, hdr);
245 if (hdr->nlmsg_type == NLMSG_DONE || !(hdr->nlmsg_flags & NLM_F_MULTI))
246 {
247 entry->complete = TRUE;
248 entry->condvar->signal(entry->condvar);
249 }
250 }
251 else
252 {
253 DBG1(DBG_KNL, "received unknown netlink seq %u, ignored", seq);
254 }
255 this->mutex->unlock(this->mutex);
256
257 return entry != NULL;
258 }
259
260 /**
261 * Read and queue response message, optionally blocking, returns TRUE on timeout
262 */
263 static bool read_and_queue(private_netlink_socket_t *this, bool block)
264 {
265 struct nlmsghdr *hdr;
266 char buf[this->buflen];
267 ssize_t len, read_len;
268 bool wipe = FALSE;
269
270 len = read_len = read_msg(this, buf, sizeof(buf), block);
271 if (len == -1)
272 {
273 return TRUE;
274 }
275 if (len)
276 {
277 hdr = (struct nlmsghdr*)buf;
278 while (NLMSG_OK(hdr, len))
279 {
280 if (this->protocol == NETLINK_XFRM &&
281 hdr->nlmsg_type == XFRM_MSG_NEWSA)
282 { /* wipe potential IPsec SA keys */
283 wipe = TRUE;
284 }
285 if (!queue(this, hdr))
286 {
287 break;
288 }
289 hdr = NLMSG_NEXT(hdr, len);
290 }
291 }
292 if (wipe)
293 {
294 memwipe(buf, read_len);
295 }
296 return FALSE;
297 }
298
299 CALLBACK(watch, bool,
300 private_netlink_socket_t *this, int fd, watcher_event_t event)
301 {
302 if (event == WATCHER_READ)
303 {
304 read_and_queue(this, FALSE);
305 }
306 return TRUE;
307 }
308
309 /**
310 * Send a netlink request, try once
311 */
312 static status_t send_once(private_netlink_socket_t *this, struct nlmsghdr *in,
313 uintptr_t seq, struct nlmsghdr **out, size_t *out_len)
314 {
315 struct nlmsghdr *hdr;
316 entry_t *entry;
317 u_char *ptr;
318 int i;
319
320 in->nlmsg_seq = seq;
321 in->nlmsg_pid = getpid();
322
323 if (this->names)
324 {
325 DBG3(DBG_KNL, "sending %N %u: %b", this->names, in->nlmsg_type,
326 (u_int)seq, in, in->nlmsg_len);
327 }
328
329 this->mutex->lock(this->mutex);
330 if (!write_msg(this, in))
331 {
332 this->mutex->unlock(this->mutex);
333 return FAILED;
334 }
335
336 INIT(entry,
337 .condvar = condvar_create(CONDVAR_TYPE_DEFAULT),
338 .hdrs = array_create(0, 0),
339 );
340 this->entries->put(this->entries, (void*)seq, entry);
341
342 while (!entry->complete)
343 {
344 if (this->parallel &&
345 lib->watcher->get_state(lib->watcher) != WATCHER_STOPPED &&
346 lib->processor->get_total_threads(lib->processor))
347 {
348 if (this->timeout)
349 {
350 if (entry->condvar->timed_wait(entry->condvar, this->mutex,
351 this->timeout))
352 {
353 break;
354 }
355 }
356 else
357 {
358 entry->condvar->wait(entry->condvar, this->mutex);
359 }
360 }
361 else
362 { /* During (de-)initialization, no watcher thread is active.
363 * collect responses ourselves. */
364 if (read_and_queue(this, TRUE))
365 {
366 break;
367 }
368 }
369 }
370 this->entries->remove(this->entries, (void*)seq);
371
372 this->mutex->unlock(this->mutex);
373
374 if (!entry->complete)
375 { /* timeout */
376 destroy_entry(entry);
377 return OUT_OF_RES;
378 }
379
380 for (i = 0, *out_len = 0; i < array_count(entry->hdrs); i++)
381 {
382 array_get(entry->hdrs, i, &hdr);
383 *out_len += NLMSG_ALIGN(hdr->nlmsg_len);
384 }
385 ptr = malloc(*out_len);
386 *out = (struct nlmsghdr*)ptr;
387
388 while (array_remove(entry->hdrs, ARRAY_HEAD, &hdr))
389 {
390 if (this->names)
391 {
392 DBG3(DBG_KNL, "received %N %u: %b", this->names, hdr->nlmsg_type,
393 hdr->nlmsg_seq, hdr, hdr->nlmsg_len);
394 }
395 memcpy(ptr, hdr, hdr->nlmsg_len);
396 ptr += NLMSG_ALIGN(hdr->nlmsg_len);
397 free(hdr);
398 }
399 destroy_entry(entry);
400 return SUCCESS;
401 }
402
403 /**
404 * Ignore errors for message types that might have completed previously
405 */
406 static void ignore_retransmit_error(private_netlink_socket_t *this,
407 struct nlmsgerr *err, int type)
408 {
409 switch (err->error)
410 {
411 case -EEXIST:
412 switch (this->protocol)
413 {
414 case NETLINK_XFRM:
415 switch (type)
416 {
417 case XFRM_MSG_NEWPOLICY:
418 case XFRM_MSG_NEWSA:
419 err->error = 0;
420 break;
421 }
422 break;
423 case NETLINK_ROUTE:
424 switch (type)
425 {
426 case RTM_NEWADDR:
427 case RTM_NEWLINK:
428 case RTM_NEWNEIGH:
429 case RTM_NEWROUTE:
430 case RTM_NEWRULE:
431 err->error = 0;
432 break;
433 }
434 break;
435 }
436 break;
437 case -ENOENT:
438 switch (this->protocol)
439 {
440 case NETLINK_XFRM:
441 switch (type)
442 {
443 case XFRM_MSG_DELPOLICY:
444 case XFRM_MSG_DELSA:
445 err->error = 0;
446 break;
447 }
448 break;
449 case NETLINK_ROUTE:
450 switch (type)
451 {
452 case RTM_DELADDR:
453 case RTM_DELLINK:
454 case RTM_DELNEIGH:
455 case RTM_DELROUTE:
456 case RTM_DELRULE:
457 err->error = 0;
458 break;
459 }
460 break;
461 }
462 break;
463 }
464 }
465
466 METHOD(netlink_socket_t, netlink_send, status_t,
467 private_netlink_socket_t *this, struct nlmsghdr *in, struct nlmsghdr **out,
468 size_t *out_len)
469 {
470 uintptr_t seq;
471 u_int try;
472
473 seq = ref_get(&this->seq);
474
475 for (try = 0; try <= this->retries; ++try)
476 {
477 struct nlmsghdr *hdr;
478 status_t status;
479 size_t len;
480
481 if (try > 0)
482 {
483 DBG1(DBG_KNL, "retransmitting Netlink request (%u/%u)",
484 try, this->retries);
485 }
486 status = send_once(this, in, seq, &hdr, &len);
487 switch (status)
488 {
489 case SUCCESS:
490 break;
491 case OUT_OF_RES:
492 continue;
493 default:
494 return status;
495 }
496 if (hdr->nlmsg_type == NLMSG_ERROR)
497 {
498 struct nlmsgerr* err;
499
500 err = NLMSG_DATA(hdr);
501 if (err->error == -EBUSY)
502 {
503 free(hdr);
504 try--;
505 continue;
506 }
507 if (this->ignore_retransmit_errors && try > 0)
508 {
509 ignore_retransmit_error(this, err, in->nlmsg_type);
510 }
511 }
512 *out = hdr;
513 *out_len = len;
514 return SUCCESS;
515 }
516 DBG1(DBG_KNL, "Netlink request timed out after %u retransmits",
517 this->retries);
518 return OUT_OF_RES;
519 }
520
521 METHOD(netlink_socket_t, netlink_send_ack, status_t,
522 private_netlink_socket_t *this, struct nlmsghdr *in)
523 {
524 struct nlmsghdr *out, *hdr;
525 size_t len;
526
527 if (netlink_send(this, in, &out, &len) != SUCCESS)
528 {
529 return FAILED;
530 }
531 hdr = out;
532 while (NLMSG_OK(hdr, len))
533 {
534 switch (hdr->nlmsg_type)
535 {
536 case NLMSG_ERROR:
537 {
538 struct nlmsgerr* err = NLMSG_DATA(hdr);
539
540 if (err->error)
541 {
542 if (-err->error == EEXIST)
543 { /* do not report existing routes */
544 free(out);
545 return ALREADY_DONE;
546 }
547 if (-err->error == ESRCH)
548 { /* do not report missing entries */
549 free(out);
550 return NOT_FOUND;
551 }
552 DBG1(DBG_KNL, "received netlink error: %s (%d)",
553 strerror(-err->error), -err->error);
554 free(out);
555 return FAILED;
556 }
557 free(out);
558 return SUCCESS;
559 }
560 default:
561 hdr = NLMSG_NEXT(hdr, len);
562 continue;
563 case NLMSG_DONE:
564 break;
565 }
566 break;
567 }
568 DBG1(DBG_KNL, "netlink request not acknowledged");
569 free(out);
570 return FAILED;
571 }
572
573 METHOD(netlink_socket_t, destroy, void,
574 private_netlink_socket_t *this)
575 {
576 if (this->socket != -1)
577 {
578 if (this->parallel)
579 {
580 lib->watcher->remove(lib->watcher, this->socket);
581 }
582 close(this->socket);
583 }
584 this->entries->destroy(this->entries);
585 this->mutex->destroy(this->mutex);
586 free(this);
587 }
588
589 /*
590 * Described in header
591 */
592 u_int netlink_get_buflen()
593 {
594 u_int buflen;
595
596 buflen = lib->settings->get_int(lib->settings,
597 "%s.plugins.kernel-netlink.buflen", 0, lib->ns);
598 if (!buflen)
599 {
600 long pagesize = sysconf(_SC_PAGESIZE);
601
602 if (pagesize == -1)
603 {
604 pagesize = 4096;
605 }
606 /* base this on NLMSG_GOODSIZE */
607 buflen = min(pagesize, 8192);
608 }
609 return buflen;
610 }
611
612 /*
613 * Described in header
614 */
615 netlink_socket_t *netlink_socket_create(int protocol, enum_name_t *names,
616 bool parallel)
617 {
618 private_netlink_socket_t *this;
619 struct sockaddr_nl addr = {
620 .nl_family = AF_NETLINK,
621 };
622 bool force_buf = FALSE;
623 int rcvbuf_size = 0;
624
625 INIT(this,
626 .public = {
627 .send = _netlink_send,
628 .send_ack = _netlink_send_ack,
629 .destroy = _destroy,
630 },
631 .seq = 200,
632 .mutex = mutex_create(MUTEX_TYPE_RECURSIVE),
633 .socket = socket(AF_NETLINK, SOCK_RAW, protocol),
634 .entries = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4),
635 .protocol = protocol,
636 .names = names,
637 .buflen = netlink_get_buflen(),
638 .timeout = lib->settings->get_int(lib->settings,
639 "%s.plugins.kernel-netlink.timeout", 0, lib->ns),
640 .retries = lib->settings->get_int(lib->settings,
641 "%s.plugins.kernel-netlink.retries", 0, lib->ns),
642 .ignore_retransmit_errors = lib->settings->get_bool(lib->settings,
643 "%s.plugins.kernel-netlink.ignore_retransmit_errors",
644 FALSE, lib->ns),
645 .parallel = parallel,
646 );
647
648 if (this->socket == -1)
649 {
650 DBG1(DBG_KNL, "unable to create netlink socket: %s (%d)",
651 strerror(errno), errno);
652 destroy(this);
653 return NULL;
654 }
655 if (bind(this->socket, (struct sockaddr*)&addr, sizeof(addr)))
656 {
657 DBG1(DBG_KNL, "unable to bind netlink socket: %s (%d)",
658 strerror(errno), errno);
659 destroy(this);
660 return NULL;
661 }
662 rcvbuf_size = lib->settings->get_int(lib->settings,
663 "%s.plugins.kernel-netlink.receive_buffer_size",
664 rcvbuf_size, lib->ns);
665 if (rcvbuf_size)
666 {
667 int optname;
668
669 force_buf = lib->settings->get_bool(lib->settings,
670 "%s.plugins.kernel-netlink.force_receive_buffer_size",
671 force_buf, lib->ns);
672 optname = force_buf ? SO_RCVBUFFORCE : SO_RCVBUF;
673
674 if (setsockopt(this->socket, SOL_SOCKET, optname, &rcvbuf_size,
675 sizeof(rcvbuf_size)) == -1)
676 {
677 DBG1(DBG_KNL, "failed to %supdate receive buffer size to %d: %s",
678 force_buf ? "forcibly " : "", rcvbuf_size, strerror(errno));
679 }
680 }
681 if (this->parallel)
682 {
683 lib->watcher->add(lib->watcher, this->socket, WATCHER_READ, watch, this);
684 }
685
686 return &this->public;
687 }
688
689 /*
690 * Described in header
691 */
692 void netlink_add_attribute(struct nlmsghdr *hdr, int rta_type, chunk_t data,
693 size_t buflen)
694 {
695 struct rtattr *rta;
696
697 if (NLMSG_ALIGN(hdr->nlmsg_len) + RTA_LENGTH(data.len) > buflen)
698 {
699 DBG1(DBG_KNL, "unable to add attribute, buffer too small");
700 return;
701 }
702
703 rta = (struct rtattr*)(((char*)hdr) + NLMSG_ALIGN(hdr->nlmsg_len));
704 rta->rta_type = rta_type;
705 rta->rta_len = RTA_LENGTH(data.len);
706 memcpy(RTA_DATA(rta), data.ptr, data.len);
707 hdr->nlmsg_len = NLMSG_ALIGN(hdr->nlmsg_len) + RTA_ALIGN(rta->rta_len);
708 }
709
710 /**
711 * Add an attribute to the given Netlink message
712 */
713 static struct rtattr *add_rtattr(struct nlmsghdr *hdr, int buflen, int type,
714 int len)
715 {
716 struct rtattr *rta;
717
718 if (NLMSG_ALIGN(hdr->nlmsg_len) + RTA_LENGTH(len) > buflen)
719 {
720 DBG1(DBG_KNL, "unable to add attribute, buffer too small");
721 return NULL;
722 }
723
724 rta = ((void*)hdr) + NLMSG_ALIGN(hdr->nlmsg_len);
725 rta->rta_type = type;
726 rta->rta_len = RTA_LENGTH(len);
727 hdr->nlmsg_len = NLMSG_ALIGN(hdr->nlmsg_len) + RTA_ALIGN(rta->rta_len);
728 return rta;
729 }
730
731 /*
732 * Described in header
733 */
734 void *netlink_nested_start(struct nlmsghdr *hdr, size_t buflen, int type)
735 {
736 return add_rtattr(hdr, buflen, type, 0);
737 }
738
739 /*
740 * Described in header
741 */
742 void netlink_nested_end(struct nlmsghdr *hdr, void *attr)
743 {
744 struct rtattr *rta = attr;
745 void *end;
746
747 if (attr)
748 {
749 end = (char*)hdr + NLMSG_ALIGN(hdr->nlmsg_len);
750 rta->rta_len = end - attr;
751 }
752 }
753
754 /*
755 * Described in header
756 */
757 void *netlink_reserve(struct nlmsghdr *hdr, int buflen, int type, int len)
758 {
759 struct rtattr *rta;
760
761 rta = add_rtattr(hdr, buflen, type, len);
762 if (!rta)
763 {
764 return NULL;
765 }
766 return RTA_DATA(rta);
767 }
768
769 /*
770 * Described in header
771 */
772 void route_entry_destroy(route_entry_t *this)
773 {
774 free(this->if_name);
775 DESTROY_IF(this->src_ip);
776 DESTROY_IF(this->gateway);
777 chunk_free(&this->dst_net);
778 free(this);
779 }
780
781 /*
782 * Described in header
783 */
784 route_entry_t *route_entry_clone(const route_entry_t *this)
785 {
786 route_entry_t *route;
787
788 INIT(route,
789 .if_name = strdupnull(this->if_name),
790 .src_ip = this->src_ip ? this->src_ip->clone(this->src_ip) : NULL,
791 .gateway = this->gateway ? this->gateway->clone(this->gateway) : NULL,
792 .dst_net = chunk_clone(this->dst_net),
793 .prefixlen = this->prefixlen,
794 .pass = this->pass,
795 );
796 return route;
797 }
798
799 /*
800 * Described in header
801 */
802 u_int route_entry_hash(const route_entry_t *this)
803 {
804 return chunk_hash_inc(chunk_from_thing(this->prefixlen),
805 chunk_hash(this->dst_net));
806 }
807
808 /**
809 * Compare two IP addresses, also accept it if both are NULL
810 */
811 static bool addrs_null_or_equal(host_t *a, host_t *b)
812 {
813 return (!a && !b) || (a && b && a->ip_equals(a, b));
814 }
815
816 /*
817 * Described in header
818 */
819 bool route_entry_equals(const route_entry_t *a, const route_entry_t *b)
820 {
821 return streq(a->if_name, b->if_name) &&
822 a->pass == b->pass &&
823 a->prefixlen == b->prefixlen &&
824 chunk_equals(a->dst_net, b->dst_net) &&
825 addrs_null_or_equal(a->src_ip, b->src_ip) &&
826 addrs_null_or_equal(a->gateway, b->gateway);
827 }