src/sys/net/if_trunk.c

1720 lines
40 KiB
C

/* $OpenBSD: if_trunk.c,v 1.154 2023/12/23 10:52:54 bluhm Exp $ */
/*
* Copyright (c) 2005, 2006, 2007 Reyk Floeter <reyk@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/systm.h>
#include <sys/task.h>
#include <sys/timeout.h>
#include <crypto/siphash.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_types.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#ifdef INET6
#include <netinet/ip6.h>
#endif
#include <net/if_vlan_var.h>
#include <net/if_trunk.h>
#include <net/trunklacp.h>
#include "bpfilter.h"
#if NBPFILTER > 0
#include <net/bpf.h>
#endif
SLIST_HEAD(__trhead, trunk_softc) trunk_list; /* list of trunks */
void trunkattach(int);
int trunk_clone_create(struct if_clone *, int);
int trunk_clone_destroy(struct ifnet *);
void trunk_lladdr(struct arpcom *, u_int8_t *);
int trunk_capabilities(struct trunk_softc *);
void trunk_port_lladdr(struct trunk_port *, u_int8_t *);
int trunk_port_create(struct trunk_softc *, struct ifnet *);
int trunk_port_destroy(struct trunk_port *);
void trunk_port_state(void *);
void trunk_port_ifdetach(void *);
int trunk_port_ioctl(struct ifnet *, u_long, caddr_t);
int trunk_port_output(struct ifnet *, struct mbuf *, struct sockaddr *,
struct rtentry *);
struct trunk_port *trunk_port_get(struct trunk_softc *, struct ifnet *);
int trunk_port_checkstacking(struct trunk_softc *);
void trunk_port2req(struct trunk_port *, struct trunk_reqport *);
int trunk_ioctl(struct ifnet *, u_long, caddr_t);
int trunk_ether_addmulti(struct trunk_softc *, struct ifreq *);
int trunk_ether_delmulti(struct trunk_softc *, struct ifreq *);
void trunk_ether_purgemulti(struct trunk_softc *);
int trunk_ether_cmdmulti(struct trunk_port *, u_long);
int trunk_ioctl_allports(struct trunk_softc *, u_long, caddr_t);
void trunk_input(struct ifnet *, struct mbuf *);
void trunk_start(struct ifnet *);
void trunk_init(struct ifnet *);
void trunk_stop(struct ifnet *);
int trunk_media_change(struct ifnet *);
void trunk_media_status(struct ifnet *, struct ifmediareq *);
struct trunk_port *trunk_link_active(struct trunk_softc *,
struct trunk_port *);
const void *trunk_gethdr(struct mbuf *, u_int, u_int, void *);
struct if_clone trunk_cloner =
IF_CLONE_INITIALIZER("trunk", trunk_clone_create, trunk_clone_destroy);
/* Simple round robin */
int trunk_rr_attach(struct trunk_softc *);
int trunk_rr_detach(struct trunk_softc *);
void trunk_rr_port_destroy(struct trunk_port *);
int trunk_rr_start(struct trunk_softc *, struct mbuf *);
int trunk_rr_input(struct trunk_softc *, struct trunk_port *,
struct mbuf *);
/* Active failover */
int trunk_fail_attach(struct trunk_softc *);
int trunk_fail_detach(struct trunk_softc *);
int trunk_fail_port_create(struct trunk_port *);
void trunk_fail_port_destroy(struct trunk_port *);
int trunk_fail_start(struct trunk_softc *, struct mbuf *);
int trunk_fail_input(struct trunk_softc *, struct trunk_port *,
struct mbuf *);
void trunk_fail_linkstate(struct trunk_port *);
/* Loadbalancing */
int trunk_lb_attach(struct trunk_softc *);
int trunk_lb_detach(struct trunk_softc *);
int trunk_lb_port_create(struct trunk_port *);
void trunk_lb_port_destroy(struct trunk_port *);
int trunk_lb_start(struct trunk_softc *, struct mbuf *);
int trunk_lb_input(struct trunk_softc *, struct trunk_port *,
struct mbuf *);
int trunk_lb_porttable(struct trunk_softc *, struct trunk_port *);
/* Broadcast mode */
int trunk_bcast_attach(struct trunk_softc *);
int trunk_bcast_detach(struct trunk_softc *);
int trunk_bcast_start(struct trunk_softc *, struct mbuf *);
int trunk_bcast_input(struct trunk_softc *, struct trunk_port *,
struct mbuf *);
/* 802.3ad LACP */
int trunk_lacp_attach(struct trunk_softc *);
int trunk_lacp_detach(struct trunk_softc *);
int trunk_lacp_start(struct trunk_softc *, struct mbuf *);
int trunk_lacp_input(struct trunk_softc *, struct trunk_port *,
struct mbuf *);
/* Trunk protocol table */
static const struct {
enum trunk_proto ti_proto;
int (*ti_attach)(struct trunk_softc *);
} trunk_protos[] = {
{ TRUNK_PROTO_ROUNDROBIN, trunk_rr_attach },
{ TRUNK_PROTO_FAILOVER, trunk_fail_attach },
{ TRUNK_PROTO_LOADBALANCE, trunk_lb_attach },
{ TRUNK_PROTO_BROADCAST, trunk_bcast_attach },
{ TRUNK_PROTO_LACP, trunk_lacp_attach },
{ TRUNK_PROTO_NONE, NULL }
};
void
trunkattach(int count)
{
SLIST_INIT(&trunk_list);
if_clone_attach(&trunk_cloner);
}
int
trunk_clone_create(struct if_clone *ifc, int unit)
{
struct trunk_softc *tr;
struct ifnet *ifp;
int i, error = 0;
tr = malloc(sizeof(*tr), M_DEVBUF, M_WAITOK|M_ZERO);
tr->tr_proto = TRUNK_PROTO_NONE;
for (i = 0; trunk_protos[i].ti_proto != TRUNK_PROTO_NONE; i++) {
if (trunk_protos[i].ti_proto == TRUNK_PROTO_DEFAULT) {
tr->tr_proto = trunk_protos[i].ti_proto;
if ((error = trunk_protos[i].ti_attach(tr)) != 0) {
free(tr, M_DEVBUF, sizeof *tr);
return (error);
}
break;
}
}
SLIST_INIT(&tr->tr_ports);
/* Initialise pseudo media types */
ifmedia_init(&tr->tr_media, 0, trunk_media_change,
trunk_media_status);
ifmedia_add(&tr->tr_media, IFM_ETHER | IFM_AUTO, 0, NULL);
ifmedia_set(&tr->tr_media, IFM_ETHER | IFM_AUTO);
ifp = &tr->tr_ac.ac_if;
ifp->if_softc = tr;
ifp->if_start = trunk_start;
ifp->if_ioctl = trunk_ioctl;
ifp->if_flags = IFF_SIMPLEX | IFF_BROADCAST | IFF_MULTICAST;
ifp->if_capabilities = trunk_capabilities(tr);
ifp->if_xflags = IFXF_CLONED;
snprintf(ifp->if_xname, sizeof(ifp->if_xname), "%s%d",
ifc->ifc_name, unit);
/*
* Attach as an ordinary ethernet device, children will be attached
* as special device IFT_IEEE8023ADLAG.
*/
if_counters_alloc(ifp);
if_attach(ifp);
ether_ifattach(ifp);
/* Insert into the global list of trunks */
SLIST_INSERT_HEAD(&trunk_list, tr, tr_entries);
return (0);
}
int
trunk_clone_destroy(struct ifnet *ifp)
{
struct trunk_softc *tr = (struct trunk_softc *)ifp->if_softc;
struct trunk_port *tp;
int error;
/* Remove any multicast groups that we may have joined. */
trunk_ether_purgemulti(tr);
/* Shutdown and remove trunk ports, return on error */
NET_LOCK();
while ((tp = SLIST_FIRST(&tr->tr_ports)) != NULL) {
if ((error = trunk_port_destroy(tp)) != 0) {
NET_UNLOCK();
return (error);
}
}
NET_UNLOCK();
ifmedia_delete_instance(&tr->tr_media, IFM_INST_ANY);
ether_ifdetach(ifp);
if_detach(ifp);
SLIST_REMOVE(&trunk_list, tr, trunk_softc, tr_entries);
free(tr, M_DEVBUF, sizeof *tr);
return (0);
}
void
trunk_lladdr(struct arpcom *ac, u_int8_t *lladdr)
{
struct ifnet *ifp = &ac->ac_if;
struct sockaddr_dl *sdl;
sdl = ifp->if_sadl;
sdl->sdl_type = IFT_ETHER;
sdl->sdl_alen = ETHER_ADDR_LEN;
bcopy(lladdr, LLADDR(sdl), ETHER_ADDR_LEN);
bcopy(lladdr, ac->ac_enaddr, ETHER_ADDR_LEN);
}
int
trunk_capabilities(struct trunk_softc *tr)
{
struct trunk_port *tp;
int cap = ~0, priv;
/* Preserve private capabilities */
priv = tr->tr_capabilities & IFCAP_TRUNK_MASK;
/* Get capabilities from the trunk ports */
SLIST_FOREACH(tp, &tr->tr_ports, tp_entries)
cap &= tp->tp_capabilities;
if (tr->tr_ifflags & IFF_DEBUG) {
printf("%s: capabilities 0x%08x\n",
tr->tr_ifname, cap == ~0 ? priv : (cap | priv));
}
return (cap == ~0 ? priv : (cap | priv));
}
void
trunk_port_lladdr(struct trunk_port *tp, u_int8_t *lladdr)
{
struct ifnet *ifp = tp->tp_if;
/* Set the link layer address */
trunk_lladdr((struct arpcom *)ifp, lladdr);
/* Reset the port to update the lladdr */
ifnewlladdr(ifp);
}
int
trunk_port_create(struct trunk_softc *tr, struct ifnet *ifp)
{
struct trunk_softc *tr_ptr;
struct trunk_port *tp;
struct arpcom *ac0;
int error = 0;
/* Limit the maximal number of trunk ports */
if (tr->tr_count >= TRUNK_MAX_PORTS)
return (ENOSPC);
/* Check if port has already been associated to a trunk */
if (trunk_port_get(NULL, ifp) != NULL)
return (EBUSY);
/* XXX Disallow non-ethernet interfaces (this should be any of 802) */
if (ifp->if_type != IFT_ETHER)
return (EPROTONOSUPPORT);
ac0 = (struct arpcom *)ifp;
if (ac0->ac_trunkport != NULL)
return (EBUSY);
/* Take MTU from the first member port */
if (SLIST_EMPTY(&tr->tr_ports)) {
if (tr->tr_ifflags & IFF_DEBUG)
printf("%s: first port, setting trunk mtu %u\n",
tr->tr_ifname, ifp->if_mtu);
tr->tr_ac.ac_if.if_mtu = ifp->if_mtu;
tr->tr_ac.ac_if.if_hardmtu = ifp->if_hardmtu;
} else if (tr->tr_ac.ac_if.if_mtu != ifp->if_mtu) {
printf("%s: adding %s failed, MTU %u != %u\n", tr->tr_ifname,
ifp->if_xname, ifp->if_mtu, tr->tr_ac.ac_if.if_mtu);
return (EINVAL);
}
if ((error = ifpromisc(ifp, 1)) != 0)
return (error);
if ((tp = malloc(sizeof *tp, M_DEVBUF, M_NOWAIT|M_ZERO)) == NULL)
return (ENOMEM);
/* Check if port is a stacked trunk */
SLIST_FOREACH(tr_ptr, &trunk_list, tr_entries) {
if (ifp == &tr_ptr->tr_ac.ac_if) {
tp->tp_flags |= TRUNK_PORT_STACK;
if (trunk_port_checkstacking(tr_ptr) >=
TRUNK_MAX_STACKING) {
free(tp, M_DEVBUF, sizeof *tp);
return (E2BIG);
}
}
}
/* Change the interface type */
tp->tp_iftype = ifp->if_type;
ifp->if_type = IFT_IEEE8023ADLAG;
tp->tp_ioctl = ifp->if_ioctl;
ifp->if_ioctl = trunk_port_ioctl;
tp->tp_output = ifp->if_output;
ifp->if_output = trunk_port_output;
tp->tp_if = ifp;
tp->tp_trunk = tr;
/* Save port link layer address */
bcopy(((struct arpcom *)ifp)->ac_enaddr, tp->tp_lladdr, ETHER_ADDR_LEN);
if (SLIST_EMPTY(&tr->tr_ports)) {
tr->tr_primary = tp;
tp->tp_flags |= TRUNK_PORT_MASTER;
trunk_lladdr(&tr->tr_ac, tp->tp_lladdr);
}
/* Insert into the list of ports */
SLIST_INSERT_HEAD(&tr->tr_ports, tp, tp_entries);
tr->tr_count++;
/* Update link layer address for this port */
trunk_port_lladdr(tp,
((struct arpcom *)(tr->tr_primary->tp_if))->ac_enaddr);
/* Update trunk capabilities */
tr->tr_capabilities = trunk_capabilities(tr);
/* Add multicast addresses to this port */
trunk_ether_cmdmulti(tp, SIOCADDMULTI);
/* Register callback for physical link state changes */
task_set(&tp->tp_ltask, trunk_port_state, tp);
if_linkstatehook_add(ifp, &tp->tp_ltask);
/* Register callback if parent wants to unregister */
task_set(&tp->tp_dtask, trunk_port_ifdetach, tp);
if_detachhook_add(ifp, &tp->tp_dtask);
if (tr->tr_port_create != NULL)
error = (*tr->tr_port_create)(tp);
/* Change input handler of the physical interface. */
tp->tp_input = ifp->if_input;
NET_ASSERT_LOCKED();
ac0->ac_trunkport = tp;
ifp->if_input = trunk_input;
return (error);
}
int
trunk_port_checkstacking(struct trunk_softc *tr)
{
struct trunk_softc *tr_ptr;
struct trunk_port *tp;
int m = 0;
SLIST_FOREACH(tp, &tr->tr_ports, tp_entries) {
if (tp->tp_flags & TRUNK_PORT_STACK) {
tr_ptr = (struct trunk_softc *)tp->tp_if->if_softc;
m = MAX(m, trunk_port_checkstacking(tr_ptr));
}
}
return (m + 1);
}
int
trunk_port_destroy(struct trunk_port *tp)
{
struct trunk_softc *tr = (struct trunk_softc *)tp->tp_trunk;
struct trunk_port *tp_ptr;
struct ifnet *ifp = tp->tp_if;
struct arpcom *ac0 = (struct arpcom *)ifp;
/* Restore previous input handler. */
NET_ASSERT_LOCKED();
ifp->if_input = tp->tp_input;
ac0->ac_trunkport = NULL;
/* Remove multicast addresses from this port */
trunk_ether_cmdmulti(tp, SIOCDELMULTI);
ifpromisc(ifp, 0);
if (tr->tr_port_destroy != NULL)
(*tr->tr_port_destroy)(tp);
/* Restore interface type. */
ifp->if_type = tp->tp_iftype;
ifp->if_ioctl = tp->tp_ioctl;
ifp->if_output = tp->tp_output;
if_detachhook_del(ifp, &tp->tp_dtask);
if_linkstatehook_del(ifp, &tp->tp_ltask);
/* Finally, remove the port from the trunk */
SLIST_REMOVE(&tr->tr_ports, tp, trunk_port, tp_entries);
tr->tr_count--;
/* Update the primary interface */
if (tp == tr->tr_primary) {
u_int8_t lladdr[ETHER_ADDR_LEN];
if ((tp_ptr = SLIST_FIRST(&tr->tr_ports)) == NULL) {
bzero(&lladdr, ETHER_ADDR_LEN);
} else {
bcopy(((struct arpcom *)tp_ptr->tp_if)->ac_enaddr,
lladdr, ETHER_ADDR_LEN);
tp_ptr->tp_flags = TRUNK_PORT_MASTER;
}
trunk_lladdr(&tr->tr_ac, lladdr);
tr->tr_primary = tp_ptr;
/* Update link layer address for each port */
SLIST_FOREACH(tp_ptr, &tr->tr_ports, tp_entries)
trunk_port_lladdr(tp_ptr, lladdr);
}
/* Reset the port lladdr */
trunk_port_lladdr(tp, tp->tp_lladdr);
if_put(ifp);
free(tp, M_DEVBUF, sizeof *tp);
/* Update trunk capabilities */
tr->tr_capabilities = trunk_capabilities(tr);
return (0);
}
int
trunk_port_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
struct trunk_reqport *rp = (struct trunk_reqport *)data;
struct trunk_softc *tr;
struct trunk_port *tp = NULL;
struct ifnet *ifp0 = NULL;
int error = 0;
/* Should be checked by the caller */
if (ifp->if_type != IFT_IEEE8023ADLAG ||
(tp = trunk_port_get(NULL, ifp)) == NULL ||
(tr = (struct trunk_softc *)tp->tp_trunk) == NULL) {
error = EINVAL;
goto fallback;
}
switch (cmd) {
case SIOCGTRUNKPORT:
if (rp->rp_portname[0] == '\0' ||
(ifp0 = if_unit(rp->rp_portname)) != ifp) {
if_put(ifp0);
error = EINVAL;
break;
}
if_put(ifp0);
/* Search in all trunks if the global flag is set */
if ((tp = trunk_port_get(rp->rp_flags & TRUNK_PORT_GLOBAL ?
NULL : tr, ifp)) == NULL) {
error = ENOENT;
break;
}
trunk_port2req(tp, rp);
break;
case SIOCSIFMTU:
/* Do not allow the MTU to be changed once joined */
error = EINVAL;
break;
default:
error = ENOTTY;
goto fallback;
}
return (error);
fallback:
if (tp != NULL)
error = (*tp->tp_ioctl)(ifp, cmd, data);
return (error);
}
int
trunk_port_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst,
struct rtentry *rt)
{
/* restrict transmission on trunk members to bpf only */
if (ifp->if_type == IFT_IEEE8023ADLAG &&
(m_tag_find(m, PACKET_TAG_DLT, NULL) == NULL)) {
m_freem(m);
return (EBUSY);
}
return (ether_output(ifp, m, dst, rt));
}
void
trunk_port_ifdetach(void *arg)
{
struct trunk_port *tp = (struct trunk_port *)arg;
trunk_port_destroy(tp);
}
struct trunk_port *
trunk_port_get(struct trunk_softc *tr, struct ifnet *ifp)
{
struct trunk_port *tp;
struct trunk_softc *tr_ptr;
if (tr != NULL) {
/* Search port in specified trunk */
SLIST_FOREACH(tp, &tr->tr_ports, tp_entries) {
if (tp->tp_if == ifp)
return (tp);
}
} else {
/* Search all trunks for the selected port */
SLIST_FOREACH(tr_ptr, &trunk_list, tr_entries) {
SLIST_FOREACH(tp, &tr_ptr->tr_ports, tp_entries) {
if (tp->tp_if == ifp)
return (tp);
}
}
}
return (NULL);
}
void
trunk_port2req(struct trunk_port *tp, struct trunk_reqport *rp)
{
struct trunk_softc *tr = (struct trunk_softc *)tp->tp_trunk;
strlcpy(rp->rp_ifname, tr->tr_ifname, sizeof(rp->rp_ifname));
strlcpy(rp->rp_portname, tp->tp_if->if_xname, sizeof(rp->rp_portname));
rp->rp_prio = tp->tp_prio;
if (tr->tr_portreq != NULL)
(*tr->tr_portreq)(tp, (caddr_t)&rp->rp_psc);
/* Add protocol specific flags */
switch (tr->tr_proto) {
case TRUNK_PROTO_FAILOVER:
rp->rp_flags = tp->tp_flags;
if (tp == trunk_link_active(tr, tr->tr_primary))
rp->rp_flags |= TRUNK_PORT_ACTIVE;
break;
case TRUNK_PROTO_ROUNDROBIN:
case TRUNK_PROTO_LOADBALANCE:
case TRUNK_PROTO_BROADCAST:
rp->rp_flags = tp->tp_flags;
if (TRUNK_PORTACTIVE(tp))
rp->rp_flags |= TRUNK_PORT_ACTIVE;
break;
case TRUNK_PROTO_LACP:
/* LACP has a different definition of active */
rp->rp_flags = lacp_port_status(tp);
break;
default:
break;
}
}
int
trunk_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
struct trunk_softc *tr = (struct trunk_softc *)ifp->if_softc;
struct trunk_reqall *ra = (struct trunk_reqall *)data;
struct trunk_reqport *rp = (struct trunk_reqport *)data, rpbuf;
struct trunk_opts *tro = (struct trunk_opts *)data;
struct ifreq *ifr = (struct ifreq *)data;
struct lacp_softc *lsc;
struct trunk_port *tp;
struct lacp_port *lp;
struct ifnet *tpif;
int i, error = 0;
bzero(&rpbuf, sizeof(rpbuf));
switch (cmd) {
case SIOCGTRUNK:
ra->ra_proto = tr->tr_proto;
if (tr->tr_req != NULL)
(*tr->tr_req)(tr, (caddr_t)&ra->ra_psc);
ra->ra_ports = i = 0;
tp = SLIST_FIRST(&tr->tr_ports);
while (tp && ra->ra_size >=
i + sizeof(struct trunk_reqport)) {
trunk_port2req(tp, &rpbuf);
error = copyout(&rpbuf, (caddr_t)ra->ra_port + i,
sizeof(struct trunk_reqport));
if (error)
break;
i += sizeof(struct trunk_reqport);
ra->ra_ports++;
tp = SLIST_NEXT(tp, tp_entries);
}
break;
case SIOCSTRUNK:
if ((error = suser(curproc)) != 0) {
error = EPERM;
break;
}
if (ra->ra_proto >= TRUNK_PROTO_MAX) {
error = EPROTONOSUPPORT;
break;
}
/*
* Use of ifp->if_input and ac->ac_trunkport is
* protected by NET_LOCK, but that may not be true
* in the future. The below comment and code flow is
* maintained to help in that future.
*
* Serialize modifications to the trunk and trunk
* ports via the ifih SRP: detaching trunk_input
* from the trunk port will require all currently
* running trunk_input's on this port to finish
* granting us an exclusive access to it.
*/
NET_ASSERT_LOCKED();
SLIST_FOREACH(tp, &tr->tr_ports, tp_entries) {
/* if_ih_remove(tp->tp_if, trunk_input, tp); */
tp->tp_if->if_input = tp->tp_input;
}
if (tr->tr_proto != TRUNK_PROTO_NONE)
error = tr->tr_detach(tr);
if (error != 0)
break;
for (i = 0; i < nitems(trunk_protos); i++) {
if (trunk_protos[i].ti_proto == ra->ra_proto) {
if (tr->tr_ifflags & IFF_DEBUG)
printf("%s: using proto %u\n",
tr->tr_ifname,
trunk_protos[i].ti_proto);
tr->tr_proto = trunk_protos[i].ti_proto;
if (tr->tr_proto != TRUNK_PROTO_NONE)
error = trunk_protos[i].ti_attach(tr);
SLIST_FOREACH(tp, &tr->tr_ports, tp_entries) {
/* if_ih_insert(tp->tp_if,
trunk_input, tp); */
tp->tp_if->if_input = trunk_input;
}
/* Update trunk capabilities */
tr->tr_capabilities = trunk_capabilities(tr);
goto out;
}
}
error = EPROTONOSUPPORT;
break;
case SIOCGTRUNKOPTS:
/* Only LACP trunks have options atm */
if (tro->to_proto != TRUNK_PROTO_LACP) {
error = EPROTONOSUPPORT;
break;
}
lsc = LACP_SOFTC(tr);
tro->to_lacpopts.lacp_mode = lsc->lsc_mode;
tro->to_lacpopts.lacp_timeout = lsc->lsc_timeout;
tro->to_lacpopts.lacp_prio = lsc->lsc_sys_prio;
tro->to_lacpopts.lacp_portprio = lsc->lsc_port_prio;
tro->to_lacpopts.lacp_ifqprio = lsc->lsc_ifq_prio;
break;
case SIOCSTRUNKOPTS:
if ((error = suser(curproc)) != 0) {
error = EPERM;
break;
}
/* Only LACP trunks have options atm */
if (tro->to_proto != TRUNK_PROTO_LACP) {
error = EPROTONOSUPPORT;
break;
}
lsc = LACP_SOFTC(tr);
switch(tro->to_opts) {
case TRUNK_OPT_LACP_MODE:
/*
* Ensure mode changes occur immediately
* on all ports
*/
lsc->lsc_mode = tro->to_lacpopts.lacp_mode;
if (lsc->lsc_mode == 0) {
LIST_FOREACH(lp, &lsc->lsc_ports,
lp_next)
lp->lp_state &=
~LACP_STATE_ACTIVITY;
} else {
LIST_FOREACH(lp, &lsc->lsc_ports,
lp_next)
lp->lp_state |=
LACP_STATE_ACTIVITY;
}
break;
case TRUNK_OPT_LACP_TIMEOUT:
/*
* Ensure timeout changes occur immediately
* on all ports
*/
lsc->lsc_timeout =
tro->to_lacpopts.lacp_timeout;
if (lsc->lsc_timeout == 0) {
LIST_FOREACH(lp, &lsc->lsc_ports,
lp_next)
lp->lp_state &=
~LACP_STATE_TIMEOUT;
} else {
LIST_FOREACH(lp, &lsc->lsc_ports,
lp_next)
lp->lp_state |=
LACP_STATE_TIMEOUT;
}
break;
case TRUNK_OPT_LACP_SYS_PRIO:
if (tro->to_lacpopts.lacp_prio == 0) {
error = EINVAL;
break;
}
lsc->lsc_sys_prio = tro->to_lacpopts.lacp_prio;
break;
case TRUNK_OPT_LACP_PORT_PRIO:
if (tro->to_lacpopts.lacp_portprio == 0) {
error = EINVAL;
break;
}
lsc->lsc_port_prio =
tro->to_lacpopts.lacp_portprio;
break;
case TRUNK_OPT_LACP_IFQ_PRIO:
if (tro->to_lacpopts.lacp_ifqprio >
IFQ_MAXPRIO) {
error = EINVAL;
break;
}
lsc->lsc_ifq_prio =
tro->to_lacpopts.lacp_ifqprio;
break;
}
break;
case SIOCGTRUNKPORT:
if (rp->rp_portname[0] == '\0' ||
(tpif = if_unit(rp->rp_portname)) == NULL) {
error = EINVAL;
break;
}
/* Search in all trunks if the global flag is set */
tp = trunk_port_get(rp->rp_flags & TRUNK_PORT_GLOBAL ?
NULL : tr, tpif);
if_put(tpif);
if(tp == NULL) {
error = ENOENT;
break;
}
trunk_port2req(tp, rp);
break;
case SIOCSTRUNKPORT:
if ((error = suser(curproc)) != 0) {
error = EPERM;
break;
}
if (rp->rp_portname[0] == '\0' ||
(tpif = if_unit(rp->rp_portname)) == NULL) {
error = EINVAL;
break;
}
error = trunk_port_create(tr, tpif);
if (error != 0)
if_put(tpif);
break;
case SIOCSTRUNKDELPORT:
if ((error = suser(curproc)) != 0) {
error = EPERM;
break;
}
if (rp->rp_portname[0] == '\0' ||
(tpif = if_unit(rp->rp_portname)) == NULL) {
error = EINVAL;
break;
}
/* Search in all trunks if the global flag is set */
tp = trunk_port_get(rp->rp_flags & TRUNK_PORT_GLOBAL ?
NULL : tr, tpif);
if_put(tpif);
if(tp == NULL) {
error = ENOENT;
break;
}
error = trunk_port_destroy(tp);
break;
case SIOCSIFADDR:
ifp->if_flags |= IFF_UP;
/* FALLTHROUGH */
case SIOCSIFFLAGS:
error = ENETRESET;
break;
case SIOCADDMULTI:
error = trunk_ether_addmulti(tr, ifr);
break;
case SIOCDELMULTI:
error = trunk_ether_delmulti(tr, ifr);
break;
case SIOCSIFMEDIA:
case SIOCGIFMEDIA:
error = ifmedia_ioctl(ifp, ifr, &tr->tr_media, cmd);
break;
case SIOCSIFLLADDR:
/* Update the port lladdrs as well */
SLIST_FOREACH(tp, &tr->tr_ports, tp_entries)
trunk_port_lladdr(tp, ifr->ifr_addr.sa_data);
error = ENETRESET;
break;
default:
error = ether_ioctl(ifp, &tr->tr_ac, cmd, data);
}
if (error == ENETRESET) {
if (ifp->if_flags & IFF_UP) {
if ((ifp->if_flags & IFF_RUNNING) == 0)
trunk_init(ifp);
} else {
if (ifp->if_flags & IFF_RUNNING)
trunk_stop(ifp);
}
error = 0;
}
out:
return (error);
}
int
trunk_ether_addmulti(struct trunk_softc *tr, struct ifreq *ifr)
{
struct trunk_mc *mc;
u_int8_t addrlo[ETHER_ADDR_LEN], addrhi[ETHER_ADDR_LEN];
int error;
/* Ignore ENETRESET error code */
if ((error = ether_addmulti(ifr, &tr->tr_ac)) != ENETRESET)
return (error);
if ((mc = malloc(sizeof(*mc), M_DEVBUF, M_NOWAIT)) == NULL) {
error = ENOMEM;
goto failed;
}
ether_multiaddr(&ifr->ifr_addr, addrlo, addrhi);
ETHER_LOOKUP_MULTI(addrlo, addrhi, &tr->tr_ac, mc->mc_enm);
bcopy(&ifr->ifr_addr, &mc->mc_addr, ifr->ifr_addr.sa_len);
SLIST_INSERT_HEAD(&tr->tr_mc_head, mc, mc_entries);
if ((error = trunk_ioctl_allports(tr, SIOCADDMULTI,
(caddr_t)ifr)) != 0) {
trunk_ether_delmulti(tr, ifr);
return (error);
}
return (error);
failed:
ether_delmulti(ifr, &tr->tr_ac);
return (error);
}
int
trunk_ether_delmulti(struct trunk_softc *tr, struct ifreq *ifr)
{
struct ether_multi *enm;
struct trunk_mc *mc;
u_int8_t addrlo[ETHER_ADDR_LEN], addrhi[ETHER_ADDR_LEN];
int error;
if ((error = ether_multiaddr(&ifr->ifr_addr, addrlo, addrhi)) != 0)
return (error);
ETHER_LOOKUP_MULTI(addrlo, addrhi, &tr->tr_ac, enm);
if (enm == NULL)
return (EINVAL);
SLIST_FOREACH(mc, &tr->tr_mc_head, mc_entries)
if (mc->mc_enm == enm)
break;
/* We won't delete entries we didn't add */
if (mc == NULL)
return (EINVAL);
if ((error = ether_delmulti(ifr, &tr->tr_ac)) != ENETRESET)
return (error);
/* We no longer use this multicast address. Tell parent so. */
error = trunk_ioctl_allports(tr, SIOCDELMULTI, (caddr_t)ifr);
if (error == 0) {
SLIST_REMOVE(&tr->tr_mc_head, mc, trunk_mc, mc_entries);
free(mc, M_DEVBUF, sizeof(*mc));
} else {
/* XXX At least one port failed to remove the address */
if (tr->tr_ifflags & IFF_DEBUG) {
printf("%s: failed to remove multicast address "
"on all ports (%d)\n", tr->tr_ifname, error);
}
(void)ether_addmulti(ifr, &tr->tr_ac);
}
return (0);
}
void
trunk_ether_purgemulti(struct trunk_softc *tr)
{
struct trunk_mc *mc;
struct trunk_ifreq ifs;
struct ifreq *ifr = &ifs.ifreq.ifreq;
while ((mc = SLIST_FIRST(&tr->tr_mc_head)) != NULL) {
bcopy(&mc->mc_addr, &ifr->ifr_addr, mc->mc_addr.ss_len);
/* Try to remove multicast address on all ports */
trunk_ioctl_allports(tr, SIOCDELMULTI, (caddr_t)ifr);
SLIST_REMOVE(&tr->tr_mc_head, mc, trunk_mc, mc_entries);
free(mc, M_DEVBUF, sizeof(*mc));
}
}
int
trunk_ether_cmdmulti(struct trunk_port *tp, u_long cmd)
{
struct trunk_softc *tr = (struct trunk_softc *)tp->tp_trunk;
struct trunk_mc *mc;
struct trunk_ifreq ifs;
struct ifreq *ifr = &ifs.ifreq.ifreq;
int ret, error = 0;
bcopy(tp->tp_ifname, ifr->ifr_name, IFNAMSIZ);
SLIST_FOREACH(mc, &tr->tr_mc_head, mc_entries) {
bcopy(&mc->mc_addr, &ifr->ifr_addr, mc->mc_addr.ss_len);
if ((ret = tp->tp_ioctl(tp->tp_if, cmd, (caddr_t)ifr)) != 0) {
if (tr->tr_ifflags & IFF_DEBUG) {
printf("%s: ioctl %lu failed on %s: %d\n",
tr->tr_ifname, cmd, tp->tp_ifname, ret);
}
/* Store last known error and continue */
error = ret;
}
}
return (error);
}
int
trunk_ioctl_allports(struct trunk_softc *tr, u_long cmd, caddr_t data)
{
struct ifreq *ifr = (struct ifreq *)data;
struct trunk_port *tp;
int ret, error = 0;
SLIST_FOREACH(tp, &tr->tr_ports, tp_entries) {
bcopy(tp->tp_ifname, ifr->ifr_name, IFNAMSIZ);
if ((ret = tp->tp_ioctl(tp->tp_if, cmd, data)) != 0) {
if (tr->tr_ifflags & IFF_DEBUG) {
printf("%s: ioctl %lu failed on %s: %d\n",
tr->tr_ifname, cmd, tp->tp_ifname, ret);
}
/* Store last known error and continue */
error = ret;
}
}
return (error);
}
void
trunk_start(struct ifnet *ifp)
{
struct trunk_softc *tr = (struct trunk_softc *)ifp->if_softc;
struct mbuf *m;
int error;
for (;;) {
m = ifq_dequeue(&ifp->if_snd);
if (m == NULL)
break;
#if NBPFILTER > 0
if (ifp->if_bpf)
bpf_mtap_ether(ifp->if_bpf, m, BPF_DIRECTION_OUT);
#endif
if (tr->tr_proto != TRUNK_PROTO_NONE && tr->tr_count) {
error = (*tr->tr_start)(tr, m);
if (error != 0)
ifp->if_oerrors++;
} else {
m_freem(m);
if (tr->tr_proto != TRUNK_PROTO_NONE)
ifp->if_oerrors++;
}
}
}
u_int32_t
trunk_hashmbuf(struct mbuf *m, SIPHASH_KEY *key)
{
u_int16_t etype, ether_vtag;
u_int32_t p = 0;
u_int16_t *vlan, vlanbuf[2];
int off;
struct ether_header *eh;
struct ip *ip, ipbuf;
#ifdef INET6
u_int32_t flow;
struct ip6_hdr *ip6, ip6buf;
#endif
SIPHASH_CTX ctx;
if (m->m_pkthdr.csum_flags & M_FLOWID)
return (m->m_pkthdr.ph_flowid);
SipHash24_Init(&ctx, key);
off = sizeof(*eh);
if (m->m_len < off)
goto done;
eh = mtod(m, struct ether_header *);
etype = ntohs(eh->ether_type);
SipHash24_Update(&ctx, &eh->ether_shost, ETHER_ADDR_LEN);
SipHash24_Update(&ctx, &eh->ether_dhost, ETHER_ADDR_LEN);
/* Special handling for encapsulating VLAN frames */
if (m->m_flags & M_VLANTAG) {
ether_vtag = EVL_VLANOFTAG(m->m_pkthdr.ether_vtag);
SipHash24_Update(&ctx, &ether_vtag, sizeof(ether_vtag));
} else if (etype == ETHERTYPE_VLAN) {
if ((vlan = (u_int16_t *)
trunk_gethdr(m, off, EVL_ENCAPLEN, &vlanbuf)) == NULL)
return (p);
ether_vtag = EVL_VLANOFTAG(*vlan);
SipHash24_Update(&ctx, &ether_vtag, sizeof(ether_vtag));
etype = ntohs(vlan[1]);
off += EVL_ENCAPLEN;
}
switch (etype) {
case ETHERTYPE_IP:
if ((ip = (struct ip *)
trunk_gethdr(m, off, sizeof(*ip), &ipbuf)) == NULL)
return (p);
SipHash24_Update(&ctx, &ip->ip_src, sizeof(struct in_addr));
SipHash24_Update(&ctx, &ip->ip_dst, sizeof(struct in_addr));
break;
#ifdef INET6
case ETHERTYPE_IPV6:
if ((ip6 = (struct ip6_hdr *)
trunk_gethdr(m, off, sizeof(*ip6), &ip6buf)) == NULL)
return (p);
SipHash24_Update(&ctx, &ip6->ip6_src, sizeof(struct in6_addr));
SipHash24_Update(&ctx, &ip6->ip6_dst, sizeof(struct in6_addr));
flow = ip6->ip6_flow & IPV6_FLOWLABEL_MASK;
SipHash24_Update(&ctx, &flow, sizeof(flow)); /* IPv6 flow label */
break;
#endif
}
done:
return SipHash24_End(&ctx);
}
void
trunk_init(struct ifnet *ifp)
{
struct trunk_softc *tr = (struct trunk_softc *)ifp->if_softc;
ifp->if_flags |= IFF_RUNNING;
if (tr->tr_init != NULL)
(*tr->tr_init)(tr);
}
void
trunk_stop(struct ifnet *ifp)
{
struct trunk_softc *tr = (struct trunk_softc *)ifp->if_softc;
ifp->if_flags &= ~IFF_RUNNING;
if (tr->tr_stop != NULL)
(*tr->tr_stop)(tr);
}
void
trunk_input(struct ifnet *ifp, struct mbuf *m)
{
struct arpcom *ac0 = (struct arpcom *)ifp;
struct trunk_port *tp;
struct trunk_softc *tr;
struct ifnet *trifp = NULL;
struct ether_header *eh;
if (m->m_len < sizeof(*eh))
goto bad;
eh = mtod(m, struct ether_header *);
if (ETHER_IS_MULTICAST(eh->ether_dhost))
ifp->if_imcasts++;
/* Should be checked by the caller */
if (ifp->if_type != IFT_IEEE8023ADLAG)
goto bad;
tp = (struct trunk_port *)ac0->ac_trunkport;
if ((tr = (struct trunk_softc *)tp->tp_trunk) == NULL)
goto bad;
trifp = &tr->tr_ac.ac_if;
if (tr->tr_proto == TRUNK_PROTO_NONE)
goto bad;
if ((*tr->tr_input)(tr, tp, m)) {
/*
* We stop here if the packet has been consumed
* by the protocol routine.
*/
return;
}
if ((trifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING))
goto bad;
/*
* Drop promiscuously received packets if we are not in
* promiscuous mode.
*/
if (!ETHER_IS_MULTICAST(eh->ether_dhost) &&
(ifp->if_flags & IFF_PROMISC) &&
(trifp->if_flags & IFF_PROMISC) == 0) {
if (bcmp(&tr->tr_ac.ac_enaddr, eh->ether_dhost,
ETHER_ADDR_LEN)) {
m_freem(m);
return;
}
}
if_vinput(trifp, m);
return;
bad:
if (trifp != NULL)
trifp->if_ierrors++;
m_freem(m);
}
int
trunk_media_change(struct ifnet *ifp)
{
struct trunk_softc *tr = (struct trunk_softc *)ifp->if_softc;
if (tr->tr_ifflags & IFF_DEBUG)
printf("%s\n", __func__);
/* Ignore */
return (0);
}
void
trunk_media_status(struct ifnet *ifp, struct ifmediareq *imr)
{
struct trunk_softc *tr = (struct trunk_softc *)ifp->if_softc;
struct trunk_port *tp;
imr->ifm_status = IFM_AVALID;
imr->ifm_active = IFM_ETHER | IFM_AUTO;
SLIST_FOREACH(tp, &tr->tr_ports, tp_entries) {
if (TRUNK_PORTACTIVE(tp))
imr->ifm_status |= IFM_ACTIVE;
}
}
void
trunk_port_state(void *arg)
{
struct trunk_port *tp = (struct trunk_port *)arg;
struct trunk_softc *tr = NULL;
if (tp != NULL)
tr = (struct trunk_softc *)tp->tp_trunk;
if (tr == NULL)
return;
if (tr->tr_linkstate != NULL)
(*tr->tr_linkstate)(tp);
trunk_link_active(tr, tp);
}
struct trunk_port *
trunk_link_active(struct trunk_softc *tr, struct trunk_port *tp)
{
struct trunk_port *tp_next, *rval = NULL;
int new_link = LINK_STATE_DOWN;
/*
* Search a port which reports an active link state.
*/
if (tp == NULL)
goto search;
if (TRUNK_PORTACTIVE(tp)) {
rval = tp;
goto found;
}
if ((tp_next = SLIST_NEXT(tp, tp_entries)) != NULL &&
TRUNK_PORTACTIVE(tp_next)) {
rval = tp_next;
goto found;
}
search:
SLIST_FOREACH(tp_next, &tr->tr_ports, tp_entries) {
if (TRUNK_PORTACTIVE(tp_next)) {
rval = tp_next;
goto found;
}
}
found:
if (rval != NULL) {
/*
* The IEEE 802.1D standard assumes that a trunk with
* multiple ports is always full duplex. This is valid
* for load sharing trunks and if at least two links
* are active. Unfortunately, checking the latter would
* be too expensive at this point.
*/
if ((tr->tr_capabilities & IFCAP_TRUNK_FULLDUPLEX) &&
(tr->tr_count > 1))
new_link = LINK_STATE_FULL_DUPLEX;
else
new_link = rval->tp_link_state;
}
if (tr->tr_ac.ac_if.if_link_state != new_link) {
tr->tr_ac.ac_if.if_link_state = new_link;
if_link_state_change(&tr->tr_ac.ac_if);
}
return (rval);
}
const void *
trunk_gethdr(struct mbuf *m, u_int off, u_int len, void *buf)
{
if (m->m_pkthdr.len < (off + len))
return (NULL);
else if (m->m_len < (off + len)) {
m_copydata(m, off, len, buf);
return (buf);
}
return (mtod(m, caddr_t) + off);
}
/*
* Simple round robin trunking
*/
int
trunk_rr_attach(struct trunk_softc *tr)
{
struct trunk_port *tp;
tr->tr_detach = trunk_rr_detach;
tr->tr_start = trunk_rr_start;
tr->tr_input = trunk_rr_input;
tr->tr_init = NULL;
tr->tr_stop = NULL;
tr->tr_linkstate = NULL;
tr->tr_port_create = NULL;
tr->tr_port_destroy = trunk_rr_port_destroy;
tr->tr_capabilities = IFCAP_TRUNK_FULLDUPLEX;
tr->tr_req = NULL;
tr->tr_portreq = NULL;
tp = SLIST_FIRST(&tr->tr_ports);
tr->tr_psc = (caddr_t)tp;
return (0);
}
int
trunk_rr_detach(struct trunk_softc *tr)
{
tr->tr_psc = NULL;
return (0);
}
void
trunk_rr_port_destroy(struct trunk_port *tp)
{
struct trunk_softc *tr = (struct trunk_softc *)tp->tp_trunk;
if (tp == (struct trunk_port *)tr->tr_psc)
tr->tr_psc = NULL;
}
int
trunk_rr_start(struct trunk_softc *tr, struct mbuf *m)
{
struct trunk_port *tp = (struct trunk_port *)tr->tr_psc, *tp_next;
int error = 0;
if (tp == NULL && (tp = trunk_link_active(tr, NULL)) == NULL) {
m_freem(m);
return (ENOENT);
}
if ((error = if_enqueue(tp->tp_if, m)) != 0)
return (error);
/* Get next active port */
tp_next = trunk_link_active(tr, SLIST_NEXT(tp, tp_entries));
tr->tr_psc = (caddr_t)tp_next;
return (0);
}
int
trunk_rr_input(struct trunk_softc *tr, struct trunk_port *tp, struct mbuf *m)
{
/* Just pass in the packet to our trunk device */
return (0);
}
/*
* Active failover
*/
int
trunk_fail_attach(struct trunk_softc *tr)
{
tr->tr_detach = trunk_fail_detach;
tr->tr_start = trunk_fail_start;
tr->tr_input = trunk_fail_input;
tr->tr_init = NULL;
tr->tr_stop = NULL;
tr->tr_port_create = trunk_fail_port_create;
tr->tr_port_destroy = trunk_fail_port_destroy;
tr->tr_linkstate = trunk_fail_linkstate;
tr->tr_req = NULL;
tr->tr_portreq = NULL;
/* Get primary or the next active port */
tr->tr_psc = (caddr_t)trunk_link_active(tr, tr->tr_primary);
return (0);
}
int
trunk_fail_detach(struct trunk_softc *tr)
{
tr->tr_psc = NULL;
return (0);
}
int
trunk_fail_port_create(struct trunk_port *tp)
{
struct trunk_softc *tr = (struct trunk_softc *)tp->tp_trunk;
/* Get primary or the next active port */
tr->tr_psc = (caddr_t)trunk_link_active(tr, tr->tr_primary);
return (0);
}
void
trunk_fail_port_destroy(struct trunk_port *tp)
{
struct trunk_softc *tr = (struct trunk_softc *)tp->tp_trunk;
struct trunk_port *tp_next;
if ((caddr_t)tp == tr->tr_psc) {
/* Get the next active port */
tp_next = trunk_link_active(tr, SLIST_NEXT(tp, tp_entries));
if (tp_next == tp)
tr->tr_psc = NULL;
else
tr->tr_psc = (caddr_t)tp_next;
} else {
/* Get primary or the next active port */
tr->tr_psc = (caddr_t)trunk_link_active(tr, tr->tr_primary);
}
}
int
trunk_fail_start(struct trunk_softc *tr, struct mbuf *m)
{
struct trunk_port *tp = (struct trunk_port *)tr->tr_psc;
/* Use the master port if active or the next available port */
if (tp == NULL) {
m_freem(m);
return (ENOENT);
}
return (if_enqueue(tp->tp_if, m));
}
int
trunk_fail_input(struct trunk_softc *tr, struct trunk_port *tp, struct mbuf *m)
{
if ((caddr_t)tp == tr->tr_psc)
return (0);
m_freem(m);
return (-1);
}
void
trunk_fail_linkstate(struct trunk_port *tp)
{
struct trunk_softc *tr = (struct trunk_softc *)tp->tp_trunk;
tr->tr_psc = (caddr_t)trunk_link_active(tr, tr->tr_primary);
}
/*
* Loadbalancing
*/
int
trunk_lb_attach(struct trunk_softc *tr)
{
struct trunk_lb *lb;
if ((lb = malloc(sizeof(*lb), M_DEVBUF, M_NOWAIT|M_ZERO)) == NULL)
return (ENOMEM);
tr->tr_detach = trunk_lb_detach;
tr->tr_start = trunk_lb_start;
tr->tr_input = trunk_lb_input;
tr->tr_port_create = trunk_lb_port_create;
tr->tr_port_destroy = trunk_lb_port_destroy;
tr->tr_linkstate = NULL;
tr->tr_capabilities = IFCAP_TRUNK_FULLDUPLEX;
tr->tr_req = NULL;
tr->tr_portreq = NULL;
tr->tr_init = NULL;
tr->tr_stop = NULL;
arc4random_buf(&lb->lb_key, sizeof(lb->lb_key));
tr->tr_psc = (caddr_t)lb;
return (0);
}
int
trunk_lb_detach(struct trunk_softc *tr)
{
struct trunk_lb *lb = (struct trunk_lb *)tr->tr_psc;
free(lb, M_DEVBUF, sizeof *lb);
return (0);
}
int
trunk_lb_porttable(struct trunk_softc *tr, struct trunk_port *tp)
{
struct trunk_lb *lb = (struct trunk_lb *)tr->tr_psc;
struct trunk_port *tp_next;
int i = 0;
bzero(&lb->lb_ports, sizeof(lb->lb_ports));
SLIST_FOREACH(tp_next, &tr->tr_ports, tp_entries) {
if (tp_next == tp)
continue;
if (i >= TRUNK_MAX_PORTS)
return (EINVAL);
if (tr->tr_ifflags & IFF_DEBUG)
printf("%s: port %s at index %d\n",
tr->tr_ifname, tp_next->tp_ifname, i);
lb->lb_ports[i++] = tp_next;
}
return (0);
}
int
trunk_lb_port_create(struct trunk_port *tp)
{
struct trunk_softc *tr = (struct trunk_softc *)tp->tp_trunk;
return (trunk_lb_porttable(tr, NULL));
}
void
trunk_lb_port_destroy(struct trunk_port *tp)
{
struct trunk_softc *tr = (struct trunk_softc *)tp->tp_trunk;
trunk_lb_porttable(tr, tp);
}
int
trunk_lb_start(struct trunk_softc *tr, struct mbuf *m)
{
struct trunk_lb *lb = (struct trunk_lb *)tr->tr_psc;
struct trunk_port *tp = NULL;
u_int32_t p = 0;
p = trunk_hashmbuf(m, &lb->lb_key);
p %= tr->tr_count;
tp = lb->lb_ports[p];
/*
* Check the port's link state. This will return the next active
* port if the link is down or the port is NULL.
*/
if ((tp = trunk_link_active(tr, tp)) == NULL) {
m_freem(m);
return (ENOENT);
}
return (if_enqueue(tp->tp_if, m));
}
int
trunk_lb_input(struct trunk_softc *tr, struct trunk_port *tp, struct mbuf *m)
{
/* Just pass in the packet to our trunk device */
return (0);
}
/*
* Broadcast mode
*/
int
trunk_bcast_attach(struct trunk_softc *tr)
{
tr->tr_detach = trunk_bcast_detach;
tr->tr_start = trunk_bcast_start;
tr->tr_input = trunk_bcast_input;
tr->tr_init = NULL;
tr->tr_stop = NULL;
tr->tr_port_create = NULL;
tr->tr_port_destroy = NULL;
tr->tr_linkstate = NULL;
tr->tr_req = NULL;
tr->tr_portreq = NULL;
return (0);
}
int
trunk_bcast_detach(struct trunk_softc *tr)
{
return (0);
}
int
trunk_bcast_start(struct trunk_softc *tr, struct mbuf *m0)
{
int active_ports = 0;
int errors = 0;
struct trunk_port *tp, *last = NULL;
struct mbuf *m;
SLIST_FOREACH(tp, &tr->tr_ports, tp_entries) {
if (!TRUNK_PORTACTIVE(tp))
continue;
active_ports++;
if (last != NULL) {
m = m_copym(m0, 0, M_COPYALL, M_DONTWAIT);
if (m == NULL) {
errors++;
break;
}
if (if_enqueue(last->tp_if, m) != 0)
errors++;
}
last = tp;
}
if (last == NULL) {
m_freem(m0);
return (ENOENT);
}
if (if_enqueue(last->tp_if, m0) != 0)
errors++;
if (errors == active_ports)
return (ENOBUFS);
return (0);
}
int
trunk_bcast_input(struct trunk_softc *tr, struct trunk_port *tp, struct mbuf *m)
{
return (0);
}
/*
* 802.3ad LACP
*/
int
trunk_lacp_attach(struct trunk_softc *tr)
{
struct trunk_port *tp;
int error;
tr->tr_detach = trunk_lacp_detach;
tr->tr_port_create = lacp_port_create;
tr->tr_port_destroy = lacp_port_destroy;
tr->tr_linkstate = lacp_linkstate;
tr->tr_start = trunk_lacp_start;
tr->tr_input = trunk_lacp_input;
tr->tr_init = lacp_init;
tr->tr_stop = lacp_stop;
tr->tr_req = lacp_req;
tr->tr_portreq = lacp_portreq;
error = lacp_attach(tr);
if (error)
return (error);
SLIST_FOREACH(tp, &tr->tr_ports, tp_entries)
lacp_port_create(tp);
return (error);
}
int
trunk_lacp_detach(struct trunk_softc *tr)
{
struct trunk_port *tp;
int error;
SLIST_FOREACH(tp, &tr->tr_ports, tp_entries)
lacp_port_destroy(tp);
/* unlocking is safe here */
error = lacp_detach(tr);
return (error);
}
int
trunk_lacp_start(struct trunk_softc *tr, struct mbuf *m)
{
struct trunk_port *tp;
tp = lacp_select_tx_port(tr, m);
if (tp == NULL) {
m_freem(m);
return (EBUSY);
}
return (if_enqueue(tp->tp_if, m));
}
int
trunk_lacp_input(struct trunk_softc *tr, struct trunk_port *tp, struct mbuf *m)
{
return (lacp_input(tp, m));
}