xref: /openbsd-src/sys/net/if_etherbridge.c (revision de8cc8edbc71bd3e3bc7fbffa27ba0e564c37d8b)
1 /*	$OpenBSD: if_etherbridge.c,v 1.5 2021/02/26 08:31:23 dlg Exp $ */
2 
3 /*
4  * Copyright (c) 2018, 2021 David Gwynne <dlg@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include "bpfilter.h"
20 
21 #include <sys/param.h>
22 #include <sys/systm.h>
23 #include <sys/kernel.h>
24 #include <sys/mbuf.h>
25 #include <sys/socket.h>
26 #include <sys/ioctl.h>
27 #include <sys/timeout.h>
28 #include <sys/pool.h>
29 #include <sys/tree.h>
30 
31 #include <net/if.h>
32 #include <net/if_var.h>
33 #include <net/if_dl.h>
34 #include <net/if_media.h>
35 #include <net/if_types.h>
36 #include <net/rtable.h>
37 #include <net/toeplitz.h>
38 
39 #include <netinet/in.h>
40 #include <netinet/if_ether.h>
41 
42 /* for bridge stuff */
43 #include <net/if_bridge.h>
44 
45 #include <net/if_etherbridge.h>
46 
47 static inline void	ebe_take(struct eb_entry *);
48 static inline void	ebe_rele(struct eb_entry *);
49 static void		ebe_free(void *);
50 
51 static void		etherbridge_age(void *);
52 
53 RBT_PROTOTYPE(eb_tree, eb_entry, ebe_tentry, ebt_cmp);
54 
55 static struct pool	eb_entry_pool;
56 
57 static inline int
58 eb_port_eq(struct etherbridge *eb, void *a, void *b)
59 {
60 	return ((*eb->eb_ops->eb_op_port_eq)(eb->eb_cookie, a, b));
61 }
62 
63 static inline void *
64 eb_port_take(struct etherbridge *eb, void *port)
65 {
66 	return ((*eb->eb_ops->eb_op_port_take)(eb->eb_cookie, port));
67 }
68 
69 static inline void
70 eb_port_rele(struct etherbridge *eb, void *port)
71 {
72 	return ((*eb->eb_ops->eb_op_port_rele)(eb->eb_cookie, port));
73 }
74 
75 static inline size_t
76 eb_port_ifname(struct etherbridge *eb, char *dst, size_t len, void *port)
77 {
78 	return ((*eb->eb_ops->eb_op_port_ifname)(eb->eb_cookie, dst, len,
79 	    port));
80 }
81 
82 static inline void
83 eb_port_sa(struct etherbridge *eb, struct sockaddr_storage *ss, void *port)
84 {
85 	(*eb->eb_ops->eb_op_port_sa)(eb->eb_cookie, ss, port);
86 }
87 
88 int
89 etherbridge_init(struct etherbridge *eb, const char *name,
90     const struct etherbridge_ops *ops, void *cookie)
91 {
92 	size_t i;
93 
94 	if (eb_entry_pool.pr_size == 0) {
95 		pool_init(&eb_entry_pool, sizeof(struct eb_entry),
96 		    0, IPL_SOFTNET, 0, "ebepl", NULL);
97 	}
98 
99 	eb->eb_table = mallocarray(ETHERBRIDGE_TABLE_SIZE,
100 	    sizeof(*eb->eb_table), M_DEVBUF, M_WAITOK|M_CANFAIL);
101 	if (eb->eb_table == NULL)
102 		return (ENOMEM);
103 
104 	eb->eb_name = name;
105 	eb->eb_ops = ops;
106 	eb->eb_cookie = cookie;
107 
108 	mtx_init(&eb->eb_lock, IPL_SOFTNET);
109 	RBT_INIT(eb_tree, &eb->eb_tree);
110 
111 	eb->eb_num = 0;
112 	eb->eb_max = 100;
113 	eb->eb_max_age = 240;
114 	timeout_set(&eb->eb_tmo_age, etherbridge_age, eb);
115 
116 	for (i = 0; i < ETHERBRIDGE_TABLE_SIZE; i++) {
117 		struct eb_list *ebl = &eb->eb_table[i];
118 		SMR_TAILQ_INIT(ebl);
119 	}
120 
121 	return (0);
122 }
123 
124 int
125 etherbridge_up(struct etherbridge *eb)
126 {
127 	etherbridge_age(eb);
128 
129 	return (0);
130 }
131 
132 int
133 etherbridge_down(struct etherbridge *eb)
134 {
135 	smr_barrier();
136 
137 	return (0);
138 }
139 
140 void
141 etherbridge_destroy(struct etherbridge *eb)
142 {
143 	struct eb_entry *ebe, *nebe;
144 
145 	/* XXX assume that nothing will calling etherbridge_map now */
146 
147 	timeout_del_barrier(&eb->eb_tmo_age);
148 
149 	free(eb->eb_table, M_DEVBUF,
150 	    ETHERBRIDGE_TABLE_SIZE * sizeof(*eb->eb_table));
151 
152 	RBT_FOREACH_SAFE(ebe, eb_tree, &eb->eb_tree, nebe) {
153 		RBT_REMOVE(eb_tree, &eb->eb_tree, ebe);
154 		ebe_free(ebe);
155 	}
156 }
157 
158 static struct eb_list *
159 etherbridge_list(struct etherbridge *eb, uint64_t eba)
160 {
161 	uint16_t hash;
162 
163 	hash = stoeplitz_h64(eba) & ETHERBRIDGE_TABLE_MASK;
164 
165 	return (&eb->eb_table[hash]);
166 }
167 
168 static struct eb_entry *
169 ebl_find(struct eb_list *ebl, uint64_t eba)
170 {
171 	struct eb_entry *ebe;
172 
173 	SMR_TAILQ_FOREACH(ebe, ebl, ebe_lentry) {
174 		if (ebe->ebe_addr == eba)
175 			return (ebe);
176 	}
177 
178 	return (NULL);
179 }
180 
181 static inline void
182 ebl_insert(struct eb_list *ebl, struct eb_entry *ebe)
183 {
184 	SMR_TAILQ_INSERT_TAIL_LOCKED(ebl, ebe, ebe_lentry);
185 }
186 
187 static inline void
188 ebl_remove(struct eb_list *ebl, struct eb_entry *ebe)
189 {
190 	SMR_TAILQ_REMOVE_LOCKED(ebl, ebe, ebe_lentry);
191 }
192 
193 static inline int
194 ebt_cmp(const struct eb_entry *aebe, const struct eb_entry *bebe)
195 {
196 	if (aebe->ebe_addr > bebe->ebe_addr)
197 		return (1);
198 	if (aebe->ebe_addr < bebe->ebe_addr)
199 		return (-1);
200 	return (0);
201 }
202 
203 RBT_GENERATE(eb_tree, eb_entry, ebe_tentry, ebt_cmp);
204 
205 static inline struct eb_entry *
206 ebt_insert(struct etherbridge *eb, struct eb_entry *ebe)
207 {
208 	return (RBT_INSERT(eb_tree, &eb->eb_tree, ebe));
209 }
210 
211 static inline struct eb_entry *
212 ebt_find(struct etherbridge *eb, const struct eb_entry *ebe)
213 {
214 	return (RBT_FIND(eb_tree, &eb->eb_tree, ebe));
215 }
216 
217 static inline void
218 ebt_replace(struct etherbridge *eb, struct eb_entry *oebe,
219     struct eb_entry *nebe)
220 {
221 	struct eb_entry *rvebe;
222 
223 	RBT_REMOVE(eb_tree, &eb->eb_tree, oebe);
224 	rvebe = RBT_INSERT(eb_tree, &eb->eb_tree, nebe);
225 	KASSERTMSG(rvebe == NULL, "ebt_replace eb %p nebe %p rvebe %p",
226 	    eb, nebe, rvebe);
227 }
228 
229 static inline void
230 ebt_remove(struct etherbridge *eb, struct eb_entry *ebe)
231 {
232 	RBT_REMOVE(eb_tree, &eb->eb_tree, ebe);
233 }
234 
235 static inline void
236 ebe_take(struct eb_entry *ebe)
237 {
238 	refcnt_take(&ebe->ebe_refs);
239 }
240 
241 static void
242 ebe_rele(struct eb_entry *ebe)
243 {
244 	if (refcnt_rele(&ebe->ebe_refs))
245 		smr_call(&ebe->ebe_smr_entry, ebe_free, ebe);
246 }
247 
248 static void
249 ebe_free(void *arg)
250 {
251 	struct eb_entry *ebe = arg;
252 	struct etherbridge *eb = ebe->ebe_etherbridge;
253 
254 	eb_port_rele(eb, ebe->ebe_port);
255 	pool_put(&eb_entry_pool, ebe);
256 }
257 
258 void *
259 etherbridge_resolve_ea(struct etherbridge *eb,
260     const struct ether_addr *ea)
261 {
262 	return (etherbridge_resolve(eb, ether_addr_to_e64(ea)));
263 }
264 
265 void *
266 etherbridge_resolve(struct etherbridge *eb, uint64_t eba)
267 {
268 	struct eb_list *ebl = etherbridge_list(eb, eba);
269 	struct eb_entry *ebe;
270 
271 	SMR_ASSERT_CRITICAL();
272 
273 	ebe = ebl_find(ebl, eba);
274 	if (ebe != NULL) {
275 		if (ebe->ebe_type == EBE_DYNAMIC) {
276 			int diff = getuptime() - ebe->ebe_age;
277 			if (diff > eb->eb_max_age)
278 				return (NULL);
279 		}
280 
281 		return (ebe->ebe_port);
282 	}
283 
284 	return (NULL);
285 }
286 
287 void
288 etherbridge_map_ea(struct etherbridge *eb, void *port,
289     const struct ether_addr *ea)
290 {
291 	etherbridge_map(eb, port, ether_addr_to_e64(ea));
292 }
293 
294 void
295 etherbridge_map(struct etherbridge *eb, void *port, uint64_t eba)
296 {
297 	struct eb_list *ebl;
298 	struct eb_entry *oebe, *nebe;
299 	unsigned int num;
300 	void *nport;
301 	int new = 0;
302 	time_t now;
303 
304 	if (ETH64_IS_MULTICAST(eba) || ETH64_IS_ANYADDR(eba))
305 		return;
306 
307 	now = getuptime();
308 	ebl = etherbridge_list(eb, eba);
309 
310 	smr_read_enter();
311 	oebe = ebl_find(ebl, eba);
312 	if (oebe == NULL)
313 		new = 1;
314 	else {
315 		if (oebe->ebe_age != now)
316 			oebe->ebe_age = now;
317 
318 		/* does this entry need to be replaced? */
319 		if (oebe->ebe_type == EBE_DYNAMIC &&
320 		    !eb_port_eq(eb, oebe->ebe_port, port)) {
321 			new = 1;
322 			ebe_take(oebe);
323 		} else
324 			oebe = NULL;
325 	}
326 	smr_read_leave();
327 
328 	if (!new)
329 		return;
330 
331 	nport = eb_port_take(eb, port);
332 	if (nport == NULL) {
333 		/* XXX should we remove the old one and flood? */
334 		return;
335 	}
336 
337 	nebe = pool_get(&eb_entry_pool, PR_NOWAIT);
338 	if (nebe == NULL) {
339 		/* XXX should we remove the old one and flood? */
340 		eb_port_rele(eb, nport);
341 		return;
342 	}
343 
344 	smr_init(&nebe->ebe_smr_entry);
345 	refcnt_init(&nebe->ebe_refs);
346 	nebe->ebe_etherbridge = eb;
347 
348 	nebe->ebe_addr = eba;
349 	nebe->ebe_port = nport;
350 	nebe->ebe_type = EBE_DYNAMIC;
351 	nebe->ebe_age = now;
352 
353 	mtx_enter(&eb->eb_lock);
354 	num = eb->eb_num + (oebe == NULL);
355 	if (num <= eb->eb_max && ebt_insert(eb, nebe) == oebe) {
356 		/* we won, do the update */
357 		ebl_insert(ebl, nebe);
358 
359 		if (oebe != NULL) {
360 			ebl_remove(ebl, oebe);
361 			ebt_replace(eb, oebe, nebe);
362 
363 			/* take the table reference away */
364 			if (refcnt_rele(&oebe->ebe_refs)) {
365 				panic("%s: eb %p oebe %p refcnt",
366 				    __func__, eb, oebe);
367 			}
368 		}
369 
370 		nebe = NULL;
371 		eb->eb_num = num;
372 	}
373 	mtx_leave(&eb->eb_lock);
374 
375 	if (nebe != NULL) {
376 		/*
377 		 * the new entry didnt make it into the
378 		 * table, so it can be freed directly.
379 		 */
380 		ebe_free(nebe);
381 	}
382 
383 	if (oebe != NULL) {
384 		/*
385 		 * the old entry could be referenced in
386 		 * multiple places, including an smr read
387 		 * section, so release it properly.
388 		 */
389 		ebe_rele(oebe);
390 	}
391 }
392 
393 int
394 etherbridge_add_addr(struct etherbridge *eb, void *port,
395     const struct ether_addr *ea, unsigned int type)
396 {
397 	uint64_t eba = ether_addr_to_e64(ea);
398 	struct eb_list *ebl;
399 	struct eb_entry *nebe;
400 	unsigned int num;
401 	void *nport;
402 	int error = 0;
403 
404 	if (ETH64_IS_MULTICAST(eba) || ETH64_IS_ANYADDR(eba))
405 		return (EADDRNOTAVAIL);
406 
407 	nport = eb_port_take(eb, port);
408 	if (nport == NULL)
409 		return (ENOMEM);
410 
411 	nebe = pool_get(&eb_entry_pool, PR_NOWAIT);
412 	if (nebe == NULL) {
413 		eb_port_rele(eb, nport);
414 		return (ENOMEM);
415 	}
416 
417 	smr_init(&nebe->ebe_smr_entry);
418 	refcnt_init(&nebe->ebe_refs);
419 	nebe->ebe_etherbridge = eb;
420 
421 	nebe->ebe_addr = eba;
422 	nebe->ebe_port = nport;
423 	nebe->ebe_type = type;
424 	nebe->ebe_age = getuptime();
425 
426 	ebl = etherbridge_list(eb, eba);
427 
428 	mtx_enter(&eb->eb_lock);
429 	num = eb->eb_num + 1;
430 	if (num >= eb->eb_max)
431 		error = ENOSPC;
432 	else if (ebt_insert(eb, nebe) != NULL)
433 		error = EADDRINUSE;
434 	else {
435 		/* we win, do the insert */
436 		ebl_insert(ebl, nebe); /* give the ref to etherbridge */
437 		eb->eb_num = num;
438 	}
439 	mtx_leave(&eb->eb_lock);
440 
441 	if (error != 0) {
442 		/*
443 		 * the new entry didnt make it into the
444 		 * table, so it can be freed directly.
445 		 */
446 		ebe_free(nebe);
447 	}
448 
449 	return (error);
450 }
451 int
452 etherbridge_del_addr(struct etherbridge *eb, const struct ether_addr *ea)
453 {
454 	uint64_t eba = ether_addr_to_e64(ea);
455 	struct eb_list *ebl;
456 	struct eb_entry *oebe;
457 	const struct eb_entry key = {
458 		.ebe_addr = eba,
459 	};
460 	int error = 0;
461 
462 	ebl = etherbridge_list(eb, eba);
463 
464 	mtx_enter(&eb->eb_lock);
465 	oebe = ebt_find(eb, &key);
466 	if (oebe == NULL)
467 		error = ESRCH;
468 	else {
469 		KASSERT(eb->eb_num > 0);
470 		eb->eb_num--;
471 
472 		ebl_remove(ebl, oebe); /* it's our ref now */
473 		ebt_remove(eb, oebe);
474 	}
475 	mtx_leave(&eb->eb_lock);
476 
477 	if (oebe != NULL)
478 		ebe_rele(oebe);
479 
480 	return (error);
481 }
482 
483 static void
484 etherbridge_age(void *arg)
485 {
486 	struct etherbridge *eb = arg;
487 	struct eb_entry *ebe, *nebe;
488 	struct eb_queue ebq = TAILQ_HEAD_INITIALIZER(ebq);
489 	int diff;
490 	unsigned int now = getuptime();
491 	size_t i;
492 
493 	timeout_add_sec(&eb->eb_tmo_age, 100);
494 
495 	for (i = 0; i < ETHERBRIDGE_TABLE_SIZE; i++) {
496 		struct eb_list *ebl = &eb->eb_table[i];
497 #if 0
498 		if (SMR_TAILQ_EMPTY(ebl));
499 			continue;
500 #endif
501 
502 		mtx_enter(&eb->eb_lock); /* don't block map too much */
503 		SMR_TAILQ_FOREACH_SAFE_LOCKED(ebe, ebl, ebe_lentry, nebe) {
504 			if (ebe->ebe_type != EBE_DYNAMIC)
505 				continue;
506 
507 			diff = now - ebe->ebe_age;
508 			if (diff < eb->eb_max_age)
509 				continue;
510 
511 			ebl_remove(ebl, ebe);
512 			ebt_remove(eb, ebe);
513 			eb->eb_num--;
514 
515 			/* we own the tables ref now */
516 
517 			TAILQ_INSERT_TAIL(&ebq, ebe, ebe_qentry);
518 		}
519 		mtx_leave(&eb->eb_lock);
520 	}
521 
522 	TAILQ_FOREACH_SAFE(ebe, &ebq, ebe_qentry, nebe) {
523 		TAILQ_REMOVE(&ebq, ebe, ebe_qentry);
524 		ebe_rele(ebe);
525 	}
526 }
527 
528 void
529 etherbridge_detach_port(struct etherbridge *eb, void *port)
530 {
531 	struct eb_entry *ebe, *nebe;
532 	struct eb_queue ebq = TAILQ_HEAD_INITIALIZER(ebq);
533 	size_t i;
534 
535 	for (i = 0; i < ETHERBRIDGE_TABLE_SIZE; i++) {
536 		struct eb_list *ebl = &eb->eb_table[i];
537 
538 		mtx_enter(&eb->eb_lock); /* don't block map too much */
539 		SMR_TAILQ_FOREACH_SAFE_LOCKED(ebe, ebl, ebe_lentry, nebe) {
540 			if (!eb_port_eq(eb, ebe->ebe_port, port))
541 				continue;
542 
543 			ebl_remove(ebl, ebe);
544 			ebt_remove(eb, ebe);
545 			eb->eb_num--;
546 
547 			/* we own the tables ref now */
548 
549 			TAILQ_INSERT_TAIL(&ebq, ebe, ebe_qentry);
550 		}
551 		mtx_leave(&eb->eb_lock);
552 	}
553 
554 	smr_barrier(); /* try and do it once for all the entries */
555 
556 	TAILQ_FOREACH_SAFE(ebe, &ebq, ebe_qentry, nebe) {
557 		TAILQ_REMOVE(&ebq, ebe, ebe_qentry);
558 		if (refcnt_rele(&ebe->ebe_refs))
559 			ebe_free(ebe);
560 	}
561 }
562 
563 void
564 etherbridge_flush(struct etherbridge *eb, uint32_t flags)
565 {
566 	struct eb_entry *ebe, *nebe;
567 	struct eb_queue ebq = TAILQ_HEAD_INITIALIZER(ebq);
568 	size_t i;
569 
570 	for (i = 0; i < ETHERBRIDGE_TABLE_SIZE; i++) {
571 		struct eb_list *ebl = &eb->eb_table[i];
572 
573 		mtx_enter(&eb->eb_lock); /* don't block map too much */
574 		SMR_TAILQ_FOREACH_SAFE_LOCKED(ebe, ebl, ebe_lentry, nebe) {
575 			if (flags == IFBF_FLUSHDYN &&
576 			    ebe->ebe_type != EBE_DYNAMIC)
577 				continue;
578 
579 			ebl_remove(ebl, ebe);
580 			ebt_remove(eb, ebe);
581 			eb->eb_num--;
582 
583 			/* we own the tables ref now */
584 
585 			TAILQ_INSERT_TAIL(&ebq, ebe, ebe_qentry);
586 		}
587 		mtx_leave(&eb->eb_lock);
588 	}
589 
590 	smr_barrier(); /* try and do it once for all the entries */
591 
592 	TAILQ_FOREACH_SAFE(ebe, &ebq, ebe_qentry, nebe) {
593 		TAILQ_REMOVE(&ebq, ebe, ebe_qentry);
594 		if (refcnt_rele(&ebe->ebe_refs))
595 			ebe_free(ebe);
596 	}
597 }
598 
599 int
600 etherbridge_rtfind(struct etherbridge *eb, struct ifbaconf *baconf)
601 {
602 	struct eb_entry *ebe;
603 	struct ifbareq bareq;
604 	caddr_t buf;
605 	size_t len, nlen;
606 	time_t age, now = getuptime();
607 	int error;
608 
609 	if (baconf->ifbac_len == 0) {
610 		/* single read is atomic */
611 		baconf->ifbac_len = eb->eb_num * sizeof(bareq);
612 		return (0);
613 	}
614 
615 	buf = malloc(baconf->ifbac_len, M_TEMP, M_WAITOK|M_CANFAIL);
616 	if (buf == NULL)
617 		return (ENOMEM);
618 	len = 0;
619 
620 	mtx_enter(&eb->eb_lock);
621 	RBT_FOREACH(ebe, eb_tree, &eb->eb_tree) {
622 		nlen = len + sizeof(bareq);
623 		if (nlen > baconf->ifbac_len)
624 			break;
625 
626 		strlcpy(bareq.ifba_name, eb->eb_name,
627 		    sizeof(bareq.ifba_name));
628 		eb_port_ifname(eb,
629 		    bareq.ifba_ifsname, sizeof(bareq.ifba_ifsname),
630 		    ebe->ebe_port);
631 		ether_e64_to_addr(&bareq.ifba_dst, ebe->ebe_addr);
632 
633 		memset(&bareq.ifba_dstsa, 0, sizeof(bareq.ifba_dstsa));
634 		eb_port_sa(eb, &bareq.ifba_dstsa, ebe->ebe_port);
635 
636 		switch (ebe->ebe_type) {
637 		case EBE_DYNAMIC:
638 			age = now - ebe->ebe_age;
639 			bareq.ifba_age = MIN(age, 0xff);
640 			bareq.ifba_flags = IFBAF_DYNAMIC;
641 			break;
642 		case EBE_STATIC:
643 			bareq.ifba_age = 0;
644 			bareq.ifba_flags = IFBAF_STATIC;
645 			break;
646 		}
647 
648 		memcpy(buf + len, &bareq, sizeof(bareq));
649 		len = nlen;
650 	}
651 	nlen = baconf->ifbac_len;
652 	baconf->ifbac_len = eb->eb_num * sizeof(bareq);
653 	mtx_leave(&eb->eb_lock);
654 
655 	error = copyout(buf, baconf->ifbac_buf, len);
656 	free(buf, M_TEMP, nlen);
657 
658 	return (error);
659 }
660 
661 int
662 etherbridge_set_max(struct etherbridge *eb, struct ifbrparam *bparam)
663 {
664 	if (bparam->ifbrp_csize < 1 ||
665 	    bparam->ifbrp_csize > 4096) /* XXX */
666 		return (EINVAL);
667 
668 	/* commit */
669 	eb->eb_max = bparam->ifbrp_csize;
670 
671 	return (0);
672 }
673 
674 int
675 etherbridge_get_max(struct etherbridge *eb, struct ifbrparam *bparam)
676 {
677 	bparam->ifbrp_csize = eb->eb_max;
678 
679 	return (0);
680 }
681 
682 int
683 etherbridge_set_tmo(struct etherbridge *eb, struct ifbrparam *bparam)
684 {
685 	if (bparam->ifbrp_ctime < 8 ||
686 	    bparam->ifbrp_ctime > 3600)
687 		return (EINVAL);
688 
689 	/* commit */
690 	eb->eb_max_age = bparam->ifbrp_ctime;
691 
692 	return (0);
693 }
694 
695 int
696 etherbridge_get_tmo(struct etherbridge *eb, struct ifbrparam *bparam)
697 {
698 	bparam->ifbrp_ctime = eb->eb_max_age;
699 
700 	return (0);
701 }
702