summaryrefslogtreecommitdiffstats
path: root/freebsd/sys/netinet/ip_gre.c
diff options
context:
space:
mode:
Diffstat (limited to 'freebsd/sys/netinet/ip_gre.c')
-rw-r--r--freebsd/sys/netinet/ip_gre.c243
1 files changed, 187 insertions, 56 deletions
diff --git a/freebsd/sys/netinet/ip_gre.c b/freebsd/sys/netinet/ip_gre.c
index 673e23d5..65ab0ab9 100644
--- a/freebsd/sys/netinet/ip_gre.c
+++ b/freebsd/sys/netinet/ip_gre.c
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause-NetBSD
*
* Copyright (c) 1998 The NetBSD Foundation, Inc.
- * Copyright (c) 2014 Andrey V. Elsukov <ae@FreeBSD.org>
+ * Copyright (c) 2014, 2018 Andrey V. Elsukov <ae@FreeBSD.org>
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
@@ -43,18 +43,17 @@ __FBSDID("$FreeBSD$");
#include <rtems/bsd/local/opt_inet6.h>
#include <sys/param.h>
+#include <sys/jail.h>
#include <sys/systm.h>
-#include <sys/mbuf.h>
#include <sys/socket.h>
-#include <sys/socketvar.h>
-#include <sys/protosw.h>
+#include <sys/sockio.h>
+#include <sys/mbuf.h>
#include <sys/errno.h>
-#include <sys/time.h>
#include <sys/kernel.h>
-#include <sys/lock.h>
-#include <sys/rmlock.h>
#include <sys/sysctl.h>
-#include <net/ethernet.h>
+#include <sys/malloc.h>
+#include <sys/proc.h>
+
#include <net/if.h>
#include <net/if_var.h>
#include <net/vnet.h>
@@ -71,61 +70,177 @@ __FBSDID("$FreeBSD$");
#include <net/if_gre.h>
-extern struct domain inetdomain;
-static const struct protosw in_gre_protosw = {
- .pr_type = SOCK_RAW,
- .pr_domain = &inetdomain,
- .pr_protocol = IPPROTO_GRE,
- .pr_flags = PR_ATOMIC|PR_ADDR,
- .pr_input = gre_input,
- .pr_output = rip_output,
- .pr_ctlinput = rip_ctlinput,
- .pr_ctloutput = rip_ctloutput,
- .pr_usrreqs = &rip_usrreqs
-};
-
#define GRE_TTL 30
VNET_DEFINE(int, ip_gre_ttl) = GRE_TTL;
#define V_ip_gre_ttl VNET(ip_gre_ttl)
SYSCTL_INT(_net_inet_ip, OID_AUTO, grettl, CTLFLAG_VNET | CTLFLAG_RW,
- &VNET_NAME(ip_gre_ttl), 0, "");
+ &VNET_NAME(ip_gre_ttl), 0, "Default TTL value for encapsulated packets");
+
+VNET_DEFINE_STATIC(struct gre_list *, ipv4_hashtbl) = NULL;
+#define V_ipv4_hashtbl VNET(ipv4_hashtbl)
+#define GRE_HASH(src, dst) (V_ipv4_hashtbl[\
+ in_gre_hashval((src), (dst)) & (GRE_HASH_SIZE - 1)])
+#define GRE_HASH_SC(sc) GRE_HASH((sc)->gre_oip.ip_src.s_addr,\
+ (sc)->gre_oip.ip_dst.s_addr)
+
+static uint32_t
+in_gre_hashval(in_addr_t src, in_addr_t dst)
+{
+ uint32_t ret;
+
+ ret = fnv_32_buf(&src, sizeof(src), FNV1_32_INIT);
+ return (fnv_32_buf(&dst, sizeof(dst), ret));
+}
+
+static int
+in_gre_checkdup(const struct gre_softc *sc, in_addr_t src, in_addr_t dst)
+{
+ struct gre_softc *tmp;
+
+ if (sc->gre_family == AF_INET &&
+ sc->gre_oip.ip_src.s_addr == src &&
+ sc->gre_oip.ip_dst.s_addr == dst)
+ return (EEXIST);
+
+ CK_LIST_FOREACH(tmp, &GRE_HASH(src, dst), chain) {
+ if (tmp == sc)
+ continue;
+ if (tmp->gre_oip.ip_src.s_addr == src &&
+ tmp->gre_oip.ip_dst.s_addr == dst)
+ return (EADDRNOTAVAIL);
+ }
+ return (0);
+}
static int
-in_gre_encapcheck(const struct mbuf *m, int off, int proto, void *arg)
+in_gre_lookup(const struct mbuf *m, int off, int proto, void **arg)
{
- GRE_RLOCK_TRACKER;
+ const struct ip *ip;
struct gre_softc *sc;
- struct ip *ip;
- sc = (struct gre_softc *)arg;
- if ((GRE2IFP(sc)->if_flags & IFF_UP) == 0)
+ if (V_ipv4_hashtbl == NULL)
return (0);
- M_ASSERTPKTHDR(m);
- /*
- * We expect that payload contains at least IPv4
- * or IPv6 packet.
- */
- if (m->m_pkthdr.len < sizeof(struct greip) + sizeof(struct ip))
- return (0);
+ MPASS(in_epoch(net_epoch_preempt));
+ ip = mtod(m, const struct ip *);
+ CK_LIST_FOREACH(sc, &GRE_HASH(ip->ip_dst.s_addr,
+ ip->ip_src.s_addr), chain) {
+ /*
+ * This is an inbound packet, its ip_dst is source address
+ * in softc.
+ */
+ if (sc->gre_oip.ip_src.s_addr == ip->ip_dst.s_addr &&
+ sc->gre_oip.ip_dst.s_addr == ip->ip_src.s_addr) {
+ if ((GRE2IFP(sc)->if_flags & IFF_UP) == 0)
+ return (0);
+ *arg = sc;
+ return (ENCAP_DRV_LOOKUP);
+ }
+ }
+ return (0);
+}
- GRE_RLOCK(sc);
- if (sc->gre_family == 0)
- goto bad;
+static void
+in_gre_attach(struct gre_softc *sc)
+{
- KASSERT(sc->gre_family == AF_INET,
- ("wrong gre_family: %d", sc->gre_family));
+ sc->gre_hlen = sizeof(struct greip);
+ sc->gre_oip.ip_v = IPVERSION;
+ sc->gre_oip.ip_hl = sizeof(struct ip) >> 2;
+ sc->gre_oip.ip_p = IPPROTO_GRE;
+ gre_updatehdr(sc, &sc->gre_gihdr->gi_gre);
+ CK_LIST_INSERT_HEAD(&GRE_HASH_SC(sc), sc, chain);
+}
- ip = mtod(m, struct ip *);
- if (sc->gre_oip.ip_src.s_addr != ip->ip_dst.s_addr ||
- sc->gre_oip.ip_dst.s_addr != ip->ip_src.s_addr)
- goto bad;
+void
+in_gre_setopts(struct gre_softc *sc, u_long cmd, uint32_t value)
+{
- GRE_RUNLOCK(sc);
- return (32 * 2);
-bad:
- GRE_RUNLOCK(sc);
- return (0);
+ MPASS(cmd == GRESKEY || cmd == GRESOPTS);
+
+ /* NOTE: we are protected with gre_ioctl_sx lock */
+ MPASS(sc->gre_family == AF_INET);
+ CK_LIST_REMOVE(sc, chain);
+ GRE_WAIT();
+ if (cmd == GRESKEY)
+ sc->gre_key = value;
+ else
+ sc->gre_options = value;
+ in_gre_attach(sc);
+}
+
+int
+in_gre_ioctl(struct gre_softc *sc, u_long cmd, caddr_t data)
+{
+ struct ifreq *ifr = (struct ifreq *)data;
+ struct sockaddr_in *dst, *src;
+ struct ip *ip;
+ int error;
+
+ /* NOTE: we are protected with gre_ioctl_sx lock */
+ error = EINVAL;
+ switch (cmd) {
+ case SIOCSIFPHYADDR:
+ src = &((struct in_aliasreq *)data)->ifra_addr;
+ dst = &((struct in_aliasreq *)data)->ifra_dstaddr;
+
+ /* sanity checks */
+ if (src->sin_family != dst->sin_family ||
+ src->sin_family != AF_INET ||
+ src->sin_len != dst->sin_len ||
+ src->sin_len != sizeof(*src))
+ break;
+ if (src->sin_addr.s_addr == INADDR_ANY ||
+ dst->sin_addr.s_addr == INADDR_ANY) {
+ error = EADDRNOTAVAIL;
+ break;
+ }
+ if (V_ipv4_hashtbl == NULL)
+ V_ipv4_hashtbl = gre_hashinit();
+ error = in_gre_checkdup(sc, src->sin_addr.s_addr,
+ dst->sin_addr.s_addr);
+ if (error == EADDRNOTAVAIL)
+ break;
+ if (error == EEXIST) {
+ /* Addresses are the same. Just return. */
+ error = 0;
+ break;
+ }
+ ip = malloc(sizeof(struct greip) + 3 * sizeof(uint32_t),
+ M_GRE, M_WAITOK | M_ZERO);
+ ip->ip_src.s_addr = src->sin_addr.s_addr;
+ ip->ip_dst.s_addr = dst->sin_addr.s_addr;
+ if (sc->gre_family != 0) {
+ /* Detach existing tunnel first */
+ CK_LIST_REMOVE(sc, chain);
+ GRE_WAIT();
+ free(sc->gre_hdr, M_GRE);
+ /* XXX: should we notify about link state change? */
+ }
+ sc->gre_family = AF_INET;
+ sc->gre_hdr = ip;
+ sc->gre_oseq = 0;
+ sc->gre_iseq = UINT32_MAX;
+ in_gre_attach(sc);
+ break;
+ case SIOCGIFPSRCADDR:
+ case SIOCGIFPDSTADDR:
+ if (sc->gre_family != AF_INET) {
+ error = EADDRNOTAVAIL;
+ break;
+ }
+ src = (struct sockaddr_in *)&ifr->ifr_addr;
+ memset(src, 0, sizeof(*src));
+ src->sin_family = AF_INET;
+ src->sin_len = sizeof(*src);
+ src->sin_addr = (cmd == SIOCGIFPSRCADDR) ?
+ sc->gre_oip.ip_src: sc->gre_oip.ip_dst;
+ error = prison_if(curthread->td_ucred, (struct sockaddr *)src);
+ if (error != 0)
+ memset(src, 0, sizeof(*src));
+ break;
+ }
+ return (error);
}
int
@@ -158,14 +273,30 @@ in_gre_output(struct mbuf *m, int af, int hlen)
return (ip_output(m, NULL, NULL, IP_FORWARDING, NULL, NULL));
}
-int
-in_gre_attach(struct gre_softc *sc)
+static const struct encaptab *ecookie = NULL;
+static const struct encap_config ipv4_encap_cfg = {
+ .proto = IPPROTO_GRE,
+ .min_length = sizeof(struct greip) + sizeof(struct ip),
+ .exact_match = ENCAP_DRV_LOOKUP,
+ .lookup = in_gre_lookup,
+ .input = gre_input
+};
+
+void
+in_gre_init(void)
{
- KASSERT(sc->gre_ecookie == NULL, ("gre_ecookie isn't NULL"));
- sc->gre_ecookie = encap_attach_func(AF_INET, IPPROTO_GRE,
- in_gre_encapcheck, &in_gre_protosw, sc);
- if (sc->gre_ecookie == NULL)
- return (EEXIST);
- return (0);
+ if (!IS_DEFAULT_VNET(curvnet))
+ return;
+ ecookie = ip_encap_attach(&ipv4_encap_cfg, NULL, M_WAITOK);
+}
+
+void
+in_gre_uninit(void)
+{
+
+ if (IS_DEFAULT_VNET(curvnet))
+ ip_encap_detach(ecookie);
+ if (V_ipv4_hashtbl != NULL)
+ gre_hashdestroy(V_ipv4_hashtbl);
}