1 /* $OpenBSD: ip6_mroute.c,v 1.135 2022/09/08 10:22:07 kn Exp $ */
2 /* $NetBSD: ip6_mroute.c,v 1.59 2003/12/10 09:28:38 itojun Exp $ */
3 /* $KAME: ip6_mroute.c,v 1.45 2001/03/25 08:38:51 itojun Exp $ */
4
5 /*
6 * Copyright (C) 1998 WIDE Project.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the project nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34 /* BSDI ip_mroute.c,v 2.10 1996/11/14 00:29:52 jch Exp */
35
36 /*
37 * Copyright (c) 1989 Stephen Deering
38 * Copyright (c) 1992, 1993
39 * The Regents of the University of California. All rights reserved.
40 *
41 * This code is derived from software contributed to Berkeley by
42 * Stephen Deering of Stanford University.
43 *
44 * Redistribution and use in source and binary forms, with or without
45 * modification, are permitted provided that the following conditions
46 * are met:
47 * 1. Redistributions of source code must retain the above copyright
48 * notice, this list of conditions and the following disclaimer.
49 * 2. Redistributions in binary form must reproduce the above copyright
50 * notice, this list of conditions and the following disclaimer in the
51 * documentation and/or other materials provided with the distribution.
52 * 3. Neither the name of the University nor the names of its contributors
53 * may be used to endorse or promote products derived from this software
54 * without specific prior written permission.
55 *
56 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
57 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
58 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
59 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
60 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
61 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
62 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
63 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
64 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
65 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
66 * SUCH DAMAGE.
67 *
68 * @(#)ip_mroute.c 8.2 (Berkeley) 11/15/93
69 */
70
71 /*
72 * IP multicast forwarding procedures
73 *
74 * Written by David Waitzman, BBN Labs, August 1988.
75 * Modified by Steve Deering, Stanford, February 1989.
76 * Modified by Mark J. Steiglitz, Stanford, May, 1991
77 * Modified by Van Jacobson, LBL, January 1993
78 * Modified by Ajit Thyagarajan, PARC, August 1993
79 * Modified by Bill Fenner, PARC, April 1994
80 *
81 * MROUTING Revision: 3.5.1.2
82 */
83
84 #include <sys/param.h>
85 #include <sys/malloc.h>
86 #include <sys/systm.h>
87 #include <sys/timeout.h>
88 #include <sys/mbuf.h>
89 #include <sys/socket.h>
90 #include <sys/socketvar.h>
91 #include <sys/protosw.h>
92 #include <sys/kernel.h>
93 #include <sys/ioctl.h>
94 #include <sys/syslog.h>
95 #include <sys/sysctl.h>
96
97 #include <net/if.h>
98 #include <net/if_var.h>
99 #include <net/route.h>
100
101 #include <netinet/in.h>
102 #include <netinet6/in6_var.h>
103 #include <netinet/ip.h>
104 #include <netinet/ip6.h>
105 #include <netinet/icmp6.h>
106 #include <netinet6/ip6_var.h>
107 #include <netinet6/ip6_mroute.h>
108 #include <netinet/in_pcb.h>
109
110 /* #define MCAST_DEBUG */
111
112 #ifdef MCAST_DEBUG
113 int mcast6_debug = 1;
114 #define DPRINTF(fmt, args...) \
115 do { \
116 if (mcast6_debug) \
117 printf("%s:%d " fmt "\n", \
118 __func__, __LINE__, ## args); \
119 } while (0)
120 #else
121 #define DPRINTF(fmt, args...) \
122 do { } while (0)
123 #endif
124
125 int ip6_mdq(struct mbuf *, struct ifnet *, struct rtentry *);
126 void phyint_send6(struct ifnet *, struct ip6_hdr *, struct mbuf *);
127
128 /*
129 * Globals. All but ip6_mrouter, ip6_mrtproto and mrt6stat could be static,
130 * except for netstat or debugging purposes.
131 */
132 struct socket *ip6_mrouter[RT_TABLEID_MAX + 1];
133 struct rttimer_queue ip6_mrouterq;
134 int ip6_mrouter_ver = 0;
135 int ip6_mrtproto; /* for netstat only */
136 struct mrt6stat mrt6stat;
137
138 #define NO_RTE_FOUND 0x1
139 #define RTE_FOUND 0x2
140
141 /*
142 * Macros to compute elapsed time efficiently
143 * Borrowed from Van Jacobson's scheduling code
144 */
145 #define TV_DELTA(a, b, delta) do { \
146 int xxs; \
147 \
148 delta = (a).tv_usec - (b).tv_usec; \
149 if ((xxs = (a).tv_sec - (b).tv_sec)) { \
150 switch (xxs) { \
151 case 2: \
152 delta += 1000000; \
153 /* FALLTHROUGH */ \
154 case 1: \
155 delta += 1000000; \
156 break; \
157 default: \
158 delta += (1000000 * xxs); \
159 } \
160 } \
161 } while (0)
162
163 #define TV_LT(a, b) (((a).tv_usec < (b).tv_usec && \
164 (a).tv_sec <= (b).tv_sec) || (a).tv_sec < (b).tv_sec)
165
166 int get_sg6_cnt(struct sioc_sg_req6 *, unsigned int);
167 int get_mif6_cnt(struct sioc_mif_req6 *, unsigned int);
168 int ip6_mrouter_init(struct socket *, int, int);
169 int add_m6if(struct socket *, struct mif6ctl *);
170 int del_m6if(struct socket *, mifi_t *);
171 int add_m6fc(struct socket *, struct mf6cctl *);
172 int del_m6fc(struct socket *, struct mf6cctl *);
173 struct ifnet *mrt6_iflookupbymif(mifi_t, unsigned int);
174 struct rtentry *mf6c_find(struct ifnet *, struct in6_addr *,
175 struct in6_addr *, unsigned int);
176 struct rtentry *mrt6_mcast_add(struct ifnet *, struct sockaddr *,
177 struct sockaddr *);
178 void mrt6_mcast_del(struct rtentry *, unsigned int);
179
180 /*
181 * Handle MRT setsockopt commands to modify the multicast routing tables.
182 */
183 int
184 ip6_mrouter_set(int cmd, struct socket *so, struct mbuf *m)
185 {
186 struct inpcb *inp = sotoinpcb(so);
187
188 if (cmd != MRT6_INIT && so != ip6_mrouter[inp->inp_rtableid])
189 return (EPERM);
190
191 switch (cmd) {
192 case MRT6_INIT:
193 if (m == NULL || m->m_len < sizeof(int))
194 return (EINVAL);
195 return (ip6_mrouter_init(so, *mtod(m, int *), cmd));
196 case MRT6_DONE:
197 return (ip6_mrouter_done(so));
198 case MRT6_ADD_MIF:
199 if (m == NULL || m->m_len < sizeof(struct mif6ctl))
200 return (EINVAL);
201 return (add_m6if(so, mtod(m, struct mif6ctl *)));
202 case MRT6_DEL_MIF:
203 if (m == NULL || m->m_len < sizeof(mifi_t))
204 return (EINVAL);
205 return (del_m6if(so, mtod(m, mifi_t *)));
206 case MRT6_ADD_MFC:
207 if (m == NULL || m->m_len < sizeof(struct mf6cctl))
208 return (EINVAL);
209 return (add_m6fc(so, mtod(m, struct mf6cctl *)));
210 case MRT6_DEL_MFC:
211 if (m == NULL || m->m_len < sizeof(struct mf6cctl))
212 return (EINVAL);
213 return (del_m6fc(so, mtod(m, struct mf6cctl *)));
214 default:
215 return (EOPNOTSUPP);
216 }
217 }
218
219 /*
220 * Handle MRT getsockopt commands
221 */
222 int
223 ip6_mrouter_get(int cmd, struct socket *so, struct mbuf *m)
224 {
225 struct inpcb *inp = sotoinpcb(so);
226
227 if (so != ip6_mrouter[inp->inp_rtableid])
228 return (EPERM);
229
230 switch (cmd) {
231 default:
232 return EOPNOTSUPP;
233 }
234 }
235
236 /*
237 * Handle ioctl commands to obtain information from the cache
238 */
239 int
240 mrt6_ioctl(struct socket *so, u_long cmd, caddr_t data)
241 {
242 struct inpcb *inp = sotoinpcb(so);
243 int error;
244
245 if (inp == NULL)
246 return (ENOTCONN);
247
248 switch (cmd) {
249 case SIOCGETSGCNT_IN6:
250 NET_LOCK_SHARED();
251 error = get_sg6_cnt((struct sioc_sg_req6 *)data,
252 inp->inp_rtableid);
253 NET_UNLOCK_SHARED();
254 break;
255 case SIOCGETMIFCNT_IN6:
256 NET_LOCK_SHARED();
257 error = get_mif6_cnt((struct sioc_mif_req6 *)data,
258 inp->inp_rtableid);
259 NET_UNLOCK_SHARED();
260 break;
261 default:
262 error = ENOTTY;
263 break;
264 }
265 return error;
266 }
267
268 /*
269 * returns the packet, byte, rpf-failure count for the source group provided
270 */
271 int
272 get_sg6_cnt(struct sioc_sg_req6 *req, unsigned int rtableid)
273 {
274 struct rtentry *rt;
275 struct mf6c *mf6c;
276
277 rt = mf6c_find(NULL, &req->src.sin6_addr, &req->grp.sin6_addr,
278 rtableid);
279 if (rt == NULL) {
280 req->pktcnt = req->bytecnt = req->wrong_if = 0xffffffff;
281 return EADDRNOTAVAIL;
282 }
283
284 req->pktcnt = req->bytecnt = req->wrong_if = 0;
285 do {
286 mf6c = (struct mf6c *)rt->rt_llinfo;
287 if (mf6c == NULL)
288 continue;
289
290 req->pktcnt += mf6c->mf6c_pkt_cnt;
291 req->bytecnt += mf6c->mf6c_byte_cnt;
292 req->wrong_if += mf6c->mf6c_wrong_if;
293 } while ((rt = rtable_iterate(rt)) != NULL);
294
295 return 0;
296 }
297
298 /*
299 * returns the input and output packet and byte counts on the mif provided
300 */
301 int
302 get_mif6_cnt(struct sioc_mif_req6 *req, unsigned int rtableid)
303 {
304 struct ifnet *ifp;
305 struct mif6 *m6;
306
307 if ((ifp = mrt6_iflookupbymif(req->mifi, rtableid)) == NULL)
308 return EINVAL;
309
310 m6 = (struct mif6 *)ifp->if_mcast6;
311 req->icount = m6->m6_pkt_in;
312 req->ocount = m6->m6_pkt_out;
313 req->ibytes = m6->m6_bytes_in;
314 req->obytes = m6->m6_bytes_out;
315
316 return 0;
317 }
318
319 int
320 mrt6_sysctl_mif(void *oldp, size_t *oldlenp)
321 {
322 struct ifnet *ifp;
323 caddr_t where = oldp;
324 size_t needed, given;
325 struct mif6 *mifp;
326 struct mif6info minfo;
327
328 given = *oldlenp;
329 needed = 0;
330 memset(&minfo, 0, sizeof minfo);
331 TAILQ_FOREACH(ifp, &ifnetlist, if_list) {
332 if ((mifp = (struct mif6 *)ifp->if_mcast6) == NULL)
333 continue;
334
335 minfo.m6_mifi = mifp->m6_mifi;
336 minfo.m6_flags = mifp->m6_flags;
337 minfo.m6_lcl_addr = mifp->m6_lcl_addr;
338 minfo.m6_ifindex = ifp->if_index;
339 minfo.m6_pkt_in = mifp->m6_pkt_in;
340 minfo.m6_pkt_out = mifp->m6_pkt_out;
341 minfo.m6_bytes_in = mifp->m6_bytes_in;
342 minfo.m6_bytes_out = mifp->m6_bytes_out;
343 minfo.m6_rate_limit = mifp->m6_rate_limit;
344
345 needed += sizeof(minfo);
346 if (where && needed <= given) {
347 int error;
348
349 error = copyout(&minfo, where, sizeof(minfo));
350 if (error)
351 return (error);
352 where += sizeof(minfo);
353 }
354 }
355 if (where) {
356 *oldlenp = needed;
357 if (given < needed)
358 return (ENOMEM);
359 } else
360 *oldlenp = (11 * needed) / 10;
361
362 return (0);
363 }
364
365 struct mf6csysctlarg {
366 struct mf6cinfo *ms6a_minfos;
367 size_t ms6a_len;
368 size_t ms6a_needed;
369 };
370
371 int
372 mrt6_rtwalk_mf6csysctl(struct rtentry *rt, void *arg, unsigned int rtableid)
373 {
374 struct mf6c *mf6c = (struct mf6c *)rt->rt_llinfo;
375 struct mf6csysctlarg *msa = arg;
376 struct ifnet *ifp;
377 struct mif6 *m6;
378 struct mf6cinfo *minfo;
379 int new = 0;
380
381 /* Skip entries being removed. */
382 if (mf6c == NULL)
383 return 0;
384
385 /* Skip non-multicast routes. */
386 if (ISSET(rt->rt_flags, RTF_HOST | RTF_MULTICAST) !=
387 (RTF_HOST | RTF_MULTICAST))
388 return 0;
389
390 /* User just asked for the output size. */
391 if (msa->ms6a_minfos == NULL) {
392 msa->ms6a_needed += sizeof(*minfo);
393 return 0;
394 }
395
396 /* Skip route with invalid interfaces. */
397 if ((ifp = if_get(rt->rt_ifidx)) == NULL)
398 return 0;
399 if ((m6 = (struct mif6 *)ifp->if_mcast6) == NULL) {
400 if_put(ifp);
401 return 0;
402 }
403
404 for (minfo = msa->ms6a_minfos;
405 (uint8_t *)minfo < ((uint8_t *)msa->ms6a_minfos + msa->ms6a_len);
406 minfo++) {
407 /* Find a new entry or update old entry. */
408 if (!IN6_ARE_ADDR_EQUAL(&minfo->mf6c_origin.sin6_addr,
409 &satosin6(rt->rt_gateway)->sin6_addr) ||
410 !IN6_ARE_ADDR_EQUAL(&minfo->mf6c_mcastgrp.sin6_addr,
411 &satosin6(rt_key(rt))->sin6_addr)) {
412 if (!IN6_IS_ADDR_UNSPECIFIED(
413 &minfo->mf6c_origin.sin6_addr) ||
414 !IN6_IS_ADDR_UNSPECIFIED(
415 &minfo->mf6c_mcastgrp.sin6_addr))
416 continue;
417
418 new = 1;
419 }
420
421 minfo->mf6c_origin = *satosin6(rt->rt_gateway);
422 minfo->mf6c_mcastgrp = *satosin6(rt_key(rt));
423 minfo->mf6c_parent = mf6c->mf6c_parent;
424 minfo->mf6c_pkt_cnt += mf6c->mf6c_pkt_cnt;
425 minfo->mf6c_byte_cnt += mf6c->mf6c_byte_cnt;
426 IF_SET(m6->m6_mifi, &minfo->mf6c_ifset);
427 break;
428 }
429
430 if (new != 0)
431 msa->ms6a_needed += sizeof(*minfo);
432
433 if_put(ifp);
434
435 return 0;
436 }
437
438 int
439 mrt6_sysctl_mfc(void *oldp, size_t *oldlenp)
440 {
441 unsigned int rtableid;
442 int error;
443 struct mf6csysctlarg msa;
444
445 if (oldp != NULL && *oldlenp > MAXPHYS)
446 return EINVAL;
447
448 if (oldp != NULL)
449 msa.ms6a_minfos = malloc(*oldlenp, M_TEMP, M_WAITOK | M_ZERO);
450 else
451 msa.ms6a_minfos = NULL;
452
453 msa.ms6a_len = *oldlenp;
454 msa.ms6a_needed = 0;
455
456 for (rtableid = 0; rtableid <= RT_TABLEID_MAX; rtableid++) {
457 rtable_walk(rtableid, AF_INET6, NULL, mrt6_rtwalk_mf6csysctl,
458 &msa);
459 }
460
461 if (msa.ms6a_minfos != NULL && msa.ms6a_needed > 0 &&
462 (error = copyout(msa.ms6a_minfos, oldp, msa.ms6a_needed)) != 0) {
463 free(msa.ms6a_minfos, M_TEMP, *oldlenp);
464 return error;
465 }
466
467 free(msa.ms6a_minfos, M_TEMP, *oldlenp);
468 *oldlenp = msa.ms6a_needed;
469
470 return 0;
471 }
472
473 /*
474 * Enable multicast routing
475 */
476 int
477 ip6_mrouter_init(struct socket *so, int v, int cmd)
478 {
479 struct inpcb *inp = sotoinpcb(so);
480 unsigned int rtableid = inp->inp_rtableid;
481
482 if (so->so_type != SOCK_RAW ||
483 so->so_proto->pr_protocol != IPPROTO_ICMPV6)
484 return (EOPNOTSUPP);
485
486 if (v != 1)
487 return (ENOPROTOOPT);
488
489 if (ip6_mrouter[rtableid] != NULL)
490 return (EADDRINUSE);
491
492 ip6_mrouter[rtableid] = so;
493 ip6_mrouter_ver = cmd;
494
495 return (0);
496 }
497
498 int
499 mrouter6_rtwalk_delete(struct rtentry *rt, void *arg, unsigned int rtableid)
500 {
501 /* Skip non-multicast routes. */
502 if (ISSET(rt->rt_flags, RTF_HOST | RTF_MULTICAST) !=
503 (RTF_HOST | RTF_MULTICAST))
504 return 0;
505
506 return EEXIST;
507 }
508
509 /*
510 * Disable multicast routing
511 */
512 int
513 ip6_mrouter_done(struct socket *so)
514 {
515 struct inpcb *inp = sotoinpcb(so);
516 struct ifnet *ifp;
517 unsigned int rtableid = inp->inp_rtableid;
518 int error;
519
520 NET_ASSERT_LOCKED();
521
522 /* Delete all remaining installed multicast routes. */
523 do {
524 struct rtentry *rt = NULL;
525
526 error = rtable_walk(rtableid, AF_INET6, &rt,
527 mrouter6_rtwalk_delete, NULL);
528 if (rt != NULL && error == EEXIST) {
529 mrt6_mcast_del(rt, rtableid);
530 error = EAGAIN;
531 }
532 rtfree(rt);
533 } while (error == EAGAIN);
534
535 /* Unregister all interfaces in the domain. */
536 TAILQ_FOREACH(ifp, &ifnetlist, if_list) {
537 if (ifp->if_rdomain != rtableid)
538 continue;
539
540 ip6_mrouter_detach(ifp);
541 }
542
543 ip6_mrouter[inp->inp_rtableid] = NULL;
544 ip6_mrouter_ver = 0;
545
546 return 0;
547 }
548
549 void
550 ip6_mrouter_detach(struct ifnet *ifp)
551 {
552 struct mif6 *m6 = (struct mif6 *)ifp->if_mcast6;
553 struct in6_ifreq ifr;
554
555 if (m6 == NULL)
556 return;
557
558 ifp->if_mcast6 = NULL;
559
560 memset(&ifr, 0, sizeof(ifr));
561 ifr.ifr_addr.sin6_family = AF_INET6;
562 ifr.ifr_addr.sin6_addr = in6addr_any;
563 KERNEL_LOCK();
564 (*ifp->if_ioctl)(ifp, SIOCDELMULTI, (caddr_t)&ifr);
565 KERNEL_UNLOCK();
566
567 free(m6, M_MRTABLE, sizeof(*m6));
568 }
569
570 /*
571 * Add a mif to the mif table
572 */
573 int
574 add_m6if(struct socket *so, struct mif6ctl *mifcp)
575 {
576 struct inpcb *inp = sotoinpcb(so);
577 struct mif6 *mifp;
578 struct ifnet *ifp;
579 struct in6_ifreq ifr;
580 int error;
581 unsigned int rtableid = inp->inp_rtableid;
582
583 NET_ASSERT_LOCKED();
584
585 if (mifcp->mif6c_mifi >= MAXMIFS)
586 return EINVAL;
587
588 if (mrt6_iflookupbymif(mifcp->mif6c_mifi, rtableid) != NULL)
589 return EADDRINUSE; /* XXX: is it appropriate? */
590
591 {
592 ifp = if_get(mifcp->mif6c_pifi);
593 if (ifp == NULL)
594 return ENXIO;
595
596 /* Make sure the interface supports multicast */
597 if ((ifp->if_flags & IFF_MULTICAST) == 0) {
598 if_put(ifp);
599 return EOPNOTSUPP;
600 }
601
602 /*
603 * Enable promiscuous reception of all IPv6 multicasts
604 * from the interface.
605 */
606 memset(&ifr, 0, sizeof(ifr));
607 ifr.ifr_addr.sin6_family = AF_INET6;
608 ifr.ifr_addr.sin6_addr = in6addr_any;
609 error = (*ifp->if_ioctl)(ifp, SIOCADDMULTI, (caddr_t)&ifr);
610
611 if (error) {
612 if_put(ifp);
613 return error;
614 }
615 }
616
617 mifp = malloc(sizeof(*mifp), M_MRTABLE, M_WAITOK | M_ZERO);
618 ifp->if_mcast6 = (caddr_t)mifp;
619 mifp->m6_mifi = mifcp->mif6c_mifi;
620 mifp->m6_flags = mifcp->mif6c_flags;
621 #ifdef notyet
622 /* scaling up here allows division by 1024 in critical code */
623 mifp->m6_rate_limit = mifcp->mif6c_rate_limit * 1024 / 1000;
624 #endif
625
626 if_put(ifp);
627
628 return 0;
629 }
630
631 /*
632 * Delete a mif from the mif table
633 */
634 int
635 del_m6if(struct socket *so, mifi_t *mifip)
636 {
637 struct inpcb *inp = sotoinpcb(so);
638 struct ifnet *ifp;
639
640 NET_ASSERT_LOCKED();
641
642 if (*mifip >= MAXMIFS)
643 return EINVAL;
644 if ((ifp = mrt6_iflookupbymif(*mifip, inp->inp_rtableid)) == NULL)
645 return EINVAL;
646
647 ip6_mrouter_detach(ifp);
648
649 return 0;
650 }
651
652 int
653 mf6c_add_route(struct ifnet *ifp, struct sockaddr *origin,
654 struct sockaddr *group, struct mf6cctl *mf6cc, int wait)
655 {
656 struct rtentry *rt;
657 struct mf6c *mf6c;
658 unsigned int rtableid = ifp->if_rdomain;
659 #ifdef MCAST_DEBUG
660 char bsrc[INET6_ADDRSTRLEN], bdst[INET6_ADDRSTRLEN];
661 #endif /* MCAST_DEBUG */
662
663 rt = mrt6_mcast_add(ifp, origin, group);
664 if (rt == NULL)
665 return ENOENT;
666
667 mf6c = malloc(sizeof(*mf6c), M_MRTABLE, wait | M_ZERO);
668 if (mf6c == NULL) {
669 DPRINTF("origin %s group %s parent %d (%s) malloc failed",
670 inet_ntop(AF_INET6, origin, bsrc, sizeof(bsrc)),
671 inet_ntop(AF_INET6, group, bdst, sizeof(bdst)),
672 mf6cc->mf6cc_parent, ifp->if_xname);
673 mrt6_mcast_del(rt, rtableid);
674 rtfree(rt);
675 return ENOMEM;
676 }
677
678 rt->rt_llinfo = (caddr_t)mf6c;
679 rt_timer_add(rt, &ip6_mrouterq, rtableid);
680 mf6c->mf6c_parent = mf6cc->mf6cc_parent;
681 rtfree(rt);
682
683 return 0;
684 }
685
686 void
687 mf6c_update(struct mf6cctl *mf6cc, int wait, unsigned int rtableid)
688 {
689 struct rtentry *rt;
690 struct mf6c *mf6c;
691 struct ifnet *ifp;
692 struct sockaddr_in6 osin6, gsin6;
693 mifi_t mifi;
694 #ifdef MCAST_DEBUG
695 char bdst[INET6_ADDRSTRLEN];
696 #endif /* MCAST_DEBUG */
697
698 memset(&osin6, 0, sizeof(osin6));
699 osin6.sin6_family = AF_INET6;
700 osin6.sin6_len = sizeof(osin6);
701 osin6.sin6_addr = mf6cc->mf6cc_origin.sin6_addr;
702
703 memset(&gsin6, 0, sizeof(gsin6));
704 gsin6.sin6_family = AF_INET6;
705 gsin6.sin6_len = sizeof(gsin6);
706 gsin6.sin6_addr = mf6cc->mf6cc_mcastgrp.sin6_addr;
707
708 for (mifi = 0; mifi < MAXMIFS; mifi++) {
709 if (mifi == mf6cc->mf6cc_parent)
710 continue;
711
712 /* Test for mif existence and then update the entry. */
713 if ((ifp = mrt6_iflookupbymif(mifi, rtableid)) == NULL)
714 continue;
715
716 rt = mf6c_find(ifp, &mf6cc->mf6cc_origin.sin6_addr,
717 &mf6cc->mf6cc_mcastgrp.sin6_addr, rtableid);
718
719 /* mif not configured or removed. */
720 if (!IF_ISSET(mifi, &mf6cc->mf6cc_ifset)) {
721 /* Route doesn't exist, nothing to do. */
722 if (rt == NULL)
723 continue;
724
725 DPRINTF("del route (group %s) for mif %d (%s)",
726 inet_ntop(AF_INET6,
727 &mf6cc->mf6cc_mcastgrp.sin6_addr, bdst,
728 sizeof(bdst)), mifi, ifp->if_xname);
729 mrt6_mcast_del(rt, rtableid);
730 rtfree(rt);
731 continue;
732 }
733
734 /* Route exists, look for changes. */
735 if (rt != NULL) {
736 mf6c = (struct mf6c *)rt->rt_llinfo;
737 /* Skip route being deleted. */
738 if (mf6c == NULL) {
739 rtfree(rt);
740 continue;
741 }
742
743 /* No new changes to apply. */
744 if (mf6cc->mf6cc_parent == mf6c->mf6c_parent) {
745 rtfree(rt);
746 continue;
747 }
748
749 DPRINTF("update route (group %s) for mif %d (%s)",
750 inet_ntop(AF_INET6,
751 &mf6cc->mf6cc_mcastgrp.sin6_addr, bdst,
752 sizeof(bdst)), mifi, ifp->if_xname);
753
754 mf6c->mf6c_parent = mf6cc->mf6cc_parent;
755 rtfree(rt);
756 continue;
757 }
758
759 DPRINTF("add route (group %s) for mif %d (%s)",
760 inet_ntop(AF_INET6, &mf6cc->mf6cc_mcastgrp.sin6_addr,
761 bdst, sizeof(bdst)), mifi, ifp->if_xname);
762
763 mf6c_add_route(ifp, sin6tosa(&osin6), sin6tosa(&gsin6),
764 mf6cc, wait);
765 }
766
767 /* Create route for the parent interface. */
768 if ((ifp = mrt6_iflookupbymif(mf6cc->mf6cc_parent,
769 rtableid)) == NULL) {
770 DPRINTF("failed to find upstream interface %d",
771 mf6cc->mf6cc_parent);
772 return;
773 }
774
775 /* We already have a route, nothing to do here. */
776 if ((rt = mf6c_find(ifp, &mf6cc->mf6cc_origin.sin6_addr,
777 &mf6cc->mf6cc_mcastgrp.sin6_addr, rtableid)) != NULL) {
778 rtfree(rt);
779 return;
780 }
781
782 DPRINTF("add upstream route (group %s) for if %s",
783 inet_ntop(AF_INET6, &mf6cc->mf6cc_mcastgrp.sin6_addr,
784 bdst, sizeof(bdst)), ifp->if_xname);
785 mf6c_add_route(ifp, sin6tosa(&osin6), sin6tosa(&gsin6), mf6cc, wait);
786 }
787
788 int
789 mf6c_add(struct mf6cctl *mfccp, struct in6_addr *origin,
790 struct in6_addr *group, int vidx, unsigned int rtableid, int wait)
791 {
792 struct ifnet *ifp;
793 struct mif6 *m6;
794 struct mf6cctl mf6cc;
795
796 ifp = mrt6_iflookupbymif(vidx, rtableid);
797 if (ifp == NULL ||
798 (m6 = (struct mif6 *)ifp->if_mcast6) == NULL)
799 return ENOENT;
800
801 memset(&mf6cc, 0, sizeof(mf6cc));
802 if (mfccp == NULL) {
803 mf6cc.mf6cc_origin.sin6_family = AF_INET6;
804 mf6cc.mf6cc_origin.sin6_len = sizeof(mf6cc.mf6cc_origin);
805 mf6cc.mf6cc_origin.sin6_addr = *origin;
806 mf6cc.mf6cc_mcastgrp.sin6_family = AF_INET6;
807 mf6cc.mf6cc_mcastgrp.sin6_len = sizeof(mf6cc.mf6cc_mcastgrp);
808 mf6cc.mf6cc_mcastgrp.sin6_addr = *group;
809 mf6cc.mf6cc_parent = vidx;
810 } else
811 memcpy(&mf6cc, mfccp, sizeof(mf6cc));
812
813 mf6c_update(&mf6cc, wait, rtableid);
814
815 return 0;
816 }
817
818 int
819 add_m6fc(struct socket *so, struct mf6cctl *mfccp)
820 {
821 struct inpcb *inp = sotoinpcb(so);
822 unsigned int rtableid = inp->inp_rtableid;
823
824 NET_ASSERT_LOCKED();
825
826 return mf6c_add(mfccp, &mfccp->mf6cc_origin.sin6_addr,
827 &mfccp->mf6cc_mcastgrp.sin6_addr, mfccp->mf6cc_parent,
828 rtableid, M_WAITOK);
829 }
830
831 int
832 del_m6fc(struct socket *so, struct mf6cctl *mfccp)
833 {
834 struct inpcb *inp = sotoinpcb(so);
835 struct rtentry *rt;
836 unsigned int rtableid = inp->inp_rtableid;
837
838 NET_ASSERT_LOCKED();
839
840 while ((rt = mf6c_find(NULL, &mfccp->mf6cc_origin.sin6_addr,
841 &mfccp->mf6cc_mcastgrp.sin6_addr, rtableid)) != NULL) {
842 mrt6_mcast_del(rt, rtableid);
843 rtfree(rt);
844 }
845
846 return 0;
847 }
848
849 int
850 socket6_send(struct socket *s, struct mbuf *mm, struct sockaddr_in6 *src)
851 {
852 if (s) {
853 if (sbappendaddr(s, &s->so_rcv, sin6tosa(src), mm, NULL) != 0) {
854 sorwakeup(s);
855 return 0;
856 }
857 }
858 m_freem(mm);
859 return -1;
860 }
861
862 /*
863 * IPv6 multicast forwarding function. This function assumes that the packet
864 * pointed to by "ip6" has arrived on (or is about to be sent to) the interface
865 * pointed to by "ifp", and the packet is to be relayed to other networks
866 * that have members of the packet's destination IPv6 multicast group.
867 *
868 * The packet is returned unscathed to the caller, unless it is
869 * erroneous, in which case a non-zero return value tells the caller to
870 * discard it.
871 */
872 int
873 ip6_mforward(struct ip6_hdr *ip6, struct ifnet *ifp, struct mbuf *m)
874 {
875 struct rtentry *rt;
876 struct mif6 *mifp;
877 struct mbuf *mm;
878 struct sockaddr_in6 sin6;
879 unsigned int rtableid = ifp->if_rdomain;
880
881 NET_ASSERT_LOCKED();
882
883 /*
884 * Don't forward a packet with Hop limit of zero or one,
885 * or a packet destined to a local-only group.
886 */
887 if (ip6->ip6_hlim <= 1 || IN6_IS_ADDR_MC_INTFACELOCAL(&ip6->ip6_dst) ||
888 IN6_IS_ADDR_MC_LINKLOCAL(&ip6->ip6_dst))
889 return 0;
890 ip6->ip6_hlim--;
891
892 /*
893 * Source address check: do not forward packets with unspecified
894 * source. It was discussed in July 2000, on ipngwg mailing list.
895 * This is rather more serious than unicast cases, because some
896 * MLD packets can be sent with the unspecified source address
897 * (although such packets must normally set 1 to the hop limit field).
898 */
899 if (IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src)) {
900 ip6stat_inc(ip6s_cantforward);
901 if (ip6_log_time + ip6_log_interval < getuptime()) {
902 char src[INET6_ADDRSTRLEN], dst[INET6_ADDRSTRLEN];
903
904 ip6_log_time = getuptime();
905
906 inet_ntop(AF_INET6, &ip6->ip6_src, src, sizeof(src));
907 inet_ntop(AF_INET6, &ip6->ip6_dst, dst, sizeof(dst));
908 log(LOG_DEBUG, "cannot forward "
909 "from %s to %s nxt %d received on interface %u\n",
910 src, dst, ip6->ip6_nxt, m->m_pkthdr.ph_ifidx);
911 }
912 return 0;
913 }
914
915 /*
916 * Determine forwarding mifs from the forwarding cache table
917 */
918 rt = mf6c_find(NULL, &ip6->ip6_src, &ip6->ip6_dst, rtableid);
919
920 /* Entry exists, so forward if necessary */
921 if (rt) {
922 return (ip6_mdq(m, ifp, rt));
923 } else {
924 /*
925 * If we don't have a route for packet's origin,
926 * Make a copy of the packet &
927 * send message to routing daemon
928 */
929
930 mrt6stat.mrt6s_no_route++;
931
932 {
933 struct mrt6msg *im;
934
935 if ((mifp = (struct mif6 *)ifp->if_mcast6) == NULL)
936 return EHOSTUNREACH;
937
938 /*
939 * Make a copy of the header to send to the user
940 * level process
941 */
942 mm = m_copym(m, 0, sizeof(struct ip6_hdr), M_NOWAIT);
943 if (mm == NULL)
944 return ENOBUFS;
945
946 /*
947 * Send message to routing daemon
948 */
949 (void)memset(&sin6, 0, sizeof(sin6));
950 sin6.sin6_len = sizeof(sin6);
951 sin6.sin6_family = AF_INET6;
952 sin6.sin6_addr = ip6->ip6_src;
953
954 im = NULL;
955 switch (ip6_mrouter_ver) {
956 case MRT6_INIT:
957 im = mtod(mm, struct mrt6msg *);
958 im->im6_msgtype = MRT6MSG_NOCACHE;
959 im->im6_mbz = 0;
960 im->im6_mif = mifp->m6_mifi;
961 break;
962 default:
963 m_freem(mm);
964 return EINVAL;
965 }
966
967 if (socket6_send(ip6_mrouter[rtableid], mm,
968 &sin6) < 0) {
969 log(LOG_WARNING, "ip6_mforward: ip6_mrouter "
970 "socket queue full\n");
971 mrt6stat.mrt6s_upq_sockfull++;
972 return ENOBUFS;
973 }
974
975 mrt6stat.mrt6s_upcalls++;
976
977 mf6c_add(NULL, &ip6->ip6_src, &ip6->ip6_dst,
978 mifp->m6_mifi, rtableid, M_NOWAIT);
979 }
980
981 return 0;
982 }
983 }
984
985 void
986 mf6c_expire_route(struct rtentry *rt, u_int rtableid)
987 {
988 struct mf6c *mf6c = (struct mf6c *)rt->rt_llinfo;
989 #ifdef MCAST_DEBUG
990 char bsrc[INET6_ADDRSTRLEN], bdst[INET6_ADDRSTRLEN];
991 #endif /* MCAST_DEBUG */
992
993 /* Skip entry being deleted. */
994 if (mf6c == NULL)
995 return;
996
997 DPRINTF("origin %s group %s interface %d expire %s",
998 inet_ntop(AF_INET6, &satosin6(rt->rt_gateway)->sin6_addr,
999 bsrc, sizeof(bsrc)),
1000 inet_ntop(AF_INET6, &satosin6(rt_key(rt))->sin6_addr,
1001 bdst, sizeof(bdst)), rt->rt_ifidx,
1002 mf6c->mf6c_expire ? "yes" : "no");
1003
1004 if (mf6c->mf6c_expire == 0) {
1005 mf6c->mf6c_expire = 1;
1006 rt_timer_add(rt, &ip6_mrouterq, rtableid);
1007 return;
1008 }
1009
1010 mrt6_mcast_del(rt, rtableid);
1011 }
1012
1013 /*
1014 * Packet forwarding routine once entry in the cache is made
1015 */
1016 int
1017 ip6_mdq(struct mbuf *m, struct ifnet *ifp, struct rtentry *rt)
1018 {
1019 struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *);
1020 struct mif6 *m6, *mifp = (struct mif6 *)ifp->if_mcast6;
1021 struct mf6c *mf6c = (struct mf6c *)rt->rt_llinfo;
1022 struct ifnet *ifn;
1023 int plen = m->m_pkthdr.len;
1024
1025 if (mifp == NULL || mf6c == NULL) {
1026 rtfree(rt);
1027 return EHOSTUNREACH;
1028 }
1029
1030 /*
1031 * Don't forward if it didn't arrive from the parent mif
1032 * for its origin.
1033 */
1034 if (mifp->m6_mifi != mf6c->mf6c_parent) {
1035 /* came in the wrong interface */
1036 mrt6stat.mrt6s_wrong_if++;
1037 mf6c->mf6c_wrong_if++;
1038 rtfree(rt);
1039 return 0;
1040 } /* if wrong iif */
1041
1042 /* If I sourced this packet, it counts as output, else it was input. */
1043 if (m->m_pkthdr.ph_ifidx == 0) {
1044 /* XXX: is ph_ifidx really 0 when output?? */
1045 mifp->m6_pkt_out++;
1046 mifp->m6_bytes_out += plen;
1047 } else {
1048 mifp->m6_pkt_in++;
1049 mifp->m6_bytes_in += plen;
1050 }
1051
1052 /*
1053 * For each mif, forward a copy of the packet if there are group
1054 * members downstream on the interface.
1055 */
1056 do {
1057 /* Don't consider non multicast routes. */
1058 if (ISSET(rt->rt_flags, RTF_HOST | RTF_MULTICAST) !=
1059 (RTF_HOST | RTF_MULTICAST))
1060 continue;
1061
1062 mf6c = (struct mf6c *)rt->rt_llinfo;
1063 if (mf6c == NULL)
1064 continue;
1065
1066 mf6c->mf6c_pkt_cnt++;
1067 mf6c->mf6c_byte_cnt += m->m_pkthdr.len;
1068
1069 /* Don't let this route expire. */
1070 mf6c->mf6c_expire = 0;
1071
1072 if ((ifn = if_get(rt->rt_ifidx)) == NULL)
1073 continue;
1074
1075 /* Sanity check: did we configure this? */
1076 if ((m6 = (struct mif6 *)ifn->if_mcast6) == NULL) {
1077 if_put(ifn);
1078 continue;
1079 }
1080
1081 /* Don't send in the upstream interface. */
1082 if (mf6c->mf6c_parent == m6->m6_mifi) {
1083 if_put(ifn);
1084 continue;
1085 }
1086
1087 /*
1088 * check if the outgoing packet is going to break
1089 * a scope boundary.
1090 */
1091 if ((mifp->m6_flags & MIFF_REGISTER) == 0 &&
1092 (m6->m6_flags & MIFF_REGISTER) == 0 &&
1093 (in6_addr2scopeid(ifp->if_index, &ip6->ip6_dst) !=
1094 in6_addr2scopeid(ifn->if_index, &ip6->ip6_dst) ||
1095 in6_addr2scopeid(ifp->if_index, &ip6->ip6_src) !=
1096 in6_addr2scopeid(ifn->if_index, &ip6->ip6_src))) {
1097 if_put(ifn);
1098 ip6stat_inc(ip6s_badscope);
1099 continue;
1100 }
1101
1102 m6->m6_pkt_out++;
1103 m6->m6_bytes_out += plen;
1104
1105 phyint_send6(ifn, ip6, m);
1106 if_put(ifn);
1107 } while ((rt = rtable_iterate(rt)) != NULL);
1108
1109 return 0;
1110 }
1111
1112 void
1113 phyint_send6(struct ifnet *ifp, struct ip6_hdr *ip6, struct mbuf *m)
1114 {
1115 struct mbuf *mb_copy;
1116 struct sockaddr_in6 *dst6, sin6;
1117 int error = 0;
1118
1119 NET_ASSERT_LOCKED();
1120
1121 /*
1122 * Make a new reference to the packet; make sure that
1123 * the IPv6 header is actually copied, not just referenced,
1124 * so that ip6_output() only scribbles on the copy.
1125 */
1126 mb_copy = m_dup_pkt(m, max_linkhdr, M_NOWAIT);
1127 if (mb_copy == NULL)
1128 return;
1129 /* set MCAST flag to the outgoing packet */
1130 mb_copy->m_flags |= M_MCAST;
1131
1132 /*
1133 * If we sourced the packet, call ip6_output since we may divide
1134 * the packet into fragments when the packet is too big for the
1135 * outgoing interface.
1136 * Otherwise, we can simply send the packet to the interface
1137 * sending queue.
1138 */
1139 if (m->m_pkthdr.ph_ifidx == 0) {
1140 struct ip6_moptions im6o;
1141
1142 im6o.im6o_ifidx = ifp->if_index;
1143 /* XXX: ip6_output will override ip6->ip6_hlim */
1144 im6o.im6o_hlim = ip6->ip6_hlim;
1145 im6o.im6o_loop = 1;
1146 error = ip6_output(mb_copy, NULL, NULL, IPV6_FORWARDING, &im6o,
1147 NULL);
1148 return;
1149 }
1150
1151 /*
1152 * If we belong to the destination multicast group
1153 * on the outgoing interface, loop back a copy.
1154 */
1155 dst6 = &sin6;
1156 memset(&sin6, 0, sizeof(sin6));
1157 if (in6_hasmulti(&ip6->ip6_dst, ifp)) {
1158 dst6->sin6_len = sizeof(struct sockaddr_in6);
1159 dst6->sin6_family = AF_INET6;
1160 dst6->sin6_addr = ip6->ip6_dst;
1161 ip6_mloopback(ifp, m, dst6);
1162 }
1163 /*
1164 * Put the packet into the sending queue of the outgoing interface
1165 * if it would fit in the MTU of the interface.
1166 */
1167 if (mb_copy->m_pkthdr.len <= ifp->if_mtu || ifp->if_mtu < IPV6_MMTU) {
1168 dst6->sin6_len = sizeof(struct sockaddr_in6);
1169 dst6->sin6_family = AF_INET6;
1170 dst6->sin6_addr = ip6->ip6_dst;
1171 error = ifp->if_output(ifp, mb_copy, sin6tosa(dst6), NULL);
1172 } else {
1173 if (ip6_mcast_pmtu)
1174 icmp6_error(mb_copy, ICMP6_PACKET_TOO_BIG, 0,
1175 ifp->if_mtu);
1176 else {
1177 m_freem(mb_copy); /* simply discard the packet */
1178 }
1179 }
1180 }
1181
1182 struct ifnet *
1183 mrt6_iflookupbymif(mifi_t mifi, unsigned int rtableid)
1184 {
1185 struct mif6 *m6;
1186 struct ifnet *ifp;
1187
1188 TAILQ_FOREACH(ifp, &ifnetlist, if_list) {
1189 if (ifp->if_rdomain != rtableid)
1190 continue;
1191 if ((m6 = (struct mif6 *)ifp->if_mcast6) == NULL)
1192 continue;
1193 if (m6->m6_mifi != mifi)
1194 continue;
1195
1196 return ifp;
1197 }
1198
1199 return NULL;
1200 }
1201
1202 struct rtentry *
1203 mf6c_find(struct ifnet *ifp, struct in6_addr *origin, struct in6_addr *group,
1204 unsigned int rtableid)
1205 {
1206 struct rtentry *rt;
1207 struct sockaddr_in6 msin6;
1208
1209 memset(&msin6, 0, sizeof(msin6));
1210 msin6.sin6_family = AF_INET6;
1211 msin6.sin6_len = sizeof(msin6);
1212 msin6.sin6_addr = *group;
1213
1214 rt = rtalloc(sin6tosa(&msin6), 0, rtableid);
1215 do {
1216 if (!rtisvalid(rt)) {
1217 rtfree(rt);
1218 return NULL;
1219 }
1220 if (ISSET(rt->rt_flags, RTF_HOST | RTF_MULTICAST) !=
1221 (RTF_HOST | RTF_MULTICAST))
1222 continue;
1223 /* Return first occurrence if interface is not specified. */
1224 if (ifp == NULL)
1225 return rt;
1226 if (rt->rt_ifidx == ifp->if_index)
1227 return rt;
1228 } while ((rt = rtable_iterate(rt)) != NULL);
1229
1230 return NULL;
1231 }
1232
1233 struct rtentry *
1234 mrt6_mcast_add(struct ifnet *ifp, struct sockaddr *origin,
1235 struct sockaddr *group)
1236 {
1237 struct ifaddr *ifa;
1238 int rv;
1239 unsigned int rtableid = ifp->if_rdomain;
1240
1241 TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) {
1242 if (ifa->ifa_addr->sa_family == AF_INET6)
1243 break;
1244 }
1245 if (ifa == NULL) {
1246 DPRINTF("ifa == NULL");
1247 return NULL;
1248 }
1249
1250 rv = rt_ifa_add(ifa, RTF_HOST | RTF_MULTICAST | RTF_MPATH, group,
1251 ifp->if_rdomain);
1252 if (rv != 0) {
1253 DPRINTF("rt_ifa_add failed %d", rv);
1254 return NULL;
1255 }
1256
1257 return mf6c_find(ifp, NULL, &satosin6(group)->sin6_addr, rtableid);
1258 }
1259
1260 void
1261 mrt6_mcast_del(struct rtentry *rt, unsigned int rtableid)
1262 {
1263 struct ifnet *ifp;
1264 int error;
1265
1266 /* Remove all timers related to this route. */
1267 rt_timer_remove_all(rt);
1268
1269 free(rt->rt_llinfo, M_MRTABLE, sizeof(struct mf6c));
1270 rt->rt_llinfo = NULL;
1271
1272 ifp = if_get(rt->rt_ifidx);
1273 if (ifp == NULL)
1274 return;
1275 error = rtdeletemsg(rt, ifp, rtableid);
1276 if_put(ifp);
1277
1278 if (error)
1279 DPRINTF("delete route error %d\n", error);
1280 }
Cache object: 7e4ccf8f07c79126d4a328385056de40
|