1 /*
2 * $Id: getoutaddr.c,v 1.140.4.3.2.3 2017/01/31 08:17:38 karls Exp $
3 *
4 * Copyright (c) 2001, 2002, 2006, 2008, 2009, 2010, 2011, 2012, 2013, 2014,
5 * 2016, 2017
6 * Inferno Nettverk A/S, Norway. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. The above copyright notice, this list of conditions and the following
12 * disclaimer must appear in all copies of the software, derivative works
13 * or modified versions, and any portions thereof, aswell as in all
14 * supporting documentation.
15 * 2. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software developed by
18 * Inferno Nettverk A/S, Norway.
19 * 3. The name of the author may not be used to endorse or promote products
20 * derived from this software without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 *
33 * Inferno Nettverk A/S requests users of this software to return to
34 *
35 * Software Distribution Coordinator or sdc@inet.no
36 * Inferno Nettverk A/S
37 * Oslo Research Park
38 * Gaustadall�en 21
39 * NO-0349 Oslo
40 * Norway
41 *
42 * any improvements or extensions that they make and grant Inferno Nettverk A/S
43 * the rights to redistribute these changes.
44 *
45 */
46
47 #include "common.h"
48
49 static const char rcsid[] =
50 "$Id: getoutaddr.c,v 1.140.4.3.2.3 2017/01/31 08:17:38 karls Exp $";
51
52 static int
53 addrscope_matches(const struct sockaddr_in6 *addr,
54 const ipv6_addrscope_t addrscope);
55 /*
56 * Returns true if address-scope "addrscope" matches the address scope
57 * of address "addr".
58 */
59
60 static int
61 ifname_matches(const char *ifname, const uint32_t ifindex);
62 /*
63 * Returns true if there is an interface with the name "ifname", and
64 * the index of this interface is "ifinxex".
65 *
66 * Returns false otherwise.
67 */
68
69 static sa_family_t
70 get_external_safamily(const struct sockaddr_storage *client,
71 const int command, const sockshost_t *reqhost);
72 /*
73 * Returns the sa_family_t that should be used for a client with address
74 * "client", requesting the SOCKS command "command" with the request host
75 * "reqhost".
76 *
77 * On success the sa_family_t that should be used.
78 * On failure returns AF_UNSPEC.
79 */
80
81
82 static struct sockaddr_storage *
83 getdefaultexternal(const sa_family_t safamily, ipv6_addrscope_t addrscope,
84 const uint32_t ifindex, struct sockaddr_storage *addr);
85 /*
86 * Returns the default IP address of sa_family_t "safamily" to use for
87 * external connections. The portnumber in the address returned should
88 * be ignored.
89 *
90 * "safamily" can be set to AF_UNSPEC if the function can return an
91 * address of any sa_family_t. If there is no address of type safamily
92 * available, the semantics are the same as if safamily was set to AF_UNSPEC.
93 *
94 * "addrscope" indicates the scopeid of the external addresses we want
95 * returned. "addrscope" only applies if "safamily" is AF_INET6.
96 * If "addrscope" is addrscope_linklocal, "ifindex" indicates the interface
97 * the returned address should be configured on. Otherwise, "ifindex" is
98 * ignored.
99 *
100 * The ipaddress to use is stored in "addr", and a pointer to it is returned.
101 */
102
103 struct sockaddr_storage *
getoutaddr(laddr,client_l,client_r,cmd,reqhost,emsg,emsglen)104 getoutaddr(laddr, client_l, client_r, cmd, reqhost, emsg, emsglen)
105 struct sockaddr_storage *laddr;
106 const struct sockaddr_storage *client_l;
107 const struct sockaddr_storage *client_r;
108 const int cmd;
109 const sockshost_t *reqhost;
110 char *emsg;
111 const size_t emsglen;
112 {
113 const char *function = "getoutaddr()";
114 struct sockaddr_storage raddr;
115 char addrstr[MAXSOCKADDRSTRING], raddrstr[MAXSOCKSHOSTSTRING];
116
117 slog(LOG_DEBUG,
118 "%s: client %s, cmd %s, reqhost %s, external.rotation = %s",
119 function,
120 sockaddr2string(client_r, addrstr, sizeof(addrstr)),
121 command2string(cmd),
122 sockshost2string(reqhost, raddrstr, sizeof(raddrstr)),
123 rotation2string(sockscf.external.rotation));
124
125 bzero(&raddr, sizeof(raddr));
126
127 /*
128 * First figure out what /type/ of address (ipv4 or ipv6) we need to
129 * bind on the external side.
130 */
131 switch (cmd) {
132 case SOCKS_BIND:
133 if (reqhost->atype == SOCKS_ADDR_IPV4
134 && reqhost->addr.ipv4.s_addr == htonl(BINDEXTENSION_IPADDR))
135 SET_SOCKADDR(&raddr, external_has_global_safamily(AF_INET) ?
136 AF_INET : AF_INET6);
137 else if (reqhost->atype == SOCKS_ADDR_IPV4
138 || reqhost->atype == SOCKS_ADDR_IPV6)
139 sockshost2sockaddr(reqhost, &raddr);
140 else {
141 /*
142 * Have to expect the bindreply from an address given as a
143 * hostname by the client. Since we may have multiple address
144 * families configured on the external interface on which we
145 * can accept the bindreply on, we need to bind an address of
146 * the correct type.
147 *
148 * For now we assume that the type of address returned first
149 * is the type of address we should bind.
150 */
151 int gaierr;
152
153 SASSERTX(reqhost->atype == SOCKS_ADDR_DOMAIN);
154
155 sockshost2sockaddr2(reqhost, &raddr, &gaierr, emsg, emsglen);
156 if (gaierr != 0) {
157 log_resolvefailed(reqhost->addr.domain, EXTERNALIF, gaierr);
158 return NULL;
159 }
160 }
161
162 break;
163
164 case SOCKS_CONNECT:
165 case SOCKS_UDPASSOCIATE: {
166 int gaierr;
167
168 sockshost2sockaddr2(reqhost, &raddr, &gaierr, emsg, emsglen);
169 if (gaierr != 0) {
170 SASSERTX(reqhost->atype == SOCKS_ADDR_DOMAIN);
171
172 log_resolvefailed(reqhost->addr.domain, EXTERNALIF, gaierr);
173 return NULL;
174 }
175
176 break;
177 }
178
179 default:
180 SERRX(cmd);
181 }
182
183 switch (sockscf.external.rotation) {
184 case ROTATION_NONE:
185 /*
186 * We have a complicating factor here regarding IPv6 scopeids.
187 * If the target destination is specified with a non-global
188 * scopeid (e.g. a link-local address), we need to bind an address
189 * not only of the same type, but on the *same link/interface* too.
190 */
191
192 getdefaultexternal(raddr.ss_family,
193 raddr.ss_family == AF_INET6 ?
194 ipv6_addrscope(&TOIN6(&raddr)->sin6_addr)
195 : addrscope_global,
196 TOIN6(&raddr)->sin6_scope_id,
197 laddr);
198 break;
199
200 case ROTATION_SAMESAME:
201 if (client_l->ss_family == raddr.ss_family)
202 *laddr = *client_l;
203 else {
204 snprintf(emsg, emsglen,
205 "rotation for external addresses is set to %s, but "
206 "that can not work for this %s request. "
207 "The internal address we accepted the client on is "
208 "a %s (%s), but the target address is a %s (%s)",
209 rotation2string(sockscf.external.rotation),
210 command2string(cmd),
211 safamily2string(client_l->ss_family),
212 sockaddr2string(client_l, NULL, 0),
213 safamily2string(raddr.ss_family),
214 sockaddr2string(&raddr, raddrstr, sizeof(raddrstr)));
215
216 swarnx("%s: %s", function, emsg);
217 return NULL;
218 }
219
220 break;
221
222 case ROTATION_ROUTE: {
223 if (IPADDRISBOUND(&raddr)) {
224 /*
225 * Connect a udp socket and check what local address was chosen
226 * by the kernel for connecting to dst. Idea from Quagga source.
227 */
228 sockshost_t host;
229 int s;
230
231 if ((s = socket(raddr.ss_family, SOCK_DGRAM, 0)) == -1) {
232 snprintf(emsg, emsglen,
233 "could not create new %s UDP socket with socket(2): %s",
234 safamily2string(raddr.ss_family), strerror(errno));
235
236 swarn("%s: %s", function, emsg);
237 return NULL;
238 }
239
240 if (!PORTISBOUND(&raddr))
241 /* use any valid portnumber. This is just a dry-run */
242 SET_SOCKADDRPORT(&raddr, 1);
243
244 sockaddr2sockshost(&raddr, &host);
245 if (socks_connecthost(s,
246 EXTERNALIF,
247 &host,
248 laddr,
249 NULL,
250 -1,
251 emsg,
252 emsglen) == -1) {
253 slog(LOG_DEBUG, "%s: %s", function, emsg);
254 close(s);
255
256 if (cmd == SOCKS_UDPASSOCIATE)
257 return NULL;
258
259 /*
260 * Else: continue.
261 * While highly unlikely it will work later, when we actually
262 * do try to connect/send data, it could be the local (but
263 * external to Dante) configuration is to block udp packets to
264 * this destination, but allow tcp.
265 */
266 getdefaultexternal(raddr.ss_family,
267 raddr.ss_family == AF_INET6 ?
268 ipv6_addrscope(&TOIN6(&raddr)->sin6_addr)
269 : addrscope_global,
270 TOIN6(&raddr)->sin6_scope_id,
271 laddr);
272 break;
273 }
274
275 close(s);
276 }
277 else
278 getdefaultexternal(get_external_safamily(client_r, cmd, reqhost),
279 addrscope_global,
280 0,
281 laddr);
282
283 break;
284 }
285
286 default:
287 SERRX(sockscf.external.rotation);
288 }
289
290 if (addrindex_on_externallist(&sockscf.external, laddr) != -1)
291 slog(LOG_DEBUG,
292 "%s: local address %s selected for forwarding from client %s to %s",
293 function,
294 sockaddr2string2(laddr, 0, addrstr, sizeof(addrstr)),
295 sockaddr2string(client_r, NULL, 0),
296 sockaddr2string(&raddr, raddrstr, sizeof(raddrstr)));
297 else {
298 char rotation[256];
299
300 if (sockscf.external.rotation == ROTATION_NONE)
301 *rotation = NUL; /* default. Don't print anything confusing. */
302 else
303 snprintf(rotation, sizeof(rotation),
304 "using external.rotation = %s, ",
305 rotation2string(sockscf.external.rotation));
306
307 if (IPADDRISBOUND(laddr))
308 snprintf(emsg, emsglen,
309 "%slocal address %s was selected for forwarding from our "
310 "local client %s to target %s, but that local address is "
311 "not set on our external interface(s). Configuration "
312 "error in %s?",
313 rotation,
314 sockaddr2string2(laddr, 0, addrstr, sizeof(addrstr)),
315 sockaddr2string(client_r, NULL, 0),
316 sockaddr2string(&raddr, raddrstr, sizeof(raddrstr)),
317 sockscf.option.configfile);
318 else
319 snprintf(emsg, emsglen,
320 "%snone of the addresses configured on our external "
321 "interface(s) can be used for forwarding from our local "
322 "client %s to target %s. Configuration error in %s?",
323 rotation,
324 sockaddr2string(client_r, NULL, 0),
325 sockaddr2string(&raddr, raddrstr, sizeof(raddrstr)),
326 sockscf.option.configfile);
327
328 /*
329 * Assume that if the user has not configured any such address,
330 * his intention is to not support that af (e.g., support only
331 * ipv4). If he has configured such an address, but we could for
332 * some reason not use it, it's more likely to be a configuration
333 * error though, so warn.
334 */
335 slog(external_has_safamily(laddr->ss_family) ? LOG_WARNING : LOG_DEBUG,
336 "%s: %s", function, emsg);
337
338 return NULL;
339 }
340
341 /*
342 * Try to set the local port to the best value also, though this is mostly
343 * just guessing for all but the bind-case.
344 */
345 switch (cmd) {
346 #if SOCKS_SERVER
347 case SOCKS_BIND:
348 if (reqhost->atype == SOCKS_ADDR_IPV4
349 && reqhost->addr.ipv4.s_addr == htonl(BINDEXTENSION_IPADDR))
350 SET_SOCKADDRPORT(laddr, GET_SOCKADDRPORT(client_r));
351 else if ( (raddr.ss_family == AF_INET
352 && TOIN(&raddr)->sin_addr.s_addr == htonl(INADDR_ANY))
353 || (raddr.ss_family == AF_INET6
354 && memcmp(&TOIN6(&raddr)->sin6_addr,
355 &in6addr_any,
356 sizeof(in6addr_any)) == 0))
357 SET_SOCKADDRPORT(laddr, reqhost->port);
358 else
359 SET_SOCKADDRPORT(laddr, htons(0));
360
361 break;
362 #endif /* SOCKS_SERVER */
363
364 case SOCKS_CONNECT:
365 case SOCKS_UDPASSOCIATE: /* reqhost is the target of the first packet. */
366 SET_SOCKADDRPORT(laddr, GET_SOCKADDRPORT(client_r));
367 break;
368
369 default:
370 SERRX(cmd);
371 }
372
373 SASSERTX(IPADDRISBOUND(laddr));
374
375 return laddr;
376 }
377
378 struct sockaddr_storage *
getinaddr(laddr,_client,emsg,emsglen)379 getinaddr(laddr, _client, emsg, emsglen)
380 struct sockaddr_storage *laddr;
381 const struct sockaddr_storage *_client;
382 char *emsg;
383 const size_t emsglen;
384 {
385 const char *function = "getinaddr()";
386 struct sockaddr_storage client;
387 size_t i;
388 int wildcard_address_found = 0;
389
390 slog(LOG_DEBUG, "%s: client %s",
391 function, sockaddr2string(_client, NULL, 0));
392
393 SASSERTX(_client->ss_family == AF_INET || _client->ss_family == AF_INET6);
394
395 sockaddrcpy(&client, _client, sizeof(client));
396
397 /*
398 * Just return the first address of the appropriate type from our
399 * internal list and hope the best.
400 */
401 for (i = 0; i < sockscf.internal.addrc; ++i) {
402 if (sockscf.internal.addrv[i].addr.ss_family == client.ss_family) {
403 if (IPADDRISBOUND(&sockscf.internal.addrv[i].addr)) {
404 sockaddrcpy(laddr, &sockscf.internal.addrv[i].addr, sizeof(*laddr));
405
406 slog(LOG_DEBUG, "%s: address %s selected",
407 function, sockaddr2string(laddr, NULL, 0));
408
409 return laddr;
410 }
411 else
412 wildcard_address_found = 1;
413 }
414 }
415
416 if (wildcard_address_found)
417 snprintf(emsg, emsglen,
418 "no specific %s found amongst the internal addresses, only "
419 "an unbound wildcard address found. This client requires "
420 "an internal IP-address to be specified in %s however",
421 safamily2string(client.ss_family), SOCKD_CONFIGFILE);
422 else
423 snprintf(emsg, emsglen, "no %s found amongst the internal addresses",
424 safamily2string(client.ss_family));
425
426 slog(wildcard_address_found ? LOG_NOTICE : LOG_DEBUG,
427 "%s: %s", function, emsg);
428
429 return NULL;
430 }
431
432 static struct sockaddr_storage *
getdefaultexternal(safamily,addrscope,ifindex,addr)433 getdefaultexternal(safamily, addrscope, ifindex, addr)
434 const sa_family_t safamily;
435 const ipv6_addrscope_t addrscope;
436 const uint32_t ifindex;
437 struct sockaddr_storage *addr;
438 {
439 const char *function = "getdefaultexternal()";
440 const char *safamilystring = (safamily == AF_UNSPEC ?
441 "<any address>" : safamily2string(safamily));
442 const char *addrscopestring = addrscope2string(addrscope);
443 size_t i, addrfound;
444
445 slog(LOG_DEBUG, "%s: looking for an %s with scopeid %d/%s, ifindex %d",
446 function, safamilystring, addrscope, addrscopestring, ifindex);
447
448 for (i = 0, addrfound = 0; i < sockscf.external.addrc && !addrfound; ++i) {
449 switch (sockscf.external.addrv[i].atype) {
450 case SOCKS_ADDR_IFNAME: {
451 struct sockaddr_storage mask;
452
453 size_t ii = 0;
454 while (ifname2sockaddr(sockscf.external.addrv[i].addr.ifname,
455 ii++,
456 addr,
457 &mask) != NULL) {
458 if (safamily == AF_UNSPEC || addr->ss_family == safamily) {
459 if (safamily == AF_INET6) {
460 if (!addrscope_matches(TOIN6(addr), addrscope))
461 continue;
462
463 if (addrscope == addrscope_linklocal
464 && !ifname_matches(sockscf.external.addrv[i].addr.ifname,
465 ifindex))
466 continue;
467 }
468
469 addrfound = 1;
470 break;
471 }
472 }
473
474 break;
475 }
476
477 case SOCKS_ADDR_IPV4:
478 case SOCKS_ADDR_IPV6:
479 if (safamily != AF_UNSPEC
480 && safamily != atype2safamily(sockscf.external.addrv[i].atype)) {
481 slog(LOG_DEBUG,
482 "%s: atype of address %s does not match atype %s",
483 function,
484 ruleaddr2string(&sockscf.external.addrv[i],
485 ADDRINFO_ATYPE,
486 NULL,
487 0),
488 safamily2string(safamily));
489 continue;
490 }
491
492 sockshost2sockaddr(ruleaddr2sockshost(&sockscf.external.addrv[i],
493 NULL,
494 SOCKS_TCP),
495 addr);
496
497 if (addr->ss_family == AF_INET6) {
498 const char *ifname;
499
500 if (addrscope == addrscope_nodelocal)
501 /*
502 * a nodelocal address should be able to reach any
503 * other nodelocal address.
504 */
505 ;
506 else if (!addrscope_matches(TOIN6(addr), addrscope))
507 continue;
508
509 if ((ifname = sockaddr2ifname(addr, NULL, 0)) == NULL) {
510 swarnx("%s: could not find any interface with address %s",
511 function, sockaddr2string(addr, NULL, 0));
512
513 continue;
514 }
515
516 if (addrscope == addrscope_linklocal
517 && !ifname_matches(ifname, ifindex))
518 continue;
519 }
520
521 addrfound = 1;
522 break;
523
524 default:
525 SERRX((*sockscf.external.addrv).atype);
526 }
527 }
528
529 if (addrfound)
530 slog(LOG_DEBUG, "%s: matched %s %s",
531 function,
532 safamilystring,
533 sockaddr2string2(addr, ADDRINFO_SCOPEID | ADDRINFO_PORT, NULL, 0));
534 else {
535 slog(LOG_DEBUG,
536 "%s: no matching %s found on external list, using INADDR_ANY",
537 function, safamilystring);
538
539 bzero(addr, sizeof(*addr));
540 SET_SOCKADDR(addr, safamily == AF_UNSPEC ? AF_INET : safamily);
541 }
542
543 return addr;
544 }
545
546 sa_family_t
get_external_safamily(client,command,reqhost)547 get_external_safamily(client, command, reqhost)
548 const struct sockaddr_storage *client;
549 const int command;
550 const sockshost_t *reqhost;
551 {
552 const char *function = "get_external_safamily()";
553 sa_family_t safamily;
554
555 switch (command) {
556 case SOCKS_BIND:
557 case SOCKS_UDPASSOCIATE:
558 switch (reqhost->atype) {
559 case SOCKS_ADDR_IPV4:
560 case SOCKS_ADDR_IPV6:
561 safamily = atype2safamily(reqhost->atype);
562 break;
563
564 case SOCKS_ADDR_DOMAIN: {
565 struct sockaddr_storage p;
566
567 sockshost2sockaddr(reqhost, &p);
568 if (IPADDRISBOUND(&p))
569 safamily = p.ss_family;
570 else
571 safamily = client->ss_family;
572
573 break;
574 }
575
576 default:
577 SERRX(reqhost->atype);
578 }
579 break;
580
581 case SOCKS_CONNECT: {
582 struct sockaddr_storage p;
583
584 sockshost2sockaddr(reqhost, &p);
585 if (IPADDRISBOUND(&p))
586 safamily = p.ss_family;
587 else
588 safamily = client->ss_family;
589
590 break;
591 }
592
593 default:
594 SERRX(command);
595 }
596
597 if (external_has_safamily(safamily))
598 return safamily;
599
600 /*
601 * Do not have the optimal safamily. Anything else we can try?
602 */
603 switch (safamily) {
604 case AF_INET:
605 if (external_has_safamily(AF_INET6))
606 return AF_INET6;
607
608 break;
609
610 case AF_INET6:
611 if (external_has_safamily(AF_INET))
612 return AF_INET;
613
614 break;
615
616 default:
617 SERRX(safamily);
618 }
619
620 swarnx("%s: strange ... could not find any address to bind on external side "
621 "for command %s from client %s. Reqhost is %s. "
622 "Have IPv4? %s. IPv6? %s",
623 function,
624 command2string(command),
625 sockaddr2string(client, NULL, 0),
626 sockshost2string(reqhost, NULL, 0),
627 external_has_safamily(AF_INET) ? "Yes" : "No",
628 external_has_safamily(AF_INET6) ?
629 external_has_global_safamily(AF_INET6) ?
630 "Yes (global)" : "Yes (local only)"
631 : "No");
632
633 return AF_UNSPEC;
634 }
635
636 static int
addrscope_matches(addr,scope)637 addrscope_matches(addr, scope)
638 const struct sockaddr_in6 *addr;
639 const ipv6_addrscope_t scope;
640 {
641 const char *function = "addrscope_matches()";
642 const ipv6_addrscope_t addrscope = ipv6_addrscope(&addr->sin6_addr);
643
644 if (addrscope == scope)
645 return 1;
646
647 if (scope == addrscope_nodelocal
648 && addrscope == addrscope_global)
649 /*
650 * a locally configured global address should be able to connect
651 * to any nodelocal address, but a linklocal address will not
652 * necessarily be able to connect to a nodelocal one, for some
653 * reason. That at least appears to be the case on FreeBSD 9.1.
654 */
655 return 1;
656
657 if (sockscf.option.debug)
658 slog(LOG_DEBUG,
659 "%s: skipping address %s with system scopeid "
660 "0x%x (internal: %d/%s) while searching for "
661 "address with internal scopeid %d/%s",
662 function,
663 sockaddr2string(TOCSS(addr), NULL, 0),
664 addr->sin6_scope_id,
665 ipv6_addrscope(&addr->sin6_addr),
666 addrscope2string(ipv6_addrscope(&addr->sin6_addr)),
667 (int)scope,
668 addrscope2string(scope));
669
670 return 0;
671 }
672
673 static int
ifname_matches(ifname,ifindex)674 ifname_matches(ifname, ifindex)
675 const char *ifname;
676 const uint32_t ifindex;
677 {
678 const char *function = "ifname_matches()";
679 uint32_t ifnameindex;
680 int matches;
681
682 if ((ifnameindex = if_nametoindex(ifname)) == 0) {
683 swarn("%s: if_nametoindex(%s) failed", function, ifname);
684 return 0;
685 }
686
687 matches = (ifnameindex == ifindex);
688
689 slog(LOG_DEBUG, "%s: ifname %s/ifindex %u %s ifindex %u",
690 function,
691 ifname,
692 ifnameindex,
693 matches ? "matches" : "does not match",
694 ifindex);
695
696 return matches;
697 }
698