-/* $OpenBSD: if_gre.c,v 1.93 2018/02/08 05:00:37 dlg Exp $ */
+/* $OpenBSD: if_gre.c,v 1.94 2018/02/08 22:08:18 dlg Exp $ */
/* $NetBSD: if_gre.c,v 1.9 1999/10/25 19:18:11 drochner Exp $ */
/*
static int
gre_ip_output(const struct gre_tunnel *, struct mbuf *,
uint8_t);
+
+static int gre_tunnel_ioctl(struct ifnet *, struct gre_tunnel *,
+ u_long, void *);
+
/*
* layer 3 GRE tunnels
*/
struct gre_tunnel *);
static struct mbuf *
- gre_encap(struct gre_softc *, struct mbuf *, uint8_t *);
+ gre_encap(struct gre_tunnel *, struct mbuf *, uint16_t);
static void gre_keepalive_send(void *);
static void gre_keepalive_recv(struct ifnet *ifp, struct mbuf *);
static void gre_keepalive_hold(void *);
+/*
+ * Ethernet GRE tunnels
+ */
+
+struct egre_softc {
+ struct gre_tunnel sc_tunnel; /* must be first */
+ struct arpcom sc_ac;
+ struct ifmedia sc_media;
+};
+
+static int egre_clone_create(struct if_clone *, int);
+static int egre_clone_destroy(struct ifnet *);
+
+static void egre_start(struct ifnet *);
+static int egre_ioctl(struct ifnet *, u_long, caddr_t);
+static int egre_media_change(struct ifnet *);
+static void egre_media_status(struct ifnet *, struct ifmediareq *);
+
+static int egre_up(struct egre_softc *);
+static int egre_down(struct egre_softc *);
+
+static int egre_input(const struct gre_tunnel *, struct mbuf *, int);
+
+struct if_clone egre_cloner =
+ IF_CLONE_INITIALIZER("egre", egre_clone_create, egre_clone_destroy);
+
+struct gre_tree egre_softcs = RBT_INITIALIZER();
/*
* It is not easy to calculate the right value for a GRE MTU.
greattach(int n)
{
if_clone_attach(&gre_cloner);
+ if_clone_attach(&egre_cloner);
}
static int
return (0);
}
+static int
+egre_clone_create(struct if_clone *ifc, int unit)
+{
+ struct egre_softc *sc;
+ struct ifnet *ifp;
+
+ sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK|M_ZERO);
+ ifp = &sc->sc_ac.ac_if;
+
+ snprintf(ifp->if_xname, sizeof(ifp->if_xname), "%s%d",
+ ifc->ifc_name, unit);
+
+ ifp->if_softc = sc;
+ ifp->if_mtu = 1500; /* XXX */
+ ifp->if_ioctl = egre_ioctl;
+ ifp->if_start = egre_start;
+ ifp->if_xflags = IFXF_CLONED;
+ IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN);
+ ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
+ ether_fakeaddr(ifp);
+
+ sc->sc_tunnel.t_ttl = ip_defttl;
+
+ ifmedia_init(&sc->sc_media, 0, egre_media_change, egre_media_status);
+ ifmedia_add(&sc->sc_media, IFM_ETHER | IFM_AUTO, 0, NULL);
+ ifmedia_set(&sc->sc_media, IFM_ETHER | IFM_AUTO);
+
+ if_attach(ifp);
+ ether_ifattach(ifp);
+
+ return (0);
+}
+
+static int
+egre_clone_destroy(struct ifnet *ifp)
+{
+ struct egre_softc *sc = ifp->if_softc;
+
+ NET_LOCK();
+ if (ISSET(ifp->if_flags, IFF_RUNNING))
+ egre_down(sc);
+ NET_UNLOCK();
+
+ ifmedia_delete_instance(&sc->sc_media, IFM_INST_ANY);
+ ether_ifdetach(ifp);
+ if_detach(ifp);
+
+ free(sc, M_DEVBUF, sizeof(*sc));
+
+ return (0);
+}
+
int
gre_input(struct mbuf **mp, int *offp, int type, int af)
{
struct gre_h_key *gkh;
void (*input)(struct ifnet *, struct mbuf *);
int bpf_af = AF_UNSPEC; /* bpf */
+ int mcast = 0;
if (!gre_allow)
goto decline;
break;
#endif
#ifdef MPLS
- case htons(ETHERTYPE_MPLS):
case htons(ETHERTYPE_MPLS_MCAST):
+ mcast = M_MCAST|M_BCAST;
+ /* fallthrough */
+ case htons(ETHERTYPE_MPLS):
#if NBPFILTER > 0
bpf_af = AF_MPLS;
#endif
input = gre_keepalive_recv;
break;
- case htons(ETHERTYPE_TRANSETHER): /* not yet */
+ case htons(ETHERTYPE_TRANSETHER):
+ if (egre_input(key, m, hlen) == -1)
+ goto decline;
default:
goto decline;
}
m_adj(m, hlen);
m->m_flags &= ~(M_MCAST|M_BCAST);
+ m->m_flags |= mcast;
m->m_pkthdr.ph_ifidx = ifp->if_index;
m->m_pkthdr.ph_rtableid = ifp->if_rdomain;
return (-1);
}
+static int
+egre_input(const struct gre_tunnel *key, struct mbuf *m, int hlen)
+{
+ struct egre_softc *sc;
+ struct mbuf_list ml = MBUF_LIST_INITIALIZER();
+ struct mbuf *n;
+ int off;
+
+ sc = (struct egre_softc *)RBT_FIND(gre_tree, &egre_softcs, key);
+ if (sc == NULL)
+ return (-1);
+
+ /* it's ours now */
+
+ m_adj(m, hlen);
+
+ if (m->m_pkthdr.len < sizeof(struct ether_header)) {
+ m_freem(m);
+ return (0);
+ }
+
+ m = m_pullup(m, sizeof(struct ether_header));
+ if (m == NULL)
+ return (0);
+
+ n = m_getptr(m, sizeof(struct ether_header), &off);
+ if (n == NULL) {
+ m_freem(m);
+ return (0);
+ }
+
+ if (!ALIGNED_POINTER(mtod(n, caddr_t) + off, uint32_t)) {
+ n = m_dup_pkt(m, ETHER_ALIGN, M_NOWAIT);
+ m_freem(m);
+ if (n == NULL)
+ return (0);
+ m = n;
+ }
+
+ m->m_flags &= ~(M_MCAST|M_BCAST);
+
+#if NPF > 0
+ pf_pkt_addr_changed(m);
+#endif
+
+ ml_enqueue(&ml, m);
+ if_input(&sc->sc_ac.ac_if, &ml);
+
+ return (0);
+}
+
static void
gre_keepalive_recv(struct ifnet *ifp, struct mbuf *m)
{
struct gre_softc *sc = ifp->if_softc;
struct mbuf *m;
uint8_t tos;
+ uint16_t proto;
#if NBPFILTER > 0
caddr_t if_bpf;
#endif
pf_pkt_addr_changed(m);
#endif
- m = gre_encap(sc, m, &tos);
+ switch (m->m_pkthdr.ph_family) {
+ case AF_INET: {
+ struct ip *ip = mtod(m, struct ip *);
+ tos = ip->ip_tos;
+
+ proto = htons(ETHERTYPE_IP);
+ break;
+ }
+#ifdef INET6
+ case AF_INET6:
+ proto = htons(ETHERTYPE_IPV6);
+ break;
+#endif
+#ifdef MPLS
+ case AF_MPLS:
+ if (m->m_flags & (M_BCAST | M_MCAST))
+ proto = htons(ETHERTYPE_MPLS_MCAST);
+ else
+ proto = htons(ETHERTYPE_MPLS);
+ break;
+#endif
+ default:
+ unhandled_af(m->m_pkthdr.ph_family);
+ }
+
+ m = gre_encap(&sc->sc_tunnel, m, proto);
if (m == NULL || gre_ip_output(&sc->sc_tunnel, m, tos) != 0) {
ifp->if_oerrors++;
continue;
}
}
+static void
+egre_start(struct ifnet *ifp)
+{
+ struct egre_softc *sc = ifp->if_softc;
+ struct mbuf *m0, *m;
+#if NBPFILTER > 0
+ caddr_t if_bpf;
+#endif
+
+ if (!gre_allow)
+ ifq_purge(&ifp->if_snd);
+
+ while ((m0 = ifq_dequeue(&ifp->if_snd)) != NULL) {
+#if NBPFILTER > 0
+ if_bpf = ifp->if_bpf;
+ if (if_bpf)
+ bpf_mtap_ether(if_bpf, m0, BPF_DIRECTION_OUT);
+#endif
+
+ m = m_gethdr(M_DONTWAIT, m0->m_type);
+ if (m == NULL) {
+ m_freem(m0);
+ continue;
+ }
+
+ M_MOVE_PKTHDR(m, m0);
+ m->m_next = m0;
+
+ MH_ALIGN(m, 0);
+ m->m_len = 0;
+
+ m->m_flags &= ~(M_MCAST|M_BCAST);
+ m->m_pkthdr.ph_ifidx = ifp->if_index;
+ m->m_pkthdr.ph_rtableid = ifp->if_rdomain;
+
+#if NPF > 0
+ pf_pkt_addr_changed(m);
+#endif
+
+ m = gre_encap(&sc->sc_tunnel, m, htons(ETHERTYPE_TRANSETHER));
+ if (m == NULL || gre_ip_output(&sc->sc_tunnel, m, 0) != 0) {
+ ifp->if_oerrors++;
+ continue;
+ }
+ }
+}
+
static int
gre_ip_output(const struct gre_tunnel *tunnel, struct mbuf *m, uint8_t tos)
{
}
static struct mbuf *
-gre_encap(struct gre_softc *sc, struct mbuf *m, uint8_t *tos)
+gre_encap(struct gre_tunnel *tunnel, struct mbuf *m, uint16_t proto)
{
struct gre_header *gh;
struct gre_h_key *gkh;
- uint16_t proto;
int hlen;
- *tos = 0;
- switch (m->m_pkthdr.ph_family) {
- case AF_INET: {
- proto = htons(ETHERTYPE_IP);
-
- struct ip *ip = mtod(m, struct ip *);
- *tos = ip->ip_tos;
- break;
- }
-#ifdef INET6
- case AF_INET6:
- proto = htons(ETHERTYPE_IPV6);
- break;
-#endif
-#ifdef MPLS
- case AF_MPLS:
- if (m->m_flags & (M_BCAST | M_MCAST))
- proto = htons(ETHERTYPE_MPLS_MCAST);
- else
- proto = htons(ETHERTYPE_MPLS);
- break;
-#endif
- case AF_UNSPEC:
- proto = htons(0);
- break;
- default:
- unhandled_af(m->m_pkthdr.ph_family);
- }
-
hlen = sizeof(*gh);
- if (sc->sc_tunnel.t_key_mask != GRE_KEY_NONE)
+ if (tunnel->t_key_mask != GRE_KEY_NONE)
hlen += sizeof(*gkh);
m = m_prepend(m, hlen, M_DONTWAIT);
gh = mtod(m, struct gre_header *);
gh->gre_flags = GRE_VERS_0;
gh->gre_proto = proto;
- if (sc->sc_tunnel.t_key_mask != GRE_KEY_NONE) {
+ if (tunnel->t_key_mask != GRE_KEY_NONE) {
gh->gre_flags |= htons(GRE_KP);
gkh = (struct gre_h_key *)(gh + 1);
- gkh->gre_key = sc->sc_tunnel.t_key;
+ gkh->gre_key = tunnel->t_key;
}
return (m);
}
static int
-gre_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+gre_tunnel_ioctl(struct ifnet *ifp, struct gre_tunnel *tunnel,
+ u_long cmd, void *data)
{
- struct gre_softc *sc = ifp->if_softc;
struct ifreq *ifr = (struct ifreq *)data;
- struct ifkalivereq *ikar = (struct ifkalivereq *)data;
int error = 0;
switch(cmd) {
- case SIOCSIFADDR:
- ifp->if_flags |= IFF_UP;
- /* FALLTHROUGH */
- case SIOCSIFFLAGS:
- if (ISSET(ifp->if_flags, IFF_UP)) {
- if (!ISSET(ifp->if_flags, IFF_RUNNING))
- error = gre_up(sc);
- else
- error = 0;
- } else {
- if (ISSET(ifp->if_flags, IFF_RUNNING))
- error = gre_down(sc);
- }
- break;
case SIOCSIFMTU:
if (ifr->ifr_mtu < 576) {
error = EINVAL;
}
break;
- case SIOCSETKALIVE:
- if (ISSET(ifp->if_flags, IFF_RUNNING)) {
- error = EBUSY;
- break;
- }
-
- if (ikar->ikar_timeo < 0 || ikar->ikar_timeo > 86400 ||
- ikar->ikar_cnt < 0 || ikar->ikar_cnt > 256)
- return (EINVAL);
-
- if (ikar->ikar_timeo == 0 || ikar->ikar_cnt == 0) {
- sc->sc_ka_count = 0;
- sc->sc_ka_timeo = 0;
- sc->sc_ka_state = GRE_KA_NONE;
- } else {
- sc->sc_ka_count = ikar->ikar_cnt;
- sc->sc_ka_timeo = ikar->ikar_timeo;
- sc->sc_ka_state = GRE_KA_DOWN;
- }
- break;
-
- case SIOCGETKALIVE:
- ikar->ikar_cnt = sc->sc_ka_count;
- ikar->ikar_timeo = sc->sc_ka_timeo;
- break;
-
case SIOCSVNETID:
if (ISSET(ifp->if_flags, IFF_RUNNING)) {
error = EBUSY;
break;
}
- error = gre_set_vnetid(&sc->sc_tunnel, ifr);
+ error = gre_set_vnetid(tunnel, ifr);
break;
case SIOCGVNETID:
- error = gre_get_vnetid(&sc->sc_tunnel, ifr);
+ error = gre_get_vnetid(tunnel, ifr);
break;
case SIOCDVNETID:
if (ISSET(ifp->if_flags, IFF_RUNNING)) {
error = EBUSY;
break;
}
- error = gre_del_vnetid(&sc->sc_tunnel);
+ error = gre_del_vnetid(tunnel);
break;
case SIOCSLIFPHYADDR:
error = EBUSY;
break;
}
- error = gre_set_tunnel(&sc->sc_tunnel,
- (struct if_laddrreq *)data);
+ error = gre_set_tunnel(tunnel, (struct if_laddrreq *)data);
break;
case SIOCGLIFPHYADDR:
- error = gre_get_tunnel(&sc->sc_tunnel,
- (struct if_laddrreq *)data);
+ error = gre_get_tunnel(tunnel, (struct if_laddrreq *)data);
break;
case SIOCDIFPHYADDR:
if (ISSET(ifp->if_flags, IFF_RUNNING)) {
break;
}
- error = gre_del_tunnel(&sc->sc_tunnel);
+ error = gre_del_tunnel(tunnel);
break;
case SIOCSLIFPHYRTABLE:
error = EINVAL;
break;
}
- sc->sc_tunnel.t_rtableid = ifr->ifr_rdomainid;
+ tunnel->t_rtableid = ifr->ifr_rdomainid;
break;
case SIOCGLIFPHYRTABLE:
- ifr->ifr_rdomainid = sc->sc_tunnel.t_rtableid;
+ ifr->ifr_rdomainid = tunnel->t_rtableid;
break;
case SIOCSLIFPHYTTL:
}
/* commit */
- sc->sc_tunnel.t_ttl = (uint8_t)ifr->ifr_ttl;
+ tunnel->t_ttl = (uint8_t)ifr->ifr_ttl;
break;
case SIOCGLIFPHYTTL:
- ifr->ifr_ttl = (int)sc->sc_tunnel.t_ttl;
+ ifr->ifr_ttl = (int)tunnel->t_ttl;
break;
default:
error = ENOTTY;
+ break;
+ }
+
+ return (error);
+}
+
+static int
+gre_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+{
+ struct gre_softc *sc = ifp->if_softc;
+ struct ifkalivereq *ikar = (struct ifkalivereq *)data;
+ int error = 0;
+
+ switch(cmd) {
+ case SIOCSIFADDR:
+ ifp->if_flags |= IFF_UP;
+ /* FALLTHROUGH */
+ case SIOCSIFFLAGS:
+ if (ISSET(ifp->if_flags, IFF_UP)) {
+ if (!ISSET(ifp->if_flags, IFF_RUNNING))
+ error = gre_up(sc);
+ else
+ error = 0;
+ } else {
+ if (ISSET(ifp->if_flags, IFF_RUNNING))
+ error = gre_down(sc);
+ }
+ break;
+ case SIOCSIFRDOMAIN:
+ if (ISSET(ifp->if_flags, IFF_RUNNING)) {
+ error = EBUSY;
+ break;
+ }
+ break;
+
+ case SIOCSETKALIVE:
+ if (ISSET(ifp->if_flags, IFF_RUNNING)) {
+ error = EBUSY;
+ break;
+ }
+
+ if (ikar->ikar_timeo < 0 || ikar->ikar_timeo > 86400 ||
+ ikar->ikar_cnt < 0 || ikar->ikar_cnt > 256)
+ return (EINVAL);
+
+ if (ikar->ikar_timeo == 0 || ikar->ikar_cnt == 0) {
+ sc->sc_ka_count = 0;
+ sc->sc_ka_timeo = 0;
+ sc->sc_ka_state = GRE_KA_NONE;
+ } else {
+ sc->sc_ka_count = ikar->ikar_cnt;
+ sc->sc_ka_timeo = ikar->ikar_timeo;
+ sc->sc_ka_state = GRE_KA_DOWN;
+ }
+ break;
+
+ case SIOCGETKALIVE:
+ ikar->ikar_cnt = sc->sc_ka_count;
+ ikar->ikar_timeo = sc->sc_ka_timeo;
+ break;
+
+ default:
+ error = gre_tunnel_ioctl(ifp, &sc->sc_tunnel, cmd, data);
+ break;
+ }
+
+ return (error);
+}
+
+static int
+egre_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+{
+ struct egre_softc *sc = ifp->if_softc;
+ int error = 0;
+
+ switch(cmd) {
+ case SIOCSIFADDR:
+ ifp->if_flags |= IFF_UP;
+ /* FALLTHROUGH */
+ case SIOCSIFFLAGS:
+ if (ISSET(ifp->if_flags, IFF_UP)) {
+ if (!ISSET(ifp->if_flags, IFF_RUNNING))
+ error = egre_up(sc);
+ else
+ error = 0;
+ } else {
+ if (ISSET(ifp->if_flags, IFF_RUNNING))
+ error = egre_down(sc);
+ }
+ break;
+
+ default:
+ error = gre_tunnel_ioctl(ifp, &sc->sc_tunnel, cmd, data);
+ if (error == ENOTTY)
+ error = ether_ioctl(ifp, &sc->sc_ac, cmd, data);
+ break;
}
return (error);
return (EHOSTUNREACH);
if (RBT_INSERT(gre_tree, &gre_softcs, &sc->sc_tunnel) != NULL)
- return (EBUSY);
+ return (EADDRINUSE);
/* we're up and running now */
struct gre_keepalive *gk;
SIPHASH_CTX ctx;
int linkhdr, len;
- uint8_t tos;
+ uint16_t proto;
if (!ISSET(sc->sc_if.if_flags, IFF_RUNNING) ||
sc->sc_ka_state == GRE_KA_NONE)
SipHash24_Update(&ctx, &gk->gk_random, sizeof(gk->gk_random));
SipHash24_Final(gk->gk_digest, &ctx);
- m->m_pkthdr.ph_family = AF_UNSPEC;
- m = gre_encap(sc, m, &tos);
+ m = gre_encap(&sc->sc_tunnel, m, htons(0));
if (m == NULL)
return;
t.t_ttl = sc->sc_tunnel.t_ttl;
memcpy(t.t_src, sc->sc_tunnel.t_dst, sizeof(t.t_src));
memcpy(t.t_dst, sc->sc_tunnel.t_src, sizeof(t.t_dst));
- m = gre_ip_encap(&t, m, tos);
+ m = gre_ip_encap(&t, m, 0);
if (m == NULL)
return;
- if (sc->sc_tunnel.t_af == AF_INET) {
+ switch (sc->sc_tunnel.t_af) {
+ case AF_INET: {
struct ip *ip;
ip = mtod(m, struct ip *);
ip->ip_id = htons(ip_randomid());
ip->ip_sum = 0;
ip->ip_sum = in_cksum(m, sizeof(*ip));
+
+ proto = htons(ETHERTYPE_IP);
+ break;
+ }
+#ifdef INET6
+ case AF_INET6:
+ proto = htons(ETHERTYPE_IPV6);
+ break;
+#endif
}
+
/*
* put it in the tunnel
*/
- m->m_pkthdr.ph_family = sc->sc_tunnel.t_af;
- m = gre_encap(sc, m, &tos);
+ m = gre_encap(&sc->sc_tunnel, m, proto);
if (m == NULL)
return;
m->m_pkthdr.ph_ifidx = sc->sc_if.if_index;
m->m_pkthdr.ph_rtableid = sc->sc_if.if_rdomain;
- gre_ip_output(&sc->sc_tunnel, m, tos);
+ gre_ip_output(&sc->sc_tunnel, m, 0);
timeout_add_sec(&sc->sc_ka_send, sc->sc_ka_timeo);
}
return (0);
}
+static int
+egre_up(struct egre_softc *sc)
+{
+ if (sc->sc_tunnel.t_af == AF_UNSPEC)
+ return (ENXIO);
+
+ NET_ASSERT_LOCKED();
+ if (RBT_INSERT(gre_tree, &egre_softcs, &sc->sc_tunnel) != NULL)
+ return (EADDRINUSE);
+
+ SET(sc->sc_ac.ac_if.if_flags, IFF_RUNNING);
+
+ return (0);
+}
+
+static int
+egre_down(struct egre_softc *sc)
+{
+ NET_ASSERT_LOCKED();
+
+ RBT_REMOVE(gre_tree, &egre_softcs, &sc->sc_tunnel);
+
+ CLR(sc->sc_ac.ac_if.if_flags, IFF_RUNNING);
+
+ /* barrier? */
+
+ return (0);
+}
+
+static int
+egre_media_change(struct ifnet *ifp)
+{
+ return (ENOTTY);
+}
+
+static void
+egre_media_status(struct ifnet *ifp, struct ifmediareq *imr)
+{
+ imr->ifm_active = IFM_ETHER | IFM_AUTO;
+ imr->ifm_status = IFM_AVALID | IFM_ACTIVE;
+}
+
int
gre_sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp,
size_t newlen)
return (error);
default:
return (ENOPROTOOPT);
- }
+ }
/* NOTREACHED */
}