/* * ng_fec.c * * Copyright (c) 2001 Berkeley Software Design, Inc. * Copyright (c) 2000, 2001 * Bill Paul . All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Bill Paul. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. * * $FreeBSD: src/sys/netgraph/ng_fec.c,v 1.1.2.1 2002/11/01 21:39:31 julian Exp $ */ /* * Copyright (c) 1996-1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Archie Cobbs * * $Whistle: ng_fec.c,v 1.33 1999/11/01 09:24:51 julian Exp $ */ /* * This module implements ethernet channel bonding using the Cisco * Fast EtherChannel mechanism. Two or four ports may be combined * into a single aggregate interface. * * Interfaces are named fec0, fec1, etc. New nodes take the * first available interface name. * * This node also includes Berkeley packet filter support. * * Note that this node doesn't need to connect to any other * netgraph nodes in order to do its work. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "opt_inet.h" #include "opt_inet6.h" #include #ifdef INET #include #include #endif #ifdef INET6 #include #endif #include #include #include #include "ng_fec.h" #define IFP2NG(ifp) ((struct ng_node *)((struct arpcom *)(ifp))->ac_netgraph) #define FEC_INC(x, y) (x) = (x + 1) % y /* * Current fast etherchannel implementations use either 2 or 4 * ports, so for now we limit the maximum bundle size to 4 interfaces. */ #define FEC_BUNDLESIZ 4 struct ng_fec_portlist { struct ifnet *fec_if; int fec_idx; int fec_ifstat; struct ether_addr fec_mac; TAILQ_ENTRY(ng_fec_portlist) fec_list; }; struct ng_fec_bundle { TAILQ_HEAD(,ng_fec_portlist) ng_fec_ports; int fec_ifcnt; int fec_btype; }; #define FEC_BTYPE_MAC 0x01 #define FEC_BTYPE_INET 0x02 #define FEC_BTYPE_INET6 0x03 /* Node private data */ struct ng_fec_private { struct arpcom arpcom; struct ifmedia ifmedia; int if_flags; int if_error; /* XXX */ int unit; /* Interface unit number */ node_p node; /* Our netgraph node */ struct ng_fec_bundle fec_bundle;/* Aggregate bundle */ struct callout fec_timeout; /* callout for ticker */ int (*real_if_output)(struct ifnet *, struct mbuf *, struct sockaddr *, struct rtentry *); }; typedef struct ng_fec_private *priv_p; /* Interface methods */ static void ng_fec_input(struct ifnet *, struct mbuf **); static void ng_fec_start(struct ifnet *ifp, struct ifaltq_subque *); static int ng_fec_choose_port(struct ng_fec_bundle *b, struct mbuf *m, struct ifnet **ifp); static int ng_fec_setport(struct ifnet *ifp, u_long cmd, caddr_t data); static void ng_fec_init(void *arg); static void ng_fec_stop(struct ifnet *ifp); static int ng_fec_ifmedia_upd(struct ifnet *ifp); static void ng_fec_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr); static int ng_fec_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data, struct ucred *); static int ng_fec_output(struct ifnet *ifp, struct mbuf *m0, struct sockaddr *dst, struct rtentry *rt0); static void ng_fec_tick(void *arg); static int ng_fec_addport(struct ng_fec_private *priv, char *iface); static int ng_fec_delport(struct ng_fec_private *priv, char *iface); #ifdef DEBUG static void ng_fec_print_ioctl(struct ifnet *ifp, int cmd, caddr_t data); #endif /* Netgraph methods */ static ng_constructor_t ng_fec_constructor; static ng_rcvmsg_t ng_fec_rcvmsg; static ng_shutdown_t ng_fec_rmnode; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_fec_cmds[] = { { NGM_FEC_COOKIE, NGM_FEC_ADD_IFACE, "add_iface", &ng_parse_string_type, NULL, }, { NGM_FEC_COOKIE, NGM_FEC_DEL_IFACE, "del_iface", &ng_parse_string_type, NULL, }, { NGM_FEC_COOKIE, NGM_FEC_SET_MODE_MAC, "set_mode_mac", NULL, NULL, }, { NGM_FEC_COOKIE, NGM_FEC_SET_MODE_INET, "set_mode_inet", NULL, NULL, }, { 0 } }; /* Node type descriptor */ static struct ng_type typestruct = { NG_VERSION, NG_FEC_NODE_TYPE, NULL, ng_fec_constructor, ng_fec_rcvmsg, ng_fec_rmnode, NULL, NULL, NULL, NULL, NULL, NULL, ng_fec_cmds }; NETGRAPH_INIT(fec, &typestruct); /* We keep a bitmap indicating which unit numbers are free. One means the unit number is free, zero means it's taken. */ static int *ng_fec_units = NULL; static int ng_fec_units_len = 0; static int ng_units_in_use = 0; #define UNITS_BITSPERWORD (sizeof(*ng_fec_units) * NBBY) /* * Find the first free unit number for a new interface. * Increase the size of the unit bitmap as necessary. */ static __inline__ int ng_fec_get_unit(int *unit) { int index, bit; for (index = 0; index < ng_fec_units_len && ng_fec_units[index] == 0; index++); if (index == ng_fec_units_len) { /* extend array */ int i, *newarray, newlen; newlen = (2 * ng_fec_units_len) + 4; newarray = kmalloc(newlen * sizeof(*ng_fec_units), M_NETGRAPH, M_NOWAIT); if (newarray == NULL) return (ENOMEM); bcopy(ng_fec_units, newarray, ng_fec_units_len * sizeof(*ng_fec_units)); for (i = ng_fec_units_len; i < newlen; i++) newarray[i] = ~0; if (ng_fec_units != NULL) kfree(ng_fec_units, M_NETGRAPH); ng_fec_units = newarray; ng_fec_units_len = newlen; } bit = ffs(ng_fec_units[index]) - 1; KASSERT(bit >= 0 && bit <= UNITS_BITSPERWORD - 1, ("%s: word=%d bit=%d", __func__, ng_fec_units[index], bit)); ng_fec_units[index] &= ~(1 << bit); *unit = (index * UNITS_BITSPERWORD) + bit; ng_units_in_use++; return (0); } /* * Free a no longer needed unit number. */ static __inline__ void ng_fec_free_unit(int unit) { int index, bit; index = unit / UNITS_BITSPERWORD; bit = unit % UNITS_BITSPERWORD; KASSERT(index < ng_fec_units_len, ("%s: unit=%d len=%d", __func__, unit, ng_fec_units_len)); KASSERT((ng_fec_units[index] & (1 << bit)) == 0, ("%s: unit=%d is free", __func__, unit)); ng_fec_units[index] |= (1 << bit); /* * XXX We could think about reducing the size of ng_fec_units[] * XXX here if the last portion is all ones * XXX At least free it if no more units. * Needed if we are eventually be able to unload. */ ng_units_in_use++; if (ng_units_in_use == 0) { /* XXX make SMP safe */ kfree(ng_fec_units, M_NETGRAPH); ng_fec_units_len = 0; ng_fec_units = NULL; } } /************************************************************************ INTERFACE STUFF ************************************************************************/ static int ng_fec_addport(struct ng_fec_private *priv, char *iface) { struct ng_fec_bundle *b; struct ifnet *ifp, *bifp; struct arpcom *ac; struct sockaddr_dl *sdl; struct ng_fec_portlist *p, *new; if (priv == NULL || iface == NULL) return(EINVAL); b = &priv->fec_bundle; ifp = &priv->arpcom.ac_if; ifnet_lock(); /* Find the interface */ bifp = ifunit(iface); if (bifp == NULL) { ifnet_unlock(); kprintf("fec%d: tried to add iface %s, which " "doesn't seem to exist\n", priv->unit, iface); return(ENOENT); } /* See if we have room in the bundle */ if (b->fec_ifcnt == FEC_BUNDLESIZ) { ifnet_unlock(); kprintf("fec%d: can't add new iface; bundle is full\n", priv->unit); return(ENOSPC); } /* See if the interface is already in the bundle */ TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { if (p->fec_if == bifp) { ifnet_unlock(); kprintf("fec%d: iface %s is already in this " "bundle\n", priv->unit, iface); return(EINVAL); } } /* Allocate new list entry. */ new = kmalloc(sizeof(struct ng_fec_portlist), M_NETGRAPH, M_NOWAIT); if (new == NULL) { ifnet_unlock(); return(ENOMEM); } ac = (struct arpcom *)bifp; ac->ac_netgraph = priv->node; /* * If this is the first interface added to the bundle, * use its MAC address for the virtual interface (and, * by extension, all the other ports in the bundle). */ if (b->fec_ifcnt == 0) { sdl = IF_LLSOCKADDR(ifp); bcopy((char *)ac->ac_enaddr, priv->arpcom.ac_enaddr, ETHER_ADDR_LEN); bcopy((char *)ac->ac_enaddr, LLADDR(sdl), ETHER_ADDR_LEN); } b->fec_btype = FEC_BTYPE_MAC; new->fec_idx = b->fec_ifcnt; b->fec_ifcnt++; /* Save the real MAC address. */ bcopy((char *)ac->ac_enaddr, (char *)&new->fec_mac, ETHER_ADDR_LEN); /* Set up phony MAC address. */ sdl = IF_LLSOCKADDR(bifp); bcopy(priv->arpcom.ac_enaddr, ac->ac_enaddr, ETHER_ADDR_LEN); bcopy(priv->arpcom.ac_enaddr, LLADDR(sdl), ETHER_ADDR_LEN); /* Add to the queue */ new->fec_if = bifp; TAILQ_INSERT_TAIL(&b->ng_fec_ports, new, fec_list); ifnet_unlock(); return(0); } static int ng_fec_delport(struct ng_fec_private *priv, char *iface) { struct ng_fec_bundle *b; struct ifnet *bifp; struct arpcom *ac; struct sockaddr_dl *sdl; struct ng_fec_portlist *p; if (priv == NULL || iface == NULL) return(EINVAL); b = &priv->fec_bundle; ifnet_lock(); /* Find the interface */ bifp = ifunit(iface); if (bifp == NULL) { ifnet_unlock(); kprintf("fec%d: tried to remove iface %s, which " "doesn't seem to exist\n", priv->unit, iface); return(ENOENT); } TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { if (p->fec_if == bifp) break; } if (p == NULL) { ifnet_unlock(); kprintf("fec%d: tried to remove iface %s which " "is not in our bundle\n", priv->unit, iface); return(EINVAL); } /* Stop interface */ bifp->if_flags &= ~IFF_UP; bifp->if_ioctl(bifp, SIOCSIFFLAGS, NULL, NULL); /* Restore MAC address. */ ac = (struct arpcom *)bifp; sdl = IF_LLSOCKADDR(bifp); bcopy((char *)&p->fec_mac, ac->ac_enaddr, ETHER_ADDR_LEN); bcopy((char *)&p->fec_mac, LLADDR(sdl), ETHER_ADDR_LEN); /* Delete port */ TAILQ_REMOVE(&b->ng_fec_ports, p, fec_list); kfree(p, M_NETGRAPH); b->fec_ifcnt--; ifnet_unlock(); return(0); } /* * Pass an ioctl command down to all the underyling interfaces in a * bundle. Used for setting multicast filters and flags. */ static int ng_fec_setport(struct ifnet *ifp, u_long command, caddr_t data) { struct ng_fec_private *priv; struct ng_fec_bundle *b; struct ifnet *oifp; struct ng_fec_portlist *p; priv = ifp->if_softc; b = &priv->fec_bundle; ifnet_deserialize_all(ifp); /* XXX */ TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { oifp = p->fec_if; if (oifp != NULL) { ifnet_serialize_all(oifp); oifp->if_ioctl(oifp, command, data, NULL); ifnet_deserialize_all(oifp); } } ifnet_serialize_all(ifp); return(0); } static void ng_fec_init(void *arg) { struct ng_fec_private *priv; struct ng_fec_bundle *b; struct ifnet *ifp, *bifp; struct ng_fec_portlist *p; ifp = arg; priv = ifp->if_softc; b = &priv->fec_bundle; if (b->fec_ifcnt == 1 || b->fec_ifcnt == 3) { kprintf("fec%d: invalid bundle " "size: %d\n", priv->unit, b->fec_ifcnt); return; } ng_fec_stop(ifp); ifnet_deserialize_all(ifp); /* XXX */ TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { bifp = p->fec_if; ifnet_serialize_all(bifp); bifp->if_flags |= IFF_UP; bifp->if_ioctl(bifp, SIOCSIFFLAGS, NULL, NULL); /* mark iface as up and let the monitor check it */ p->fec_ifstat = -1; ifnet_deserialize_all(bifp); } ifnet_serialize_all(ifp); callout_reset(&priv->fec_timeout, hz, ng_fec_tick, priv); } static void ng_fec_stop(struct ifnet *ifp) { struct ng_fec_private *priv; struct ng_fec_bundle *b; struct ifnet *bifp; struct ng_fec_portlist *p; priv = ifp->if_softc; b = &priv->fec_bundle; ifnet_deserialize_all(ifp); /* XXX */ TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { bifp = p->fec_if; ifnet_serialize_all(bifp); bifp->if_flags &= ~IFF_UP; bifp->if_ioctl(bifp, SIOCSIFFLAGS, NULL, NULL); ifnet_deserialize_all(bifp); } ifnet_serialize_all(ifp); callout_stop(&priv->fec_timeout); } static void ng_fec_tick(void *arg) { struct ng_fec_private *priv; struct ng_fec_bundle *b; struct ifmediareq ifmr; struct ifnet *ifp; struct ng_fec_portlist *p; int error = 0; priv = arg; b = &priv->fec_bundle; /* * Note: serializer for parent interface not held on entry, and * cannot be held during the loop to avoid a deadlock. */ TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { bzero((char *)&ifmr, sizeof(ifmr)); ifp = p->fec_if; ifnet_serialize_all(ifp); error = ifp->if_ioctl(ifp, SIOCGIFMEDIA, (caddr_t)&ifmr, NULL); if (error) { kprintf("fec%d: failed to check status " "of link %s\n", priv->unit, ifp->if_xname); ifnet_deserialize_all(ifp); continue; } if (ifmr.ifm_status & IFM_AVALID && IFM_TYPE(ifmr.ifm_active) == IFM_ETHER) { if (ifmr.ifm_status & IFM_ACTIVE) { if (p->fec_ifstat == -1 || p->fec_ifstat == 0) { p->fec_ifstat = 1; kprintf("fec%d: port %s in bundle " "is up\n", priv->unit, ifp->if_xname); } } else { if (p->fec_ifstat == -1 || p->fec_ifstat == 1) { p->fec_ifstat = 0; kprintf("fec%d: port %s in bundle " "is down\n", priv->unit, ifp->if_xname); } } } ifnet_deserialize_all(ifp); } ifp = &priv->arpcom.ac_if; if (ifp->if_flags & IFF_RUNNING) callout_reset(&priv->fec_timeout, hz, ng_fec_tick, priv); } static int ng_fec_ifmedia_upd(struct ifnet *ifp) { return(0); } static void ng_fec_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { struct ng_fec_private *priv; struct ng_fec_bundle *b; struct ng_fec_portlist *p; priv = ifp->if_softc; b = &priv->fec_bundle; ifmr->ifm_status = IFM_AVALID; TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { if (p->fec_ifstat) { ifmr->ifm_status |= IFM_ACTIVE; break; } } } /* * Process an ioctl for the virtual interface */ static int ng_fec_ioctl(struct ifnet *ifp, u_long command, caddr_t data, struct ucred *cr) { struct ifreq *const ifr = (struct ifreq *) data; int error = 0; struct ng_fec_private *priv; struct ng_fec_bundle *b; priv = ifp->if_softc; b = &priv->fec_bundle; #ifdef DEBUG ng_fec_print_ioctl(ifp, command, data); #endif crit_enter(); switch (command) { /* These two are mostly handled at a higher layer */ case SIOCSIFADDR: case SIOCGIFADDR: case SIOCSIFMTU: error = ether_ioctl(ifp, command, data); break; /* Set flags */ case SIOCSIFFLAGS: /* * If the interface is marked up and stopped, then start it. * If it is marked down and running, then stop it. */ if (ifr->ifr_flags & IFF_UP) { if (!(ifp->if_flags & IFF_RUNNING)) { /* Sanity. */ if (b->fec_ifcnt == 1 || b->fec_ifcnt == 3) { kprintf("fec%d: invalid bundle " "size: %d\n", priv->unit, b->fec_ifcnt); error = EINVAL; break; } ifq_clr_oactive(&ifp->if_snd); ifp->if_flags |= IFF_RUNNING; ng_fec_init(ifp); } /* * Bubble down changes in promisc mode to * underlying interfaces. */ if ((ifp->if_flags & IFF_PROMISC) != (priv->if_flags & IFF_PROMISC)) { ng_fec_setport(ifp, command, data); priv->if_flags = ifp->if_flags; } } else { if (ifp->if_flags & IFF_RUNNING) { ifp->if_flags &= ~IFF_RUNNING; ifq_clr_oactive(&ifp->if_snd); } ng_fec_stop(ifp); } break; case SIOCADDMULTI: case SIOCDELMULTI: ng_fec_setport(ifp, command, data); error = 0; break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: error = ifmedia_ioctl(ifp, ifr, &priv->ifmedia, command); break; /* Stuff that's not supported */ case SIOCSIFPHYS: error = EOPNOTSUPP; break; default: error = EINVAL; break; } crit_exit(); return (error); } /* * This routine spies on mbufs passing through ether_input(). If * they come from one of the interfaces that are aggregated into * our bundle, we fix up the ifnet pointer and increment our * packet counters so that it looks like the frames are actually * coming from us. */ static void ng_fec_input(struct ifnet *ifp, struct mbuf **m0) { struct ng_node *node; struct ng_fec_private *priv; struct ng_fec_bundle *b; struct mbuf *m; struct ifnet *bifp; struct ng_fec_portlist *p; /* Sanity check */ if (ifp == NULL || m0 == NULL) return; node = IFP2NG(ifp); /* Sanity check part II */ if (node == NULL) return; priv = node->private; b = &priv->fec_bundle; bifp = &priv->arpcom.ac_if; m = *m0; TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { if (p->fec_if == m->m_pkthdr.rcvif) break; } /* Wasn't meant for us; leave this frame alone. */ if (p == NULL) return; /* Pretend this is our frame. */ m->m_pkthdr.rcvif = bifp; IFNET_STAT_INC(bifp, ipackets, 1); IFNET_STAT_INC(bifp, ibytes, m->m_pkthdr.len); if (bifp->if_bpf) { bpf_gettoken(); if (bifp->if_bpf) bpf_mtap(bifp->if_bpf, m); bpf_reltoken(); } } /* * Take a quick peek at the packet and see if it's ok for us to use * the inet or inet6 hash methods on it, if they're enabled. We do * this by setting flags in the mbuf header. Once we've made up our * mind what to do, we pass the frame to ether_output() for further * processing. */ static int ng_fec_output_serialized(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst, struct rtentry *rt0) { const priv_p priv = (priv_p) ifp->if_softc; struct ng_fec_bundle *b; int error; /* Check interface flags */ if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING)) { m_freem(m); return (ENETDOWN); } b = &priv->fec_bundle; switch (b->fec_btype) { case FEC_BTYPE_MAC: m->m_flags |= M_FEC_MAC; break; #ifdef INET case FEC_BTYPE_INET: /* * We can't use the INET address port selection * scheme if this isn't an INET packet. */ if (dst->sa_family == AF_INET) m->m_flags |= M_FEC_INET; #ifdef INET6 else if (dst->sa_family == AF_INET6) m->m_flags |= M_FEC_INET6; #endif else { #ifdef DEBUG kprintf("%s: can't do inet aggregation of non " "inet packet\n", ifp->if_xname); #endif m->m_flags |= M_FEC_MAC; } break; #endif default: kprintf("%s: bogus hash type: %d\n", ifp->if_xname, b->fec_btype); m_freem(m); return(EINVAL); break; } /* * Pass the frame to ether_output() for all the protocol * handling. This will put the ethernet header on the packet * for us. */ priv->if_error = 0; error = priv->real_if_output(ifp, m, dst, rt0); if (priv->if_error && !error) error = priv->if_error; return(error); } static int ng_fec_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst, struct rtentry *rt0) { struct ifaltq_subque *ifsq = ifq_get_subq_default(&ifp->if_snd); int error; ifsq_serialize_hw(ifsq); error = ng_fec_output_serialized(ifp, m, dst, rt0); ifsq_deserialize_hw(ifsq); return error; } /* * Apply a hash to the source and destination addresses in the packet * in order to select an interface. Also check link status and handle * dead links accordingly. */ static int ng_fec_choose_port(struct ng_fec_bundle *b, struct mbuf *m, struct ifnet **ifp) { struct ether_header *eh; struct mbuf *m0; #ifdef INET struct ip *ip; #ifdef INET6 struct ip6_hdr *ip6; #endif #endif struct ng_fec_portlist *p; int port = 0, mask; /* * If there are only two ports, mask off all but the * last bit for XORing. If there are 4, mask off all * but the last 2 bits. */ mask = b->fec_ifcnt == 2 ? 0x1 : 0x3; eh = mtod(m, struct ether_header *); #ifdef INET ip = (struct ip *)(mtod(m, char *) + sizeof(struct ether_header)); #ifdef INET6 ip6 = (struct ip6_hdr *)(mtod(m, char *) + sizeof(struct ether_header)); #endif #endif /* * The fg_fec_output() routine is supposed to leave a * flag for us in the mbuf that tells us what hash to * use, but sometimes a new mbuf is prepended to the * chain, so we have to search every mbuf in the chain * to find the flags. */ m0 = m; while (m0) { if (m0->m_flags & (M_FEC_MAC|M_FEC_INET|M_FEC_INET6)) break; m0 = m0->m_next; } if (m0 == NULL) return(EINVAL); switch (m0->m_flags & (M_FEC_MAC|M_FEC_INET|M_FEC_INET6)) { case M_FEC_MAC: port = (eh->ether_dhost[5] ^ eh->ether_shost[5]) & mask; break; #ifdef INET case M_FEC_INET: port = (ntohl(ip->ip_dst.s_addr) ^ ntohl(ip->ip_src.s_addr)) & mask; break; #ifdef INET6 case M_FEC_INET6: port = (ip6->ip6_dst.s6_addr[15] ^ ip6->ip6_src.s6_addr[15]) & mask; break; #endif #endif default: return(EINVAL); break; } TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { if (port == p->fec_idx) break; } /* * Now that we've chosen a port, make sure it's * alive. If it's not alive, cycle through the bundle * looking for a port that is alive. If we don't find * any, return an error. */ if (p->fec_ifstat != 1) { struct ng_fec_portlist *n = NULL; n = TAILQ_NEXT(p, fec_list); if (n == NULL) n = TAILQ_FIRST(&b->ng_fec_ports); while (n != p) { if (n->fec_ifstat == 1) break; n = TAILQ_NEXT(n, fec_list); if (n == NULL) n = TAILQ_FIRST(&b->ng_fec_ports); } if (n == p) return(EAGAIN); p = n; } *ifp = p->fec_if; return(0); } /* * Now that the packet has been run through ether_output(), yank it * off our own send queue and stick it on the queue for the appropriate * underlying physical interface. Note that if the interface's send * queue is full, we save an error status in our private netgraph * space which will eventually be handed up to ng_fec_output(), which * will return it to the rest of the IP stack. We need to do this * in order to duplicate the effect of ether_output() returning ENOBUFS * when it detects that an interface's send queue is full. There's no * other way to signal the error status from here since the if_start() * routine is spec'ed to return void. * * Once the frame is queued, we call ether_output_frame() to initiate * transmission. */ static void ng_fec_start(struct ifnet *ifp, struct ifaltq_subque *ifsq __unused) { struct ng_fec_private *priv; struct ng_fec_bundle *b; struct ifnet *oifp = NULL; struct mbuf *m0; int error; priv = ifp->if_softc; b = &priv->fec_bundle; m0 = ifq_dequeue(&ifp->if_snd); if (m0 == NULL) return; BPF_MTAP(ifp, m0); /* Queue up packet on the proper port. */ error = ng_fec_choose_port(b, m0, &oifp); if (error) { IFNET_STAT_INC(ifp, ierrors, 1); m_freem(m0); priv->if_error = ENOBUFS; return; } IFNET_STAT_INC(ifp, opackets, 1); /* * Release current iface's serializer to avoid possible dead lock */ priv->if_error = ether_output_frame(oifp, m0); } #ifdef DEBUG /* * Display an ioctl to the virtual interface */ static void ng_fec_print_ioctl(struct ifnet *ifp, int command, caddr_t data) { char *str; switch (command & IOC_DIRMASK) { case IOC_VOID: str = "IO"; break; case IOC_OUT: str = "IOR"; break; case IOC_IN: str = "IOW"; break; case IOC_INOUT: str = "IORW"; break; default: str = "IO??"; } log(LOG_DEBUG, "%s: %s('%c', %d, char[%d])\n", ifp->if_xname, str, IOCGROUP(command), command & 0xff, IOCPARM_LEN(command)); } #endif /* DEBUG */ /************************************************************************ NETGRAPH NODE STUFF ************************************************************************/ /* * Constructor for a node */ static int ng_fec_constructor(node_p *nodep) { char ifname[NG_FEC_FEC_NAME_MAX + 1]; struct ifnet *ifp; node_p node; priv_p priv; struct ng_fec_bundle *b; int error = 0; /* Allocate node and interface private structures */ priv = kmalloc(sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); if (priv == NULL) return (ENOMEM); ifp = &priv->arpcom.ac_if; b = &priv->fec_bundle; /* Link them together */ ifp->if_softc = priv; /* Get an interface unit number */ if ((error = ng_fec_get_unit(&priv->unit)) != 0) { kfree(ifp, M_NETGRAPH); kfree(priv, M_NETGRAPH); return (error); } /* Call generic node constructor */ if ((error = ng_make_node_common(&typestruct, nodep)) != 0) { ng_fec_free_unit(priv->unit); kfree(ifp, M_NETGRAPH); kfree(priv, M_NETGRAPH); return (error); } node = *nodep; /* Link together node and private info */ node->private = priv; priv->node = node; priv->arpcom.ac_netgraph = node; /* Initialize interface structure */ if_initname(ifp, NG_FEC_FEC_NAME, priv->unit); ifp->if_start = ng_fec_start; ifp->if_ioctl = ng_fec_ioctl; ifp->if_init = ng_fec_init; ifp->if_watchdog = NULL; ifq_set_maxlen(&ifp->if_snd, IFQ_MAXLEN); ifp->if_mtu = NG_FEC_MTU_DEFAULT; ifp->if_flags = (IFF_SIMPLEX|IFF_BROADCAST|IFF_MULTICAST); ifp->if_type = IFT_PROPVIRTUAL; /* XXX */ ifp->if_addrlen = 0; /* XXX */ ifp->if_hdrlen = 0; /* XXX */ ifp->if_baudrate = 100000000; /* XXX */ /* Give this node the same name as the interface (if possible) */ bzero(ifname, sizeof(ifname)); strlcpy(ifname, ifp->if_xname, sizeof(ifname)); if (ng_name_node(node, ifname) != 0) log(LOG_WARNING, "%s: can't acquire netgraph name\n", ifname); /* Grab hold of the ether_input pipe. */ if (ng_ether_input_p == NULL) ng_ether_input_p = ng_fec_input; /* Attach the interface */ ether_ifattach(ifp, priv->arpcom.ac_enaddr, NULL); priv->real_if_output = ifp->if_output; ifp->if_output = ng_fec_output; callout_init(&priv->fec_timeout); TAILQ_INIT(&b->ng_fec_ports); b->fec_ifcnt = 0; ifmedia_init(&priv->ifmedia, 0, ng_fec_ifmedia_upd, ng_fec_ifmedia_sts); ifmedia_add(&priv->ifmedia, IFM_ETHER|IFM_NONE, 0, NULL); ifmedia_set(&priv->ifmedia, IFM_ETHER|IFM_NONE); /* Done */ return (0); } /* * Receive a control message */ static int ng_fec_rcvmsg(node_p node, struct ng_mesg *msg, const char *retaddr, struct ng_mesg **rptr) { const priv_p priv = node->private; struct ng_fec_bundle *b; struct ng_mesg *resp = NULL; char *ifname; int error = 0; b = &priv->fec_bundle; switch (msg->header.typecookie) { case NGM_FEC_COOKIE: switch (msg->header.cmd) { case NGM_FEC_ADD_IFACE: ifname = msg->data; error = ng_fec_addport(priv, ifname); break; case NGM_FEC_DEL_IFACE: ifname = msg->data; error = ng_fec_delport(priv, ifname); break; case NGM_FEC_SET_MODE_MAC: b->fec_btype = FEC_BTYPE_MAC; break; #ifdef INET case NGM_FEC_SET_MODE_INET: b->fec_btype = FEC_BTYPE_INET; break; #ifdef INET6 case NGM_FEC_SET_MODE_INET6: b->fec_btype = FEC_BTYPE_INET6; break; #endif #endif default: error = EINVAL; break; } break; default: error = EINVAL; break; } if (rptr) *rptr = resp; else if (resp) kfree(resp, M_NETGRAPH); kfree(msg, M_NETGRAPH); return (error); } /* * Shutdown and remove the node and its associated interface. */ static int ng_fec_rmnode(node_p node) { const priv_p priv = node->private; struct ng_fec_bundle *b; struct ng_fec_portlist *p; char ifname[IFNAMSIZ]; b = &priv->fec_bundle; ng_fec_stop(&priv->arpcom.ac_if); while (!TAILQ_EMPTY(&b->ng_fec_ports)) { p = TAILQ_FIRST(&b->ng_fec_ports); ksprintf(ifname, "%s", p->fec_if->if_xname); /* XXX: strings */ ng_fec_delport(priv, ifname); } ng_cutlinks(node); ng_unname(node); if (ng_ether_input_p != NULL) ng_ether_input_p = NULL; ether_ifdetach(&priv->arpcom.ac_if); ifmedia_removeall(&priv->ifmedia); ng_fec_free_unit(priv->unit); kfree(priv, M_NETGRAPH); node->private = NULL; ng_unref(node); return (0); }