summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Gwynne <dlg@cvs.openbsd.org>2018-02-27 04:36:19 +0000
committerDavid Gwynne <dlg@cvs.openbsd.org>2018-02-27 04:36:19 +0000
commitd9670642790b9be76e8758da5118e960da212efc (patch)
tree44b538124fe7d9c037e1a87dc51c5ff1f6eec3b0
parent64e3f2cab1a66dbf2422c93453f24f11bb6d90b4 (diff)
add support for point to multipoint GRE via mgre(4).
mgre is different to gre in that the tunnel only needs configuration for a local address, and the address inside the tunnel is configured on it is a subnet. other addresses on that subnet get mapped to a tunnel endpoint and send there. at the moment that mechanism is the routing table, using non-gateway host routes with the tunnel endpoint as the gateway address. the tunnel address configuration is ugly and confusing atm, but should improve soon. ok mpi@ who has some suggestions i can do in the tree.
-rw-r--r--sys/net/if_gre.c573
1 files changed, 484 insertions, 89 deletions
diff --git a/sys/net/if_gre.c b/sys/net/if_gre.c
index 9988b297e79..c4eb21c8096 100644
--- a/sys/net/if_gre.c
+++ b/sys/net/if_gre.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: if_gre.c,v 1.114 2018/02/25 01:52:25 dlg Exp $ */
+/* $OpenBSD: if_gre.c,v 1.115 2018/02/27 04:36:18 dlg Exp $ */
/* $NetBSD: if_gre.c,v 1.9 1999/10/25 19:18:11 drochner Exp $ */
/*
@@ -187,6 +187,9 @@ struct gre_tunnel {
};
static int
+ gre_cmp_src(const struct gre_tunnel *,
+ const struct gre_tunnel *);
+static int
gre_cmp(const struct gre_tunnel *, const struct gre_tunnel *);
static int gre_set_tunnel(struct gre_tunnel *, struct if_laddrreq *, int);
@@ -217,11 +220,11 @@ static int gre_tunnel_ioctl(struct ifnet *, struct gre_tunnel *,
*/
struct gre_softc {
- struct ifnet sc_if;
-
- struct gre_tunnel sc_tunnel;
+ struct gre_tunnel sc_tunnel; /* must be first */
TAILQ_ENTRY(gre_softc) sc_entry;
+ struct ifnet sc_if;
+
struct timeout sc_ka_send;
struct timeout sc_ka_hold;
@@ -264,13 +267,48 @@ static void gre_link_state(struct gre_softc *);
static int gre_input_key(struct mbuf **, int *, int, int,
struct gre_tunnel *);
-static struct gre_softc *
- gre_find(const struct gre_tunnel *);
static void gre_keepalive_send(void *);
static void gre_keepalive_recv(struct ifnet *ifp, struct mbuf *);
static void gre_keepalive_hold(void *);
+static struct mbuf *
+ gre_l3_encap_dst(const struct gre_tunnel *, const void *,
+ struct mbuf *m, sa_family_t);
+
+#define gre_l3_encap(_t, _m, _af) \
+ gre_l3_encap_dst((_t), &(_t)->t_dst, (_m), (_af))
+
+struct mgre_softc {
+ struct gre_tunnel sc_tunnel; /* must be first */
+ RBT_ENTRY(mgre_softc) sc_entry;
+
+ struct ifnet sc_if;
+};
+
+RBT_HEAD(mgre_tree, mgre_softc);
+
+static inline int
+ mgre_cmp(const struct mgre_softc *, const struct mgre_softc *);
+
+RBT_PROTOTYPE(mgre_tree, mgre_softc, sc_entry, mgre_cmp);
+
+static int mgre_clone_create(struct if_clone *, int);
+static int mgre_clone_destroy(struct ifnet *);
+
+struct if_clone mgre_cloner =
+ IF_CLONE_INITIALIZER("mgre", mgre_clone_create, mgre_clone_destroy);
+
+static int mgre_output(struct ifnet *, struct mbuf *, struct sockaddr *,
+ struct rtentry *);
+static void mgre_start(struct ifnet *);
+static int mgre_ioctl(struct ifnet *, u_long, caddr_t);
+
+static int mgre_up(struct mgre_softc *);
+static int mgre_down(struct mgre_softc *);
+
+struct mgre_tree mgre_tree = RBT_INITIALIZER();
+
/*
* Ethernet GRE tunnels
*/
@@ -431,6 +469,7 @@ void
greattach(int n)
{
if_clone_attach(&gre_cloner);
+ if_clone_attach(&mgre_cloner);
if_clone_attach(&egre_cloner);
if_clone_attach(&nvgre_cloner);
}
@@ -498,6 +537,59 @@ gre_clone_destroy(struct ifnet *ifp)
}
static int
+mgre_clone_create(struct if_clone *ifc, int unit)
+{
+ struct mgre_softc *sc;
+ struct ifnet *ifp;
+
+ sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK|M_ZERO);
+ ifp = &sc->sc_if;
+
+ snprintf(ifp->if_xname, sizeof(ifp->if_xname),
+ "%s%d", ifc->ifc_name, unit);
+
+ ifp->if_softc = sc;
+ ifp->if_type = IFT_L3IPVLAN;
+ ifp->if_hdrlen = 24; /* IP + GRE */
+ ifp->if_mtu = GREMTU;
+ ifp->if_flags = 0; /* it's not p2p, and can't mcast or bcast */
+ ifp->if_xflags = IFXF_CLONED;
+ ifp->if_rtrequest = p2p_rtrequest; /* maybe? */;
+ ifp->if_output = mgre_output;
+ ifp->if_start = mgre_start;
+ ifp->if_ioctl = mgre_ioctl;
+
+ sc->sc_tunnel.t_ttl = ip_defttl;
+ sc->sc_tunnel.t_df = htons(0);
+
+ if_attach(ifp);
+ if_alloc_sadl(ifp);
+
+#if NBPFILTER > 0
+ bpfattach(&ifp->if_bpf, ifp, DLT_LOOP, sizeof(uint32_t));
+#endif
+
+ return (0);
+}
+
+static int
+mgre_clone_destroy(struct ifnet *ifp)
+{
+ struct mgre_softc *sc = ifp->if_softc;
+
+ NET_LOCK();
+ if (ISSET(ifp->if_flags, IFF_RUNNING))
+ mgre_down(sc);
+ NET_UNLOCK();
+
+ if_detach(ifp);
+
+ free(sc, M_DEVBUF, sizeof(*sc));
+
+ return (0);
+}
+
+static int
egre_clone_create(struct if_clone *ifc, int unit)
{
struct egre_softc *sc;
@@ -669,7 +761,7 @@ gre_input6(struct mbuf **mp, int *offp, int type, int af)
}
#endif /* INET6 */
-static struct gre_softc *
+static inline struct ifnet *
gre_find(const struct gre_tunnel *key)
{
struct gre_softc *sc;
@@ -681,20 +773,32 @@ gre_find(const struct gre_tunnel *key)
if (!ISSET(sc->sc_if.if_flags, IFF_RUNNING))
continue;
- return (sc);
+ return (&sc->sc_if);
}
return (NULL);
}
+static inline struct ifnet *
+mgre_find(const struct gre_tunnel *key)
+{
+ struct mgre_softc *sc;
+
+ sc = RBT_FIND(mgre_tree, &mgre_tree, (const struct mgre_softc *)key);
+ if (sc != NULL)
+ return (&sc->sc_if);
+
+ return (NULL);
+}
+
static int
gre_input_key(struct mbuf **mp, int *offp, int type, int af,
struct gre_tunnel *key)
{
struct mbuf *m = *mp;
int iphlen = *offp, hlen;
- struct gre_softc *sc;
struct ifnet *ifp;
+ const struct gre_tunnel *tunnel;
caddr_t buf;
struct gre_header *gh;
struct gre_h_key *gkh;
@@ -770,11 +874,12 @@ gre_input_key(struct mbuf **mp, int *offp, int type, int af,
return (IPPROTO_DONE);
}
- sc = gre_find(key);
- if (sc == NULL)
- goto decline;
-
- ifp = &sc->sc_if;
+ ifp = gre_find(key);
+ if (ifp == NULL) {
+ ifp = mgre_find(key);
+ if (ifp == NULL)
+ goto decline;
+ }
switch (gh->gre_proto) {
case htons(GRE_WCCP): {
@@ -836,6 +941,11 @@ gre_input_key(struct mbuf **mp, int *offp, int type, int af,
break;
#endif
case htons(0):
+ if (ifp->if_type != IFT_TUNNEL) {
+ /* keepalives dont make sense for mgre */
+ goto decline;
+ }
+
#if NBPFILTER > 0
bpf_af = AF_UNSPEC;
#endif
@@ -846,9 +956,13 @@ gre_input_key(struct mbuf **mp, int *offp, int type, int af,
goto decline;
}
+ /* it's ours now */
+
m_adj(m, hlen);
- if (sc->sc_tunnel.t_ttl == -1) {
+ tunnel = ifp->if_softc; /* gre and mgre tunnel info is at the front */
+
+ if (tunnel->t_ttl == -1) {
m = m_pullup(m, ttloff + 1);
if (m == NULL)
return (IPPROTO_DONE);
@@ -856,6 +970,11 @@ gre_input_key(struct mbuf **mp, int *offp, int type, int af,
*(m->m_data + ttloff) = key->t_ttl;
}
+ if (tunnel->t_key_mask == GRE_KEY_ENTROPY) {
+ m->m_pkthdr.ph_flowid = M_FLOWID_VALID |
+ (bemtoh32(&key->t_key) & ~GRE_KEY_ENTROPY);
+ }
+
m->m_flags &= ~(M_MCAST|M_BCAST);
m->m_flags |= mcast;
m->m_pkthdr.ph_ifidx = ifp->if_index;
@@ -865,11 +984,6 @@ gre_input_key(struct mbuf **mp, int *offp, int type, int af,
pf_pkt_addr_changed(m);
#endif
- if (sc->sc_tunnel.t_key_mask == GRE_KEY_ENTROPY) {
- m->m_pkthdr.ph_flowid = M_FLOWID_VALID |
- (bemtoh32(&key->t_key) & ~GRE_KEY_ENTROPY);
- }
-
ifp->if_ipackets++;
ifp->if_ibytes += m->m_pkthdr.len;
@@ -1338,76 +1452,178 @@ gre_start(struct ifnet *ifp)
{
struct gre_softc *sc = ifp->if_softc;
struct mbuf *m;
- uint8_t ttl, tos;
- int tttl;
- uint16_t proto;
+ int af;
#if NBPFILTER > 0
caddr_t if_bpf;
#endif
- int ttloff;
-
- tttl = sc->sc_tunnel.t_ttl;
while ((m = ifq_dequeue(&ifp->if_snd)) != NULL) {
+ af = m->m_pkthdr.ph_family;
+
#if NBPFILTER > 0
if_bpf = ifp->if_bpf;
- if (if_bpf) {
- int af = m->m_pkthdr.ph_family;
+ if (if_bpf)
bpf_mtap_af(if_bpf, af, m, BPF_DIRECTION_OUT);
- }
#endif
- switch (m->m_pkthdr.ph_family) {
- case AF_INET: {
- struct ip *ip;
+ m = gre_l3_encap(&sc->sc_tunnel, m, af);
+ if (m == NULL || gre_ip_output(&sc->sc_tunnel, m) != 0) {
+ ifp->if_oerrors++;
+ continue;
+ }
+ }
+}
- m = m_pullup(m, sizeof(*ip));
- if (m == NULL)
- continue;
+static int
+mgre_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dest,
+ struct rtentry *rt0)
+{
+ struct mgre_softc *sc = ifp->if_softc;
+ struct sockaddr *gate;
+ struct rtentry *rt;
+ struct m_tag *mtag;
+ int error = 0;
+ sa_family_t af;
+ const void *addr;
- ip = mtod(m, struct ip *);
- tos = ip->ip_tos;
+ if (!gre_allow) {
+ error = EACCES;
+ goto drop;
+ }
- ttloff = offsetof(struct ip, ip_ttl);
- proto = htons(ETHERTYPE_IP);
- break;
- }
+ if (!ISSET(ifp->if_flags, IFF_RUNNING)) {
+ error = ENETDOWN;
+ goto drop;
+ }
+
+ switch (dest->sa_family) {
+ case AF_INET:
#ifdef INET6
- case AF_INET6:
- tos = 0;
- ttloff = offsetof(struct ip6_hdr, ip6_hlim);
- proto = htons(ETHERTYPE_IPV6);
- break;
+ case AF_INET6:
#endif
#ifdef MPLS
- case AF_MPLS:
- ttloff = 3;
- tos = 0;
-
- if (m->m_flags & (M_BCAST | M_MCAST))
- proto = htons(ETHERTYPE_MPLS_MCAST);
- else
- proto = htons(ETHERTYPE_MPLS);
- break;
+ case AF_MPLS:
#endif
- default:
- unhandled_af(m->m_pkthdr.ph_family);
+ break;
+ default:
+ error = EAFNOSUPPORT;
+ goto drop;
+ }
+
+ if (ISSET(m->m_flags, M_MCAST|M_BCAST)) {
+ error = ENETUNREACH;
+ goto drop;
+ }
+
+ rt = rt_getll(rt0);
+
+ /* chech rt_expire? */
+ if (ISSET(rt->rt_flags, RTF_REJECT)) {
+ error = (rt == rt0) ? EHOSTDOWN : EHOSTUNREACH;
+ goto drop;
+ }
+ if (!ISSET(rt->rt_flags, RTF_HOST)) {
+ error = EHOSTUNREACH;
+ goto drop;
+ }
+ if (ISSET(rt->rt_flags, RTF_GATEWAY)) {
+ error = EINVAL;
+ goto drop;
+ }
+
+ gate = rt->rt_gateway;
+ af = gate->sa_family;
+ if (af != sc->sc_tunnel.t_af) {
+ error = EAGAIN;
+ goto drop;
+ }
+
+ /* Try to limit infinite recursion through misconfiguration. */
+ for (mtag = m_tag_find(m, PACKET_TAG_GRE, NULL); mtag;
+ mtag = m_tag_find(m, PACKET_TAG_GRE, mtag)) {
+ if (memcmp((caddr_t)(mtag + 1), &ifp->if_index,
+ sizeof(ifp->if_index)) == 0) {
+ error = EIO;
+ goto drop;
}
+ }
+
+ mtag = m_tag_get(PACKET_TAG_GRE, sizeof(ifp->if_index), M_NOWAIT);
+ if (mtag == NULL) {
+ error = ENOBUFS;
+ goto drop;
+ }
+ memcpy((caddr_t)(mtag + 1), &ifp->if_index, sizeof(ifp->if_index));
+ m_tag_prepend(m, mtag);
+
+ switch (af) {
+ case AF_INET: {
+ struct sockaddr_in *sin = (struct sockaddr_in *)gate;
+ addr = &sin->sin_addr;
+ break;
+ }
+#ifdef INET6
+ case AF_INET6: {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)gate;
+ addr = &sin6->sin6_addr;
+ break;
+ }
+ #endif
+ default:
+ unhandled_af(af);
+ /* NOTREACHED */
+ }
- if (tttl == -1) {
- m = m_pullup(m, ttloff + 1);
- if (m == NULL)
- continue;
+ m = gre_l3_encap_dst(&sc->sc_tunnel, addr, m, dest->sa_family);
+ if (m == NULL)
+ return (ENOBUFS);
- ttl = *(m->m_data + ttloff);
- } else
- ttl = tttl;
+ m->m_pkthdr.ph_family = dest->sa_family;
- m = gre_encap(&sc->sc_tunnel, m, proto, ttl, tos);
- if (m == NULL || gre_ip_output(&sc->sc_tunnel, m) != 0) {
- ifp->if_oerrors++;
- continue;
+ error = if_enqueue(ifp, m);
+ if (error)
+ ifp->if_oerrors++;
+ return (error);
+
+drop:
+ m_freem(m);
+ return (error);
+}
+
+static void
+mgre_start(struct ifnet *ifp)
+{
+ struct mgre_softc *sc = ifp->if_softc;
+ struct mbuf *m;
+#if NBPFILTER > 0
+ caddr_t if_bpf;
+#endif
+
+ while ((m = ifq_dequeue(&ifp->if_snd)) != NULL) {
+#if NBPFILTER > 0
+ if_bpf = ifp->if_bpf;
+ if (if_bpf) {
+ struct m_hdr mh;
+ struct mbuf *n;
+ int off;
+
+ n = m_getptr(m, ifp->if_hdrlen, &off);
+ KASSERT(n != NULL);
+
+ mh.mh_flags = 0;
+ mh.mh_next = n->m_next;
+ mh.mh_len = n->m_len - off;
+ mh.mh_data = n->m_data + off;
+
+ bpf_mtap_af(if_bpf, m->m_pkthdr.ph_family,
+ (struct mbuf *)&mh, BPF_DIRECTION_OUT);
}
+#endif
+
+ if (m == NULL || gre_ip_output(&sc->sc_tunnel, m) != 0) {
+ ifp->if_oerrors++;
+ continue;
+ }
}
}
@@ -1454,6 +1670,64 @@ egre_start(struct ifnet *ifp)
}
static struct mbuf *
+gre_l3_encap_dst(const struct gre_tunnel *tunnel, const void *dst,
+ struct mbuf *m, sa_family_t af)
+{
+ uint16_t proto;
+ uint8_t ttl, tos;
+ int tttl = tunnel->t_ttl;
+ int ttloff;
+
+ switch (af) {
+ case AF_INET: {
+ struct ip *ip;
+
+ m = m_pullup(m, sizeof(*ip));
+ if (m == NULL)
+ return (NULL);
+
+ ip = mtod(m, struct ip *);
+ tos = ip->ip_tos;
+
+ ttloff = offsetof(struct ip, ip_ttl);
+ proto = htons(ETHERTYPE_IP);
+ break;
+ }
+#ifdef INET6
+ case AF_INET6:
+ tos = 0;
+ ttloff = offsetof(struct ip6_hdr, ip6_hlim);
+ proto = htons(ETHERTYPE_IPV6);
+ break;
+ #endif
+#ifdef MPLS
+ case AF_MPLS:
+ ttloff = 3;
+ tos = 0;
+
+ if (m->m_flags & (M_BCAST | M_MCAST))
+ proto = htons(ETHERTYPE_MPLS_MCAST);
+ else
+ proto = htons(ETHERTYPE_MPLS);
+ break;
+#endif
+ default:
+ unhandled_af(af);
+ }
+
+ if (tttl == -1) {
+ m = m_pullup(m, ttloff + 1);
+ if (m == NULL)
+ return (NULL);
+
+ ttl = *(m->m_data + ttloff);
+ } else
+ ttl = tttl;
+
+ return (gre_encap_dst(tunnel, dst, m, proto, ttl, tos));
+}
+
+static struct mbuf *
gre_encap_dst(const struct gre_tunnel *tunnel, const union gre_addr *dst,
struct mbuf *m, uint16_t proto, uint8_t ttl, uint8_t tos)
{
@@ -1715,6 +1989,63 @@ gre_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
}
static int
+mgre_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+{
+ struct mgre_softc *sc = ifp->if_softc;
+ struct ifreq *ifr = (struct ifreq *)data;
+ int error = 0;
+
+ switch(cmd) {
+ case SIOCSIFADDR:
+ break;
+ case SIOCSIFFLAGS:
+ if (ISSET(ifp->if_flags, IFF_UP)) {
+ if (!ISSET(ifp->if_flags, IFF_RUNNING))
+ error = mgre_up(sc);
+ else
+ error = 0;
+ } else {
+ if (ISSET(ifp->if_flags, IFF_RUNNING))
+ error = mgre_down(sc);
+ }
+ break;
+
+ case SIOCSLIFPHYTTL:
+ if (ifr->ifr_ttl != -1 &&
+ (ifr->ifr_ttl < 1 || ifr->ifr_ttl > 0xff)) {
+ error = EINVAL;
+ break;
+ }
+
+ /* commit */
+ sc->sc_tunnel.t_ttl = ifr->ifr_ttl;
+ break;
+
+ case SIOCGLIFPHYTTL:
+ ifr->ifr_ttl = sc->sc_tunnel.t_ttl;
+ break;
+
+ case SIOCSLIFPHYADDR:
+ /* XXX */
+ case SIOCSVNETID:
+ case SIOCDVNETID:
+ case SIOCDIFPHYADDR:
+ case SIOCSLIFPHYRTABLE:
+ if (ISSET(ifp->if_flags, IFF_RUNNING)) {
+ error = EBUSY;
+ break;
+ }
+
+ /* FALLTHROUGH */
+ 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;
@@ -2357,6 +2688,54 @@ gre_get_vnetflowid(struct gre_tunnel *tunnel, struct ifreq *ifr)
}
static int
+mgre_up(struct mgre_softc *sc)
+{
+ unsigned int hlen;
+
+ switch (sc->sc_tunnel.t_af) {
+ case AF_UNSPEC:
+ return (EDESTADDRREQ);
+ case AF_INET:
+ hlen = sizeof(struct ip);
+ break;
+#ifdef INET6
+ case AF_INET6:
+ hlen = sizeof(struct ip6_hdr);
+ break;
+#endif /* INET6 */
+ }
+
+ hlen += sizeof(struct gre_header);
+ if (sc->sc_tunnel.t_key_mask != GRE_KEY_NONE)
+ hlen += sizeof(struct gre_h_key);
+
+ NET_ASSERT_LOCKED();
+
+ if (RBT_INSERT(mgre_tree, &mgre_tree, sc) != NULL)
+ return (EADDRINUSE);
+
+ sc->sc_if.if_hdrlen = hlen;
+ SET(sc->sc_if.if_flags, IFF_RUNNING);
+
+ return (0);
+}
+
+static int
+mgre_down(struct mgre_softc *sc)
+{
+ NET_ASSERT_LOCKED();
+
+ CLR(sc->sc_if.if_flags, IFF_RUNNING);
+ sc->sc_if.if_hdrlen = 24; /* symmetry */
+
+ RBT_REMOVE(mgre_tree, &mgre_tree, sc);
+
+ /* barrier? */
+
+ return (0);
+}
+
+static int
egre_up(struct egre_softc *sc)
{
if (sc->sc_tunnel.t_af == AF_UNSPEC)
@@ -2828,32 +3207,12 @@ gre_ip_cmp(int af, const union gre_addr *a, const union gre_addr *b)
}
static int
-gre_cmp(const struct gre_tunnel *a, const struct gre_tunnel *b)
+gre_cmp_src(const struct gre_tunnel *a, const struct gre_tunnel *b)
{
uint32_t ka, kb;
uint32_t mask;
int rv;
- /* sort by routing table */
- if (a->t_rtableid > b->t_rtableid)
- return (1);
- if (a->t_rtableid < b->t_rtableid)
- return (-1);
-
- /* sort by address */
- if (a->t_af > b->t_af)
- return (1);
- if (a->t_af < b->t_af)
- return (-1);
-
- rv = gre_ip_cmp(a->t_af, &a->t_dst, &b->t_dst);
- if (rv != 0)
- return (rv);
-
- rv = gre_ip_cmp(a->t_af, &a->t_src, &b->t_src);
- if (rv != 0)
- return (rv);
-
/* is K set at all? */
ka = a->t_key_mask & GRE_KEY_ENTROPY;
kb = b->t_key_mask & GRE_KEY_ENTROPY;
@@ -2879,9 +3238,45 @@ gre_cmp(const struct gre_tunnel *a, const struct gre_tunnel *b)
return (-1);
}
+ /* sort by routing table */
+ if (a->t_rtableid > b->t_rtableid)
+ return (1);
+ if (a->t_rtableid < b->t_rtableid)
+ return (-1);
+
+ /* sort by address */
+ if (a->t_af > b->t_af)
+ return (1);
+ if (a->t_af < b->t_af)
+ return (-1);
+
+ rv = gre_ip_cmp(a->t_af, &a->t_src, &b->t_src);
+ if (rv != 0)
+ return (rv);
+
return (0);
}
+static int
+gre_cmp(const struct gre_tunnel *a, const struct gre_tunnel *b)
+{
+ int rv;
+
+ rv = gre_cmp_src(a, b);
+ if (rv != 0)
+ return (rv);
+
+ return (gre_ip_cmp(a->t_af, &a->t_dst, &b->t_dst));
+}
+
+static inline int
+mgre_cmp(const struct mgre_softc *a, const struct mgre_softc *b)
+{
+ return (gre_cmp_src(&a->sc_tunnel, &b->sc_tunnel));
+}
+
+RBT_GENERATE(mgre_tree, mgre_softc, sc_entry, mgre_cmp);
+
static inline int
egre_cmp(const struct egre_softc *a, const struct egre_softc *b)
{