123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571 |
- /* $OpenBSD: if_mpw.c,v 1.2 2015/07/21 04:58:52 rzalamena Exp $ */
- /*
- * Copyright (c) 2015 Rafael Zalamena <rzalamena@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 "bpfilter.h"
- #include "vlan.h"
- #include <sys/param.h>
- #include <sys/systm.h>
- #include <sys/mbuf.h>
- #include <sys/socket.h>
- #include <sys/ioctl.h>
- #include <sys/errno.h>
- #include <net/if.h>
- #include <net/if_types.h>
- #include <net/route.h>
- #include <netinet/in.h>
- #include <netinet/if_ether.h>
- #include <netmpls/mpls.h>
- #if NBPFILTER > 0
- #include <net/bpf.h>
- #endif /* NBPFILTER */
- #if NVLAN > 0
- #include <net/if_vlan_var.h>
- #endif
- struct mpw_softc {
- struct ifnet sc_if;
- struct ifaddr sc_ifa;
- struct sockaddr_mpls sc_smpls; /* Local label */
- uint32_t sc_flags;
- uint32_t sc_type;
- struct shim_hdr sc_rshim;
- struct sockaddr_storage sc_nexthop;
- };
- void mpwattach(int);
- int mpw_clone_create(struct if_clone *, int);
- int mpw_clone_destroy(struct ifnet *);
- int mpw_ioctl(struct ifnet *, u_long, caddr_t);
- int mpw_output(struct ifnet *, struct mbuf *, struct sockaddr *,
- struct rtentry *);
- void mpw_start(struct ifnet *);
- int mpw_input(struct ifnet *, struct mbuf *);
- #if NVLAN > 0
- struct mbuf *mpw_vlan_handle(struct mbuf *, struct mpw_softc *);
- #endif /* NVLAN */
- struct if_clone mpw_cloner =
- IF_CLONE_INITIALIZER("mpw", mpw_clone_create, mpw_clone_destroy);
- /* ARGSUSED */
- void
- mpwattach(int n)
- {
- if_clone_attach(&mpw_cloner);
- }
- int
- mpw_clone_create(struct if_clone *ifc, int unit)
- {
- struct mpw_softc *sc;
- struct ifnet *ifp;
- struct ifih *ifih;
- sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO);
- if (sc == NULL)
- return (ENOMEM);
- ifih = malloc(sizeof(*ifih), M_DEVBUF, M_NOWAIT | M_ZERO);
- if (ifih == NULL) {
- free(sc, M_DEVBUF, sizeof(*sc));
- return (ENOMEM);
- }
- ifp = &sc->sc_if;
- snprintf(ifp->if_xname, sizeof(ifp->if_xname), "mpw%d", unit);
- ifp->if_softc = sc;
- ifp->if_mtu = ETHERMTU;
- ifp->if_flags = IFF_POINTOPOINT;
- ifp->if_ioctl = mpw_ioctl;
- ifp->if_output = mpw_output;
- ifp->if_start = mpw_start;
- ifp->if_type = IFT_MPLSTUNNEL;
- ifp->if_hdrlen = ETHER_HDR_LEN;
- IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN);
- IFQ_SET_READY(&ifp->if_snd);
- if_attach(ifp);
- if_alloc_sadl(ifp);
- sc->sc_ifa.ifa_ifp = ifp;
- sc->sc_ifa.ifa_rtrequest = link_rtrequest;
- sc->sc_ifa.ifa_addr = (struct sockaddr *) ifp->if_sadl;
- sc->sc_smpls.smpls_len = sizeof(sc->sc_smpls);
- sc->sc_smpls.smpls_family = AF_MPLS;
- ifih->ifih_input = mpw_input;
- SLIST_INSERT_HEAD(&ifp->if_inputs, ifih, ifih_next);
- #if NBPFILTER > 0
- bpfattach(&ifp->if_bpf, ifp, DLT_EN10MB, ETHER_HDR_LEN);
- #endif /* NBFILTER */
- return (0);
- }
- int
- mpw_clone_destroy(struct ifnet *ifp)
- {
- struct mpw_softc *sc = ifp->if_softc;
- struct ifih *ifih = SLIST_FIRST(&ifp->if_inputs);
- int s;
- ifp->if_flags &= ~IFF_RUNNING;
- if (sc->sc_smpls.smpls_label) {
- s = splsoftnet();
- rt_ifa_del(&sc->sc_ifa, RTF_MPLS | RTF_UP,
- smplstosa(&sc->sc_smpls));
- splx(s);
- }
- SLIST_REMOVE(&ifp->if_inputs, ifih, ifih, ifih_next);
- free(ifih, M_DEVBUF, sizeof(*ifih));
- if_detach(ifp);
- free(sc, M_DEVBUF, sizeof(*sc));
- return (0);
- }
- int
- mpw_input(struct ifnet *ifp, struct mbuf *m)
- {
- /* Don't have local broadcast. */
- m_freem(m);
- return (1);
- }
- int
- mpw_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
- {
- struct ifreq *ifr = (struct ifreq *) data;
- struct mpw_softc *sc = ifp->if_softc;
- struct sockaddr_in *sin;
- struct sockaddr_in *sin_nexthop;
- int error = 0;
- int s;
- struct ifmpwreq imr;
- switch (cmd) {
- case SIOCSIFMTU:
- if (ifr->ifr_mtu < MPE_MTU_MIN ||
- ifr->ifr_mtu > MPE_MTU_MAX)
- error = EINVAL;
- else
- ifp->if_mtu = ifr->ifr_mtu;
- break;
- case SIOCSIFFLAGS:
- if ((ifp->if_flags & IFF_UP))
- ifp->if_flags |= IFF_RUNNING;
- else
- ifp->if_flags &= ~IFF_RUNNING;
- break;
- case SIOCSETMPWCFG:
- error = suser(curproc, 0);
- if (error != 0)
- break;
- error = copyin(ifr->ifr_data, &imr, sizeof(imr));
- if (error != 0)
- break;
- /* Teardown all configuration if got no nexthop */
- sin = (struct sockaddr_in *) &imr.imr_nexthop;
- if (sin->sin_addr.s_addr == 0) {
- s = splsoftnet();
- if (rt_ifa_del(&sc->sc_ifa, RTF_MPLS | RTF_UP,
- smplstosa(&sc->sc_smpls)) == 0)
- sc->sc_smpls.smpls_label = 0;
- splx(s);
- memset(&sc->sc_rshim, 0, sizeof(sc->sc_rshim));
- memset(&sc->sc_nexthop, 0, sizeof(sc->sc_nexthop));
- sc->sc_flags = 0;
- sc->sc_type = 0;
- break;
- }
- /* Validate input */
- if (sin->sin_family != AF_INET ||
- imr.imr_lshim.shim_label > MPLS_LABEL_MAX ||
- imr.imr_lshim.shim_label <= MPLS_LABEL_RESERVED_MAX ||
- imr.imr_rshim.shim_label > MPLS_LABEL_MAX ||
- imr.imr_rshim.shim_label <= MPLS_LABEL_RESERVED_MAX) {
- error = EINVAL;
- break;
- }
- /* Setup labels and create inbound route */
- imr.imr_lshim.shim_label =
- htonl(imr.imr_lshim.shim_label << MPLS_LABEL_OFFSET);
- imr.imr_rshim.shim_label =
- htonl(imr.imr_rshim.shim_label << MPLS_LABEL_OFFSET);
- if (sc->sc_smpls.smpls_label != imr.imr_lshim.shim_label) {
- s = splsoftnet();
- if (sc->sc_smpls.smpls_label)
- rt_ifa_del(&sc->sc_ifa, RTF_MPLS | RTF_UP,
- smplstosa(&sc->sc_smpls));
- sc->sc_smpls.smpls_label = imr.imr_lshim.shim_label;
- error = rt_ifa_add(&sc->sc_ifa, RTF_MPLS | RTF_UP,
- smplstosa(&sc->sc_smpls));
- splx(s);
- if (error != 0) {
- sc->sc_smpls.smpls_label = 0;
- break;
- }
- }
- /* Apply configuration */
- sc->sc_flags = imr.imr_flags;
- sc->sc_type = imr.imr_type;
- sc->sc_rshim.shim_label = imr.imr_rshim.shim_label;
- sc->sc_rshim.shim_label |= MPLS_BOS_MASK;
- memset(&sc->sc_nexthop, 0, sizeof(sc->sc_nexthop));
- sin_nexthop = (struct sockaddr_in *) &sc->sc_nexthop;
- sin_nexthop->sin_family = sin->sin_family;
- sin_nexthop->sin_len = sizeof(struct sockaddr_in);
- sin_nexthop->sin_addr.s_addr = sin->sin_addr.s_addr;
- break;
- case SIOCGETMPWCFG:
- imr.imr_flags = sc->sc_flags;
- imr.imr_type = sc->sc_type;
- imr.imr_lshim.shim_label =
- ((ntohl(sc->sc_smpls.smpls_label & MPLS_LABEL_MASK)) >>
- MPLS_LABEL_OFFSET);
- imr.imr_rshim.shim_label =
- ((ntohl(sc->sc_rshim.shim_label & MPLS_LABEL_MASK)) >>
- MPLS_LABEL_OFFSET);
- memcpy(&imr.imr_nexthop, &sc->sc_nexthop,
- sizeof(imr.imr_nexthop));
- error = copyout(&imr, ifr->ifr_data, sizeof(imr));
- break;
- default:
- error = ENOTTY;
- break;
- }
- return (error);
- }
- int
- mpw_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *sa,
- struct rtentry *rt)
- {
- struct mpw_softc *sc = ifp->if_softc;
- struct mbuf_list ml = MBUF_LIST_INITIALIZER();
- struct ether_header *eh, ehc;
- struct shim_hdr *shim;
- int s;
- if (sc->sc_type == IMR_TYPE_NONE) {
- m_freem(m);
- return (EHOSTUNREACH);
- }
- if (sc->sc_flags & IMR_FLAG_CONTROLWORD) {
- shim = mtod(m, struct shim_hdr *);
- m_adj(m, MPLS_HDRLEN);
- /*
- * The first 4 bits identifies that this packet is a
- * control word. If the control word is configured and
- * we received an IP datagram we shall drop it.
- */
- if (shim->shim_label & CW_ZERO_MASK) {
- ifp->if_ierrors++;
- m_freem(m);
- return (EINVAL);
- }
- /* We don't support fragmentation just yet. */
- if (shim->shim_label & CW_FRAG_MASK) {
- ifp->if_ierrors++;
- m_freem(m);
- return (EINVAL);
- }
- }
- if (sc->sc_type == IMR_TYPE_ETHERNET_TAGGED) {
- m_copydata(m, 0, sizeof(ehc), (caddr_t) &ehc);
- m_adj(m, ETHER_HDR_LEN);
- /* Ethernet tagged expects at least 2 VLANs */
- if (ntohs(ehc.ether_type) != ETHERTYPE_QINQ) {
- ifp->if_ierrors++;
- m_freem(m);
- return (EINVAL);
- }
- /* Remove dummy VLAN and update ethertype */
- if (EVL_VLANOFTAG(*mtod(m, uint16_t *)) == 0) {
- m_adj(m, EVL_ENCAPLEN);
- ehc.ether_type = htons(ETHERTYPE_VLAN);
- }
- M_PREPEND(m, sizeof(*eh), M_NOWAIT);
- if (m == NULL)
- return (ENOMEM);
- eh = mtod(m, struct ether_header *);
- memcpy(eh, &ehc, sizeof(*eh));
- }
- ml_enqueue(&ml, m);
- s = splnet();
- if_input(ifp, &ml);
- splx(s);
- return (0);
- }
- #if NVLAN > 0
- extern void vlan_start(struct ifnet *ifp);
- /*
- * This routine handles VLAN tag reinsertion in packets flowing through
- * the pseudowire. Also it does the necessary modifications to the VLANs
- * to respect the RFC.
- */
- struct mbuf *
- mpw_vlan_handle(struct mbuf *m, struct mpw_softc *sc)
- {
- int needsdummy = 0;
- int fakeifv = 0;
- struct ifvlan *ifv = NULL;
- struct ether_vlan_header *evh;
- struct ifnet *ifp, *ifp0;
- int nvlan, moff;
- struct ether_header eh;
- struct ifvlan fifv;
- struct vlan_shim {
- uint16_t vs_tpid;
- uint16_t vs_tci;
- } vs;
- ifp0 = ifp = if_get(m->m_pkthdr.ph_ifidx);
- KASSERT(ifp != NULL);
- if (ifp->if_start == vlan_start)
- ifv = ifp->if_softc;
- /* If we were relying on VLAN HW support, fake an ifv */
- if (ifv == NULL && (m->m_flags & M_VLANTAG) == M_VLANTAG) {
- memset(&fifv, 0, sizeof(fifv));
- fifv.ifv_tag = EVL_VLANOFTAG(m->m_pkthdr.ether_vtag);
- fifv.ifv_prio = EVL_PRIOFTAG(m->m_pkthdr.ether_vtag);
- ifv = &fifv;
- fakeifv = 1;
- }
- /*
- * Always remove VLAN flag as we are inserting them here. Also we
- * might get a tagged packet with no VLAN interface, in this case
- * we can't do anything.
- */
- m->m_flags &= ~M_VLANTAG;
- /*
- * Do VLAN managing.
- *
- * Case ethernet (raw):
- * No VLAN: just pass it.
- * One or more VLANs: insert VLAN tag back.
- *
- * NOTE: In case of raw access mode, the if_vlan will do the job
- * of dropping non tagged packets for us.
- */
- if (sc->sc_type == IMR_TYPE_ETHERNET && ifv == NULL)
- return (m);
- /*
- * Case ethernet-tagged:
- * 0 VLAN: Drop packet
- * 1 VLAN: Tag packet with dummy VLAN
- * >1 VLAN: Nothing
- */
- if (sc->sc_type == IMR_TYPE_ETHERNET_TAGGED && ifv == NULL) {
- m_freem(m);
- return (NULL);
- }
- /* Copy and remove ethernet header */
- m_copydata(m, 0, sizeof(eh), (caddr_t) &eh);
- if (ntohs(eh.ether_type) == ETHERTYPE_VLAN ||
- ntohs(eh.ether_type) == ETHERTYPE_QINQ)
- m_adj(m, sizeof(*evh));
- else
- m_adj(m, sizeof(eh));
- /* Count VLAN stack size */
- nvlan = 0;
- while ((ifp = ifv->ifv_p) != NULL && ifp->if_start == vlan_start) {
- ifv = ifp->if_softc;
- nvlan++;
- }
- moff = sizeof(*evh) + (nvlan * EVL_ENCAPLEN);
- /* The mode ethernet tagged always need at least 2 VLANs */
- if (sc->sc_type == IMR_TYPE_ETHERNET_TAGGED && nvlan == 0) {
- needsdummy = 1;
- moff += EVL_ENCAPLEN;
- }
- /* Add VLAN to the beginning of the packet */
- M_PREPEND(m, moff, M_NOWAIT);
- if (m == NULL)
- return (NULL);
- /* Copy original ethernet type */
- moff -= sizeof(eh.ether_type);
- m_copyback(m, moff, sizeof(eh.ether_type), &eh.ether_type, M_NOWAIT);
- /* Fill inner VLAN values */
- ifv = ifp0->if_softc;
- while (nvlan-- > 0) {
- vs.vs_tci = htons((ifv->ifv_prio << EVL_PRIO_BITS) +
- ifv->ifv_tag);
- vs.vs_tpid = htons(ifv->ifv_type);
- moff -= sizeof(vs);
- m_copyback(m, moff, sizeof(vs), &vs, M_NOWAIT);
- ifp = ifv->ifv_p;
- ifv = ifp->if_softc;
- }
- /* Copy ethernet header back */
- evh = mtod(m, struct ether_vlan_header *);
- memcpy(evh->evl_dhost, eh.ether_dhost, sizeof(evh->evl_dhost));
- memcpy(evh->evl_shost, eh.ether_shost, sizeof(evh->evl_shost));
- if (fakeifv)
- ifv = &fifv;
- /* Insert the last VLAN and optionally a dummy VLAN */
- if (needsdummy) {
- evh->evl_encap_proto = ntohs(ETHERTYPE_QINQ);
- evh->evl_tag = 0;
- vs.vs_tci = ntohs((m->m_pkthdr.pf.prio << EVL_PRIO_BITS) +
- ifv->ifv_tag);
- vs.vs_tpid = ntohs(ETHERTYPE_VLAN);
- m_copyback(m, moff, sizeof(vs), &vs, M_NOWAIT);
- } else {
- evh->evl_encap_proto = (nvlan > 0) ?
- ntohs(ETHERTYPE_QINQ) : ntohs(ETHERTYPE_VLAN);
- evh->evl_tag = ntohs((m->m_pkthdr.pf.prio << EVL_PRIO_BITS) +
- ifv->ifv_tag);
- }
- return (m);
- }
- #endif /* NVLAN */
- void
- mpw_start(struct ifnet *ifp)
- {
- struct mpw_softc *sc = ifp->if_softc;
- struct mbuf *m;
- struct rtentry *rt;
- struct shim_hdr *shim;
- struct sockaddr_storage ss;
- rt = rtalloc((struct sockaddr *) &sc->sc_nexthop,
- RT_REPORT | RT_RESOLVE, 0);
- if (rt == NULL)
- return;
- /*
- * XXX: lie about being MPLS, so mpls_output() get the TTL from
- * the right place.
- */
- memcpy(&ss, &sc->sc_nexthop, sizeof(sc->sc_nexthop));
- ((struct sockaddr *) &ss)->sa_family = AF_MPLS;
- for (;;) {
- IF_DEQUEUE(&ifp->if_snd, m);
- if (m == NULL)
- break;
- if ((ifp->if_flags & IFF_RUNNING) == 0 ||
- sc->sc_rshim.shim_label == 0 ||
- sc->sc_type == IMR_TYPE_NONE) {
- m_freem(m);
- continue;
- }
- #if NVLAN > 0
- m = mpw_vlan_handle(m, sc);
- if (m == NULL)
- continue;
- #else
- /* Ethernet tagged doesn't work without VLANs'*/
- if (sc->sc_type == IMR_TYPE_ETHERNET_TAGGED) {
- m_freem(m);
- continue;
- }
- #endif /* NVLAN */
- #if NBPFILTER > 0
- if (sc->sc_if.if_bpf)
- bpf_mtap(sc->sc_if.if_bpf, m, BPF_DIRECTION_OUT);
- #endif /* NBPFILTER */
- if (sc->sc_flags & IMR_FLAG_CONTROLWORD) {
- M_PREPEND(m, sizeof(*shim), M_NOWAIT);
- if (m == NULL)
- continue;
- shim = mtod(m, struct shim_hdr *);
- memset(shim, 0, sizeof(*shim));
- }
- M_PREPEND(m, sizeof(*shim), M_NOWAIT);
- if (m == NULL)
- continue;
- shim = mtod(m, struct shim_hdr *);
- shim->shim_label = htonl(mpls_defttl) & MPLS_TTL_MASK;
- shim->shim_label |= sc->sc_rshim.shim_label;
- /* XXX: MPLS only uses domain 0 */
- m->m_pkthdr.ph_rtableid = 0;
- mpls_output(rt->rt_ifp, m, (struct sockaddr *) &ss, rt);
- }
- rtfree(rt);
- }
|