Module Name: src Committed By: knakahara Date: Thu Feb 16 08:23:36 UTC 2017
Added Files: src/sys/modules/if_l2tp: Makefile l2tp.ioconf src/sys/net: if_l2tp.c if_l2tp.h src/sys/netinet: in_l2tp.c in_l2tp.h src/sys/netinet6: in6_l2tp.c in6_l2tp.h Log Message: add missing files. To generate a diff of this commit: cvs rdiff -u -r0 -r1.1 src/sys/modules/if_l2tp/Makefile \ src/sys/modules/if_l2tp/l2tp.ioconf cvs rdiff -u -r0 -r1.1 src/sys/net/if_l2tp.c src/sys/net/if_l2tp.h cvs rdiff -u -r0 -r1.1 src/sys/netinet/in_l2tp.c src/sys/netinet/in_l2tp.h cvs rdiff -u -r0 -r1.1 src/sys/netinet6/in6_l2tp.c \ src/sys/netinet6/in6_l2tp.h Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Added files: Index: src/sys/modules/if_l2tp/Makefile diff -u /dev/null src/sys/modules/if_l2tp/Makefile:1.1 --- /dev/null Thu Feb 16 08:23:36 2017 +++ src/sys/modules/if_l2tp/Makefile Thu Feb 16 08:23:35 2017 @@ -0,0 +1,14 @@ +# $NetBSD: Makefile,v 1.1 2017/02/16 08:23:35 knakahara Exp $ + +.include "../Makefile.inc" + +.PATH: ${S}/net ${S}/netinet ${S}/netinet6 + +KMOD= if_l2tp +IOCONF= l2tp.ioconf +SRCS= if_l2tp.c in_l2tp.c in6_l2tp.c + +CPPFLAGS+= -DINET +CPPFLAGS+= -DINET6 + +.include <bsd.kmodule.mk> Index: src/sys/modules/if_l2tp/l2tp.ioconf diff -u /dev/null src/sys/modules/if_l2tp/l2tp.ioconf:1.1 --- /dev/null Thu Feb 16 08:23:36 2017 +++ src/sys/modules/if_l2tp/l2tp.ioconf Thu Feb 16 08:23:35 2017 @@ -0,0 +1,7 @@ +# $NetBSD: l2tp.ioconf,v 1.1 2017/02/16 08:23:35 knakahara Exp $ + +ioconf l2tp + +include "conf/files" + +pseudo-device l2tp Index: src/sys/net/if_l2tp.c diff -u /dev/null src/sys/net/if_l2tp.c:1.1 --- /dev/null Thu Feb 16 08:23:36 2017 +++ src/sys/net/if_l2tp.c Thu Feb 16 08:23:35 2017 @@ -0,0 +1,1502 @@ +/* $NetBSD: if_l2tp.c,v 1.1 2017/02/16 08:23:35 knakahara Exp $ */ + +/* + * Copyright (c) 2017 Internet Initiative Japan Inc. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 THE FOUNDATION OR CONTRIBUTORS + * 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. + */ + +/* + * L2TPv3 kernel interface + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: if_l2tp.c,v 1.1 2017/02/16 08:23:35 knakahara Exp $"); + +#ifdef _KERNEL_OPT +#include "opt_inet.h" +#endif + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/errno.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <sys/syslog.h> +#include <sys/proc.h> +#include <sys/conf.h> +#include <sys/kauth.h> +#include <sys/cpu.h> +#include <sys/cprng.h> +#include <sys/intr.h> +#include <sys/kmem.h> +#include <sys/mutex.h> +#include <sys/atomic.h> +#include <sys/pserialize.h> +#include <sys/device.h> +#include <sys/module.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_ether.h> +#include <net/if_types.h> +#include <net/netisr.h> +#include <net/route.h> +#include <net/bpf.h> +#include <net/if_vlanvar.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_encap.h> +#ifdef INET +#include <netinet/in_var.h> +#include <netinet/in_l2tp.h> +#endif /* INET */ +#ifdef INET6 +#include <netinet6/in6_l2tp.h> +#endif + +#include <net/if_l2tp.h> + +#if NVLAN > 0 +#include <net/if_vlanvar.h> +#endif + +/* TODO: IP_TCPMSS support */ +#undef IP_TCPMSS +#ifdef IP_TCPMSS +#include <netinet/ip_tcpmss.h> +#endif + +#include <net/bpf.h> +#include <net/net_osdep.h> + +/* + * l2tp global variable definitions + */ +LIST_HEAD(l2tp_sclist, l2tp_softc); +static struct { + struct l2tp_sclist list; + kmutex_t lock; +} l2tp_softcs __cacheline_aligned; + + +#if !defined(L2TP_ID_HASH_SIZE) +#define L2TP_ID_HASH_SIZE 64 +#endif +static struct { + kmutex_t lock; + struct pslist_head *lists; +} l2tp_hash __cacheline_aligned = { + .lists = NULL, +}; + +pserialize_t l2tp_psz __read_mostly; +struct psref_class *lv_psref_class __read_mostly; + +static void l2tp_ro_init_pc(void *, void *, struct cpu_info *); +static void l2tp_ro_fini_pc(void *, void *, struct cpu_info *); + +static int l2tp_clone_create(struct if_clone *, int); +static int l2tp_clone_destroy(struct ifnet *); + +struct if_clone l2tp_cloner = + IF_CLONE_INITIALIZER("l2tp", l2tp_clone_create, l2tp_clone_destroy); + +static int l2tp_output(struct ifnet *, struct mbuf *, + const struct sockaddr *, const struct rtentry *); +static void l2tpintr(struct l2tp_variant *); + +static void l2tp_hash_init(void); +static int l2tp_hash_fini(void); + +static void l2tp_start(struct ifnet *); +static int l2tp_transmit(struct ifnet *, struct mbuf *); + +static int l2tp_set_tunnel(struct ifnet *, struct sockaddr *, + struct sockaddr *); +static void l2tp_delete_tunnel(struct ifnet *); + +static int id_hash_func(uint32_t); + +static void l2tp_variant_update(struct l2tp_softc *, struct l2tp_variant *); +static int l2tp_set_session(struct l2tp_softc *, uint32_t, uint32_t); +static int l2tp_clear_session(struct l2tp_softc *); +static int l2tp_set_cookie(struct l2tp_softc *, uint64_t, u_int, uint64_t, u_int); +static void l2tp_clear_cookie(struct l2tp_softc *); +static void l2tp_set_state(struct l2tp_softc *, int); +static int l2tp_encap_attach(struct l2tp_variant *); +static int l2tp_encap_detach(struct l2tp_variant *); + +#ifndef MAX_L2TP_NEST +/* + * This macro controls the upper limitation on nesting of l2tp tunnels. + * Since, setting a large value to this macro with a careless configuration + * may introduce system crash, we don't allow any nestings by default. + * If you need to configure nested l2tp tunnels, you can define this macro + * in your kernel configuration file. However, if you do so, please be + * careful to configure the tunnels so that it won't make a loop. + */ +/* + * XXX + * Currently, if in_l2tp_output recursively calls, it causes locking against + * myself of struct l2tp_ro->lr_lock. So, nested l2tp tunnels is prohibited. + */ +#define MAX_L2TP_NEST 0 +#endif + +static int max_l2tp_nesting = MAX_L2TP_NEST; + +/* ARGSUSED */ +void +l2tpattach(int count) +{ + /* + * Nothing to do here, initialization is handled by the + * module initialization code in l2tpinit() below). + */ +} + +static void +l2tpinit(void) +{ + + mutex_init(&l2tp_softcs.lock, MUTEX_DEFAULT, IPL_NONE); + LIST_INIT(&l2tp_softcs.list); + + mutex_init(&l2tp_hash.lock, MUTEX_DEFAULT, IPL_NONE); + l2tp_psz = pserialize_create(); + lv_psref_class = psref_class_create("l2tpvar", IPL_SOFTNET); + if_clone_attach(&l2tp_cloner); + + l2tp_hash_init(); +} + +static int +l2tpdetach(void) +{ + int error; + + mutex_enter(&l2tp_softcs.lock); + if (!LIST_EMPTY(&l2tp_softcs.list)) { + mutex_exit(&l2tp_softcs.lock); + return EBUSY; + } + mutex_exit(&l2tp_softcs.lock); + + error = l2tp_hash_fini(); + if (error) + return error; + + if_clone_detach(&l2tp_cloner); + psref_class_destroy(lv_psref_class); + pserialize_destroy(l2tp_psz); + mutex_destroy(&l2tp_hash.lock); + + return error; +} + +static int +l2tp_clone_create(struct if_clone *ifc, int unit) +{ + struct l2tp_softc *sc; + struct l2tp_variant *var; + + sc = kmem_zalloc(sizeof(struct l2tp_softc), KM_SLEEP); + var = kmem_zalloc(sizeof(struct l2tp_variant), KM_SLEEP); + + var->lv_softc = sc; + var->lv_state = L2TP_STATE_DOWN; + var->lv_use_cookie = L2TP_COOKIE_OFF; + psref_target_init(&var->lv_psref, lv_psref_class); + + sc->l2tp_var = var; + mutex_init(&sc->l2tp_lock, MUTEX_DEFAULT, IPL_NONE); + PSLIST_ENTRY_INIT(sc, l2tp_hash); + + if_initname(&sc->l2tp_ec.ec_if, ifc->ifc_name, unit); + + l2tpattach0(sc); + + sc->l2tp_ro_percpu = percpu_alloc(sizeof(struct l2tp_ro)); + KASSERTMSG(sc->l2tp_ro_percpu != NULL, + "failed to allocate sc->l2tp_ro_percpu"); + percpu_foreach(sc->l2tp_ro_percpu, l2tp_ro_init_pc, NULL); + + mutex_enter(&l2tp_softcs.lock); + LIST_INSERT_HEAD(&l2tp_softcs.list, sc, l2tp_list); + mutex_exit(&l2tp_softcs.lock); + + return (0); +} + +void +l2tpattach0(struct l2tp_softc *sc) +{ + + sc->l2tp_ec.ec_if.if_addrlen = 0; + sc->l2tp_ec.ec_if.if_mtu = L2TP_MTU; + sc->l2tp_ec.ec_if.if_flags = IFF_POINTOPOINT|IFF_MULTICAST|IFF_SIMPLEX; + sc->l2tp_ec.ec_if.if_ioctl = l2tp_ioctl; + sc->l2tp_ec.ec_if.if_output = l2tp_output; + sc->l2tp_ec.ec_if.if_type = IFT_L2TP; + sc->l2tp_ec.ec_if.if_dlt = DLT_NULL; + sc->l2tp_ec.ec_if.if_start = l2tp_start; + sc->l2tp_ec.ec_if.if_transmit = l2tp_transmit; + sc->l2tp_ec.ec_if._if_input = ether_input; + IFQ_SET_READY(&sc->l2tp_ec.ec_if.if_snd); + if_attach(&sc->l2tp_ec.ec_if); + if_alloc_sadl(&sc->l2tp_ec.ec_if); + bpf_attach(&sc->l2tp_ec.ec_if, DLT_EN10MB, sizeof(struct ether_header)); +} + +void +l2tp_ro_init_pc(void *p, void *arg __unused, struct cpu_info *ci __unused) +{ + struct l2tp_ro *lro = p; + + mutex_init(&lro->lr_lock, MUTEX_DEFAULT, IPL_NONE); +} + +void +l2tp_ro_fini_pc(void *p, void *arg __unused, struct cpu_info *ci __unused) +{ + struct l2tp_ro *lro = p; + + rtcache_free(&lro->lr_ro); + + mutex_destroy(&lro->lr_lock); +} + +static int +l2tp_clone_destroy(struct ifnet *ifp) +{ + struct l2tp_softc *sc = container_of(ifp, struct l2tp_softc, + l2tp_ec.ec_if); + + l2tp_clear_session(sc); + l2tp_delete_tunnel(&sc->l2tp_ec.ec_if); + + mutex_enter(&l2tp_softcs.lock); + LIST_REMOVE(sc, l2tp_list); + mutex_exit(&l2tp_softcs.lock); + + bpf_detach(ifp); + + if_detach(ifp); + + percpu_foreach(sc->l2tp_ro_percpu, l2tp_ro_fini_pc, NULL); + percpu_free(sc->l2tp_ro_percpu, sizeof(struct l2tp_ro)); + + kmem_free(sc->l2tp_var, sizeof(struct l2tp_variant)); + mutex_destroy(&sc->l2tp_lock); + kmem_free(sc, sizeof(struct l2tp_softc)); + + return 0; +} + +static int +l2tp_output(struct ifnet *ifp, struct mbuf *m, const struct sockaddr *dst, + const struct rtentry *rt) +{ + struct l2tp_softc *sc = container_of(ifp, struct l2tp_softc, + l2tp_ec.ec_if); + struct l2tp_variant *var; + struct psref psref; + int error = 0; + + var = l2tp_getref_variant(sc, &psref); + if (var == NULL) { + m_freem(m); + return ENETDOWN; + } + + IFQ_CLASSIFY(&ifp->if_snd, m, dst->sa_family); + + m->m_flags &= ~(M_BCAST|M_MCAST); + + if ((ifp->if_flags & IFF_UP) == 0) { + m_freem(m); + error = ENETDOWN; + goto end; + } + + if (var->lv_psrc == NULL || var->lv_pdst == NULL) { + m_freem(m); + error = ENETDOWN; + goto end; + } + + /* XXX should we check if our outer source is legal? */ + + /* use DLT_NULL encapsulation here to pass inner af type */ + M_PREPEND(m, sizeof(int), M_DONTWAIT); + if (!m) { + error = ENOBUFS; + goto end; + } + *mtod(m, int *) = dst->sa_family; + + IFQ_ENQUEUE(&ifp->if_snd, m, error); + if (error) + goto end; + + /* + * direct call to avoid infinite loop at l2tpintr() + */ + l2tpintr(var); + + error = 0; + +end: + l2tp_putref_variant(var, &psref); + if (error) + ifp->if_oerrors++; + + return error; +} + +static void +l2tpintr(struct l2tp_variant *var) +{ + struct l2tp_softc *sc; + struct ifnet *ifp; + struct mbuf *m; + int error; + + KASSERT(psref_held(&var->lv_psref, lv_psref_class)); + + sc = var->lv_softc; + ifp = &sc->l2tp_ec.ec_if; + + /* output processing */ + if (var->lv_my_sess_id == 0 || var->lv_peer_sess_id == 0) { + IFQ_PURGE(&ifp->if_snd); + return; + } + + for (;;) { + IFQ_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + m->m_flags &= ~(M_BCAST|M_MCAST); + bpf_mtap(ifp, m); + switch (var->lv_psrc->sa_family) { +#ifdef INET + case AF_INET: + error = in_l2tp_output(var, m); + break; +#endif +#ifdef INET6 + case AF_INET6: + error = in6_l2tp_output(var, m); + break; +#endif + default: + m_freem(m); + error = ENETDOWN; + break; + } + + if (error) + ifp->if_oerrors++; + else { + ifp->if_opackets++; + /* + * obytes is incremented at ether_output() or + * bridge_enqueue(). + */ + } + } + +} + +void +l2tp_input(struct mbuf *m, struct ifnet *ifp) +{ + + KASSERT(ifp != NULL); + + if (0 == (mtod(m, u_long) & 0x03)) { + /* copy and align head of payload */ + struct mbuf *m_head; + int copy_length; + +#define L2TP_COPY_LENGTH 60 +#define L2TP_LINK_HDR_ROOM (MHLEN - L2TP_COPY_LENGTH - 4/*round4(2)*/) + + if (m->m_pkthdr.len < L2TP_COPY_LENGTH) { + copy_length = m->m_pkthdr.len; + } else { + copy_length = L2TP_COPY_LENGTH; + } + + if (m->m_len < copy_length) { + m = m_pullup(m, copy_length); + if (m == NULL) + return; + } + + MGETHDR(m_head, M_DONTWAIT, MT_HEADER); + if (m_head == NULL) { + m_freem(m); + return; + } + M_COPY_PKTHDR(m_head, m); + + m_head->m_data += 2 /* align */ + L2TP_LINK_HDR_ROOM; + memcpy(m_head->m_data, m->m_data, copy_length); + m_head->m_len = copy_length; + m->m_data += copy_length; + m->m_len -= copy_length; + + /* construct chain */ + if (m->m_len == 0) { + m_head->m_next = m_free(m); /* not m_freem */ + } else { + /* + * copyed mtag in previous call M_COPY_PKTHDR + * but don't delete mtag in case cutt of M_PKTHDR flag + */ + m_tag_delete_chain(m, NULL); + m->m_flags &= ~M_PKTHDR; + m_head->m_next = m; + } + + /* override m */ + m = m_head; + } + + m_set_rcvif(m, ifp); + + /* + * bpf_mtap() and ifp->if_ipackets++ is done in if_input() + * + * obytes is incremented at ether_output() or bridge_enqueue(). + */ + if_percpuq_enqueue(ifp->if_percpuq, m); +} + +void +l2tp_start(struct ifnet *ifp) +{ + struct psref psref; + struct l2tp_variant *var; + struct l2tp_softc *sc = container_of(ifp, struct l2tp_softc, + l2tp_ec.ec_if); + + var = l2tp_getref_variant(sc, &psref); + if (var == NULL) + return; + + if (var->lv_psrc == NULL || var->lv_pdst == NULL) + return; + + l2tpintr(var); + l2tp_putref_variant(var, &psref); +} + +int +l2tp_transmit(struct ifnet *ifp, struct mbuf *m) +{ + int error; + struct psref psref; + struct l2tp_variant *var; + struct l2tp_softc *sc = container_of(ifp, struct l2tp_softc, + l2tp_ec.ec_if); + + var = l2tp_getref_variant(sc, &psref); + if (var == NULL) { + m_freem(m); + return ENETDOWN; + } + + if (var->lv_psrc == NULL || var->lv_pdst == NULL) { + m_freem(m); + error = ENETDOWN; + goto out; + } + + m->m_flags &= ~(M_BCAST|M_MCAST); + bpf_mtap(ifp, m); + switch (var->lv_psrc->sa_family) { +#ifdef INET + case AF_INET: + error = in_l2tp_output(var, m); + break; +#endif +#ifdef INET6 + case AF_INET6: + error = in6_l2tp_output(var, m); + break; +#endif + default: + m_freem(m); + error = ENETDOWN; + break; + } + + if (error) + ifp->if_oerrors++; + else { + ifp->if_opackets++; + /* + * obytes is incremented at ether_output() or bridge_enqueue(). + */ + } + +out: + l2tp_putref_variant(var, &psref); + return error; +} + +/* XXX how should we handle IPv6 scope on SIOC[GS]IFPHYADDR? */ +int +l2tp_ioctl(struct ifnet *ifp, u_long cmd, void *data) +{ + struct l2tp_softc *sc = container_of(ifp, struct l2tp_softc, + l2tp_ec.ec_if); + struct l2tp_variant *var, *var_tmp; + struct ifreq *ifr = data; + int error = 0, size; + struct sockaddr *dst, *src; + struct l2tp_req l2tpr; + u_long mtu; + int bound; + struct psref psref; + + switch (cmd) { + case SIOCSIFADDR: + ifp->if_flags |= IFF_UP; + break; + + case SIOCSIFDSTADDR: + break; + + case SIOCADDMULTI: + case SIOCDELMULTI: + switch (ifr->ifr_addr.sa_family) { +#ifdef INET + case AF_INET: /* IP supports Multicast */ + break; +#endif /* INET */ +#ifdef INET6 + case AF_INET6: /* IP6 supports Multicast */ + break; +#endif /* INET6 */ + default: /* Other protocols doesn't support Multicast */ + error = EAFNOSUPPORT; + break; + } + break; + + case SIOCSIFMTU: + mtu = ifr->ifr_mtu; + if (mtu < L2TP_MTU_MIN || mtu > L2TP_MTU_MAX) + return (EINVAL); + ifp->if_mtu = mtu; + break; + +#ifdef INET + case SIOCSIFPHYADDR: + src = (struct sockaddr *) + &(((struct in_aliasreq *)data)->ifra_addr); + dst = (struct sockaddr *) + &(((struct in_aliasreq *)data)->ifra_dstaddr); + if (src->sa_family != AF_INET || dst->sa_family != AF_INET) + return EAFNOSUPPORT; + else if (src->sa_len != sizeof(struct sockaddr_in) + || dst->sa_len != sizeof(struct sockaddr_in)) + return EINVAL; + + error = l2tp_set_tunnel(&sc->l2tp_ec.ec_if, src, dst); + break; + +#endif /* INET */ +#ifdef INET6 + case SIOCSIFPHYADDR_IN6: + src = (struct sockaddr *) + &(((struct in6_aliasreq *)data)->ifra_addr); + dst = (struct sockaddr *) + &(((struct in6_aliasreq *)data)->ifra_dstaddr); + if (src->sa_family != AF_INET6 || dst->sa_family != AF_INET6) + return EAFNOSUPPORT; + else if (src->sa_len != sizeof(struct sockaddr_in6) + || dst->sa_len != sizeof(struct sockaddr_in6)) + return EINVAL; + + error = l2tp_set_tunnel(&sc->l2tp_ec.ec_if, src, dst); + break; + +#endif /* INET6 */ + case SIOCSLIFPHYADDR: + src = (struct sockaddr *) + &(((struct if_laddrreq *)data)->addr); + dst = (struct sockaddr *) + &(((struct if_laddrreq *)data)->dstaddr); + if (src->sa_family != dst->sa_family) + return EINVAL; + else if (src->sa_family == AF_INET + && src->sa_len != sizeof(struct sockaddr_in)) + return EINVAL; + else if (src->sa_family == AF_INET6 + && src->sa_len != sizeof(struct sockaddr_in6)) + return EINVAL; + else if (dst->sa_family == AF_INET + && dst->sa_len != sizeof(struct sockaddr_in)) + return EINVAL; + else if (dst->sa_family == AF_INET6 + && dst->sa_len != sizeof(struct sockaddr_in6)) + return EINVAL; + + error = l2tp_set_tunnel(&sc->l2tp_ec.ec_if, src, dst); + break; + + case SIOCDIFPHYADDR: + l2tp_delete_tunnel(&sc->l2tp_ec.ec_if); + break; + + case SIOCGIFPSRCADDR: +#ifdef INET6 + case SIOCGIFPSRCADDR_IN6: +#endif /* INET6 */ + bound = curlwp_bind(); + var = l2tp_getref_variant(sc, &psref); + if (var == NULL) { + curlwp_bindx(bound); + error = EADDRNOTAVAIL; + goto bad; + } + if (var->lv_psrc == NULL) { + l2tp_putref_variant(var, &psref); + curlwp_bindx(bound); + error = EADDRNOTAVAIL; + goto bad; + } + src = var->lv_psrc; + switch (cmd) { +#ifdef INET + case SIOCGIFPSRCADDR: + dst = &ifr->ifr_addr; + size = sizeof(ifr->ifr_addr); + break; +#endif /* INET */ +#ifdef INET6 + case SIOCGIFPSRCADDR_IN6: + dst = (struct sockaddr *) + &(((struct in6_ifreq *)data)->ifr_addr); + size = sizeof(((struct in6_ifreq *)data)->ifr_addr); + break; +#endif /* INET6 */ + default: + l2tp_putref_variant(var, &psref); + curlwp_bindx(bound); + error = EADDRNOTAVAIL; + goto bad; + } + if (src->sa_len > size) { + l2tp_putref_variant(var, &psref); + curlwp_bindx(bound); + return EINVAL; + } + sockaddr_copy(dst, src->sa_len, src); + l2tp_putref_variant(var, &psref); + curlwp_bindx(bound); + break; + + case SIOCGIFPDSTADDR: +#ifdef INET6 + case SIOCGIFPDSTADDR_IN6: +#endif /* INET6 */ + bound = curlwp_bind(); + var = l2tp_getref_variant(sc, &psref); + if (var == NULL) { + curlwp_bindx(bound); + error = EADDRNOTAVAIL; + goto bad; + } + if (var->lv_pdst == NULL) { + l2tp_putref_variant(var, &psref); + curlwp_bindx(bound); + error = EADDRNOTAVAIL; + goto bad; + } + src = var->lv_pdst; + switch (cmd) { +#ifdef INET + case SIOCGIFPDSTADDR: + dst = &ifr->ifr_addr; + size = sizeof(ifr->ifr_addr); + break; +#endif /* INET */ +#ifdef INET6 + case SIOCGIFPDSTADDR_IN6: + dst = (struct sockaddr *) + &(((struct in6_ifreq *)data)->ifr_addr); + size = sizeof(((struct in6_ifreq *)data)->ifr_addr); + break; +#endif /* INET6 */ + default: + l2tp_putref_variant(var, &psref); + curlwp_bindx(bound); + error = EADDRNOTAVAIL; + goto bad; + } + if (src->sa_len > size) { + l2tp_putref_variant(var, &psref); + curlwp_bindx(bound); + return EINVAL; + } + sockaddr_copy(dst, src->sa_len, src); + l2tp_putref_variant(var, &psref); + curlwp_bindx(bound); + break; + + case SIOCGLIFPHYADDR: + bound = curlwp_bind(); + var = l2tp_getref_variant(sc, &psref); + if (var == NULL) { + curlwp_bindx(bound); + error = EADDRNOTAVAIL; + goto bad; + } + if (var->lv_psrc == NULL || var->lv_pdst == NULL) { + l2tp_putref_variant(var, &psref); + curlwp_bindx(bound); + error = EADDRNOTAVAIL; + goto bad; + } + + /* copy src */ + src = var->lv_psrc; + dst = (struct sockaddr *) + &(((struct if_laddrreq *)data)->addr); + size = sizeof(((struct if_laddrreq *)data)->addr); + if (src->sa_len > size) { + l2tp_putref_variant(var, &psref); + curlwp_bindx(bound); + return EINVAL; + } + sockaddr_copy(dst, src->sa_len, src); + + /* copy dst */ + src = var->lv_pdst; + dst = (struct sockaddr *) + &(((struct if_laddrreq *)data)->dstaddr); + size = sizeof(((struct if_laddrreq *)data)->dstaddr); + if (src->sa_len > size) { + l2tp_putref_variant(var, &psref); + curlwp_bindx(bound); + return EINVAL; + } + sockaddr_copy(dst, src->sa_len, src); + l2tp_putref_variant(var, &psref); + curlwp_bindx(bound); + break; + + case SIOCSL2TPSESSION: + if ((error = copyin(ifr->ifr_data, &l2tpr, sizeof(l2tpr))) != 0) + break; + + /* session id must not zero */ + if (l2tpr.my_sess_id == 0 || l2tpr.peer_sess_id == 0) + return EINVAL; + + bound = curlwp_bind(); + var_tmp = l2tp_lookup_session_ref(l2tpr.my_sess_id, &psref); + if (var_tmp != NULL) { + /* duplicate session id */ + log(LOG_WARNING, "%s: duplicate session id %" PRIu32 " of %s\n", + sc->l2tp_ec.ec_if.if_xname, l2tpr.my_sess_id, + var_tmp->lv_softc->l2tp_ec.ec_if.if_xname); + psref_release(&psref, &var_tmp->lv_psref, + lv_psref_class); + curlwp_bindx(bound); + return EINVAL; + } + curlwp_bindx(bound); + + error = l2tp_set_session(sc, l2tpr.my_sess_id, l2tpr.peer_sess_id); + break; + case SIOCDL2TPSESSION: + l2tp_clear_session(sc); + break; + case SIOCSL2TPCOOKIE: + if ((error = copyin(ifr->ifr_data, &l2tpr, sizeof(l2tpr))) != 0) + break; + + error = l2tp_set_cookie(sc, l2tpr.my_cookie, l2tpr.my_cookie_len, + l2tpr.peer_cookie, l2tpr.peer_cookie_len); + break; + case SIOCDL2TPCOOKIE: + l2tp_clear_cookie(sc); + break; + case SIOCSL2TPSTATE: + if ((error = copyin(ifr->ifr_data, &l2tpr, sizeof(l2tpr))) != 0) + break; + + l2tp_set_state(sc, l2tpr.state); + break; + case SIOCGL2TP: + /* get L2TPV3 session info */ + memset(&l2tpr, 0, sizeof(l2tpr)); + + bound = curlwp_bind(); + var = l2tp_getref_variant(sc, &psref); + if (var == NULL) { + curlwp_bindx(bound); + error = EADDRNOTAVAIL; + goto bad; + } + + l2tpr.state = var->lv_state; + l2tpr.my_sess_id = var->lv_my_sess_id; + l2tpr.peer_sess_id = var->lv_peer_sess_id; + l2tpr.my_cookie = var->lv_my_cookie; + l2tpr.my_cookie_len = var->lv_my_cookie_len; + l2tpr.peer_cookie = var->lv_peer_cookie; + l2tpr.peer_cookie_len = var->lv_peer_cookie_len; + l2tp_putref_variant(var, &psref); + curlwp_bindx(bound); + + error = copyout(&l2tpr, ifr->ifr_data, sizeof(l2tpr)); + break; + + default: + error = ifioctl_common(ifp, cmd, data); + break; + } + bad: + return error; +} + +static int +l2tp_set_tunnel(struct ifnet *ifp, struct sockaddr *src, struct sockaddr *dst) +{ + struct l2tp_softc *sc = container_of(ifp, struct l2tp_softc, + l2tp_ec.ec_if); + struct sockaddr *osrc, *odst; + struct sockaddr *nsrc, *ndst; + struct l2tp_variant *ovar, *nvar; + int error; + + nsrc = sockaddr_dup(src, M_WAITOK); + ndst = sockaddr_dup(dst, M_WAITOK); + + nvar = kmem_alloc(sizeof(*nvar), KM_SLEEP); + + error = encap_lock_enter(); + if (error) + goto error; + + mutex_enter(&sc->l2tp_lock); + + ovar = sc->l2tp_var; + osrc = ovar->lv_psrc; + odst = ovar->lv_pdst; + *nvar = *ovar; + psref_target_init(&nvar->lv_psref, lv_psref_class); + nvar->lv_psrc = nsrc; + nvar->lv_pdst = ndst; + error = l2tp_encap_attach(nvar); + if (error) { + mutex_exit(&sc->l2tp_lock); + encap_lock_exit(); + goto error; + } + membar_producer(); + l2tp_variant_update(sc, nvar); + + mutex_exit(&sc->l2tp_lock); + + (void)l2tp_encap_detach(ovar); + encap_lock_exit(); + + if (osrc) + sockaddr_free(osrc); + if (odst) + sockaddr_free(odst); + kmem_free(ovar, sizeof(*ovar)); + + return 0; + +error: + sockaddr_free(nsrc); + sockaddr_free(ndst); + kmem_free(nvar, sizeof(*nvar)); + + return error; +} + +static void +l2tp_delete_tunnel(struct ifnet *ifp) +{ + struct l2tp_softc *sc = container_of(ifp, struct l2tp_softc, + l2tp_ec.ec_if); + struct sockaddr *osrc, *odst; + struct l2tp_variant *ovar, *nvar; + int error; + + nvar = kmem_alloc(sizeof(*nvar), KM_SLEEP); + + error = encap_lock_enter(); + if (error) { + kmem_free(nvar, sizeof(*nvar)); + return; + } + mutex_enter(&sc->l2tp_lock); + + ovar = sc->l2tp_var; + osrc = ovar->lv_psrc; + odst = ovar->lv_pdst; + *nvar = *ovar; + psref_target_init(&nvar->lv_psref, lv_psref_class); + nvar->lv_psrc = NULL; + nvar->lv_pdst = NULL; + membar_producer(); + l2tp_variant_update(sc, nvar); + + mutex_exit(&sc->l2tp_lock); + + (void)l2tp_encap_detach(ovar); + encap_lock_exit(); + + if (osrc) + sockaddr_free(osrc); + if (odst) + sockaddr_free(odst); + kmem_free(ovar, sizeof(*ovar)); +} + +static int id_hash_func(uint32_t id) +{ + uint32_t hash; + + hash = (id >> 16) ^ id; + hash = (hash >> 4) ^ hash; + + return hash & (L2TP_ID_HASH_SIZE - 1); +} + +static void +l2tp_hash_init(void) +{ + u_long mask; + + l2tp_hash.lists = hashinit(L2TP_ID_HASH_SIZE, HASH_PSLIST, true, + &mask); + KASSERT(mask == (L2TP_ID_HASH_SIZE - 1)); +} + +static int +l2tp_hash_fini(void) +{ + int i; + + mutex_enter(&l2tp_hash.lock); + + for (i = 0; i < L2TP_ID_HASH_SIZE; i++) { + if (PSLIST_WRITER_FIRST(&l2tp_hash.lists[i], struct l2tp_softc, + l2tp_hash) != NULL) { + mutex_exit(&l2tp_hash.lock); + return EBUSY; + } + } + for (i = 0; i < L2TP_ID_HASH_SIZE; i++) + PSLIST_DESTROY(&l2tp_hash.lists[i]); + + mutex_exit(&l2tp_hash.lock); + + hashdone(l2tp_hash.lists, HASH_PSLIST, L2TP_ID_HASH_SIZE - 1); + + return 0; +} + +static int +l2tp_set_session(struct l2tp_softc *sc, uint32_t my_sess_id, + uint32_t peer_sess_id) +{ + uint32_t idx; + struct l2tp_variant *nvar; + struct l2tp_variant *ovar; + struct ifnet *ifp = &sc->l2tp_ec.ec_if; + + nvar = kmem_alloc(sizeof(*nvar), KM_SLEEP); + + mutex_enter(&sc->l2tp_lock); + ovar = sc->l2tp_var; + *nvar = *ovar; + psref_target_init(&nvar->lv_psref, lv_psref_class); + nvar->lv_my_sess_id = my_sess_id; + nvar->lv_peer_sess_id = peer_sess_id; + membar_producer(); + + mutex_enter(&l2tp_hash.lock); + if (ovar->lv_my_sess_id > 0 && ovar->lv_peer_sess_id > 0) { + PSLIST_WRITER_REMOVE(sc, l2tp_hash); + pserialize_perform(l2tp_psz); + } + mutex_exit(&l2tp_hash.lock); + + l2tp_variant_update(sc, nvar); + mutex_exit(&sc->l2tp_lock); + + idx = id_hash_func(nvar->lv_my_sess_id); + if ((ifp->if_flags & IFF_DEBUG) != 0) + log(LOG_DEBUG, "%s: add hash entry: sess_id=%" PRIu32 ", idx=%" PRIu32 "\n", + sc->l2tp_ec.ec_if.if_xname, nvar->lv_my_sess_id, idx); + + mutex_enter(&l2tp_hash.lock); + PSLIST_WRITER_INSERT_HEAD(&l2tp_hash.lists[idx], sc, l2tp_hash); + mutex_exit(&l2tp_hash.lock); + + kmem_free(ovar, sizeof(*ovar)); + return 0; +} + +static int +l2tp_clear_session(struct l2tp_softc *sc) +{ + struct l2tp_variant *nvar; + struct l2tp_variant *ovar; + + nvar = kmem_alloc(sizeof(*nvar), KM_SLEEP); + + mutex_enter(&sc->l2tp_lock); + ovar = sc->l2tp_var; + *nvar = *ovar; + psref_target_init(&nvar->lv_psref, lv_psref_class); + nvar->lv_my_sess_id = 0; + nvar->lv_peer_sess_id = 0; + membar_producer(); + + mutex_enter(&l2tp_hash.lock); + if (ovar->lv_my_sess_id > 0 && ovar->lv_peer_sess_id > 0) { + PSLIST_WRITER_REMOVE(sc, l2tp_hash); + pserialize_perform(l2tp_psz); + } + mutex_exit(&l2tp_hash.lock); + + l2tp_variant_update(sc, nvar); + mutex_exit(&sc->l2tp_lock); + kmem_free(ovar, sizeof(*ovar)); + return 0; +} + +struct l2tp_variant * +l2tp_lookup_session_ref(uint32_t id, struct psref *psref) +{ + int idx; + int s; + struct l2tp_softc *sc; + + idx = id_hash_func(id); + + s = pserialize_read_enter(); + PSLIST_READER_FOREACH(sc, &l2tp_hash.lists[idx], struct l2tp_softc, + l2tp_hash) { + struct l2tp_variant *var = sc->l2tp_var; + if (var == NULL) + continue; + if (var->lv_my_sess_id != id) + continue; + psref_acquire(psref, &var->lv_psref, lv_psref_class); + pserialize_read_exit(s); + return var; + } + pserialize_read_exit(s); + return NULL; +} + +/* + * l2tp_variant update API. + * + * Assumption: + * reader side dereferences sc->l2tp_var in reader critical section only, + * that is, all of reader sides do not reader the sc->l2tp_var after + * pserialize_perform(). + */ +static void +l2tp_variant_update(struct l2tp_softc *sc, struct l2tp_variant *nvar) +{ + struct ifnet *ifp = &sc->l2tp_ec.ec_if; + struct l2tp_variant *ovar = sc->l2tp_var; + + KASSERT(mutex_owned(&sc->l2tp_lock)); + + sc->l2tp_var = nvar; + pserialize_perform(l2tp_psz); + psref_target_destroy(&ovar->lv_psref, lv_psref_class); + + /* + * In the manual of atomic_swap_ptr(3), there is no mention if 2nd + * argument is rewrite or not. So, use sc->l2tp_var instead of nvar. + */ + if (sc->l2tp_var->lv_psrc != NULL && sc->l2tp_var->lv_pdst != NULL) + ifp->if_flags |= IFF_RUNNING; + else + ifp->if_flags &= ~IFF_RUNNING; +} + +static int +l2tp_set_cookie(struct l2tp_softc *sc, uint64_t my_cookie, u_int my_cookie_len, + uint64_t peer_cookie, u_int peer_cookie_len) +{ + struct l2tp_variant *nvar; + + if (my_cookie == 0 || peer_cookie == 0) + return EINVAL; + + if (my_cookie_len != 4 && my_cookie_len != 8 + && peer_cookie_len != 4 && peer_cookie_len != 8) + return EINVAL; + + nvar = kmem_alloc(sizeof(*nvar), KM_SLEEP); + + mutex_enter(&sc->l2tp_lock); + + *nvar = *sc->l2tp_var; + psref_target_init(&nvar->lv_psref, lv_psref_class); + nvar->lv_my_cookie = my_cookie; + nvar->lv_my_cookie_len = my_cookie_len; + nvar->lv_peer_cookie = peer_cookie; + nvar->lv_peer_cookie_len = peer_cookie_len; + nvar->lv_use_cookie = L2TP_COOKIE_ON; + membar_producer(); + l2tp_variant_update(sc, nvar); + + mutex_exit(&sc->l2tp_lock); + + struct ifnet *ifp = &sc->l2tp_ec.ec_if; + if ((ifp->if_flags & IFF_DEBUG) != 0) { + log(LOG_DEBUG, + "%s: set cookie: " + "local cookie_len=%u local cookie=%" PRIu64 ", " + "remote cookie_len=%u remote cookie=%" PRIu64 "\n", + ifp->if_xname, my_cookie_len, my_cookie, + peer_cookie_len, peer_cookie); + } + + return 0; +} + +static void +l2tp_clear_cookie(struct l2tp_softc *sc) +{ + struct l2tp_variant *nvar; + + nvar = kmem_alloc(sizeof(*nvar), KM_SLEEP); + + mutex_enter(&sc->l2tp_lock); + + *nvar = *sc->l2tp_var; + psref_target_init(&nvar->lv_psref, lv_psref_class); + nvar->lv_my_cookie = 0; + nvar->lv_my_cookie_len = 0; + nvar->lv_peer_cookie = 0; + nvar->lv_peer_cookie_len = 0; + nvar->lv_use_cookie = L2TP_COOKIE_OFF; + membar_producer(); + l2tp_variant_update(sc, nvar); + + mutex_exit(&sc->l2tp_lock); +} + +static void +l2tp_set_state(struct l2tp_softc *sc, int state) +{ + struct ifnet *ifp = &sc->l2tp_ec.ec_if; + struct l2tp_variant *nvar; + + nvar = kmem_alloc(sizeof(*nvar), KM_SLEEP); + + mutex_enter(&sc->l2tp_lock); + + *nvar = *sc->l2tp_var; + psref_target_init(&nvar->lv_psref, lv_psref_class); + nvar->lv_state = state; + membar_producer(); + l2tp_variant_update(sc, nvar); + + if (nvar->lv_state == L2TP_STATE_UP) { + ifp->if_link_state = LINK_STATE_UP; + } else { + ifp->if_link_state = LINK_STATE_DOWN; + } + + mutex_exit(&sc->l2tp_lock); + +#ifdef NOTYET +#if NVLAN > 0 + vlan_linkstate_notify(ifp, ifp->if_link_state); +#endif +#endif +} + +static int +l2tp_encap_attach(struct l2tp_variant *var) +{ + int error; + + if (var == NULL || var->lv_psrc == NULL) + return EINVAL; + + switch (var->lv_psrc->sa_family) { +#ifdef INET + case AF_INET: + error = in_l2tp_attach(var); + break; +#endif +#ifdef INET6 + case AF_INET6: + error = in6_l2tp_attach(var); + break; +#endif + default: + error = EINVAL; + break; + } + + return error; +} + +static int +l2tp_encap_detach(struct l2tp_variant *var) +{ + int error; + + if (var == NULL || var->lv_psrc == NULL) + return EINVAL; + + switch (var->lv_psrc->sa_family) { +#ifdef INET + case AF_INET: + error = in_l2tp_detach(var); + break; +#endif +#ifdef INET6 + case AF_INET6: + error = in6_l2tp_detach(var); + break; +#endif + default: + error = EINVAL; + break; + } + + return error; +} + +/* + * TODO: + * unify with gif_check_nesting(). + */ +int +l2tp_check_nesting(struct ifnet *ifp, struct mbuf *m) +{ + struct m_tag *mtag; + int *count; + + mtag = m_tag_find(m, PACKET_TAG_TUNNEL_INFO, NULL); + if (mtag != NULL) { + count = (int *)(mtag + 1); + if (++(*count) > max_l2tp_nesting) { + log(LOG_NOTICE, + "%s: recursively called too many times(%d)\n", + if_name(ifp), + *count); + return EIO; + } + } else { + mtag = m_tag_get(PACKET_TAG_TUNNEL_INFO, sizeof(*count), + M_NOWAIT); + if (mtag != NULL) { + m_tag_prepend(m, mtag); + count = (int *)(mtag + 1); + *count = 0; + } +#ifdef L2TP_DEBUG + else { + log(LOG_DEBUG, + "%s: m_tag_get() failed, recursion calls are not prevented.\n", + if_name(ifp)); + } +#endif + } + + return 0; +} + +/* + * Module infrastructure + */ +#include "if_module.h" + +IF_MODULE(MODULE_CLASS_DRIVER, l2tp, "") + + +/* TODO: IP_TCPMSS support */ +#ifdef IP_TCPMSS +static int l2tp_need_tcpmss_clamp(struct ifnet *); +#ifdef INET +static struct mbuf *l2tp_tcpmss4_clamp(struct ifnet *, struct mbuf *); +#endif +#ifdef INET6 +static struct mbuf *l2tp_tcpmss6_clamp(struct ifnet *, struct mbuf *); +#endif + +struct mbuf * +l2tp_tcpmss_clamp(struct ifnet *ifp, struct mbuf *m) +{ + + if (l2tp_need_tcpmss_clamp(ifp)) { + struct ether_header *eh; + struct ether_vlan_header evh; + + /* save ether header */ + m_copydata(m, 0, sizeof(evh), (void *)&evh); + eh = (struct ether_header *)&evh; + + switch (ntohs(eh->ether_type)) { + case ETHERTYPE_VLAN: /* Ether + VLAN */ + if (m->m_pkthdr.len <= sizeof(struct ether_vlan_header)) + break; + m_adj(m, sizeof(struct ether_vlan_header)); + switch (ntohs(evh.evl_proto)) { +#ifdef INET + case ETHERTYPE_IP: /* Ether + VLAN + IPv4 */ + m = l2tp_tcpmss4_clamp(ifp, m); + if (m == NULL) + return NULL; + break; +#endif /* INET */ +#ifdef INET6 + case ETHERTYPE_IPV6: /* Ether + VLAN + IPv6 */ + m = l2tp_tcpmss6_clamp(ifp, m); + if (m == NULL) + return NULL; + break; +#endif /* INET6 */ + default: + break; + } + /* restore ether header */ + M_PREPEND(m, sizeof(struct ether_vlan_header), + M_DONTWAIT); + if (m == NULL) + return NULL; + *mtod(m, struct ether_vlan_header *) = evh; + break; +#ifdef INET + case ETHERTYPE_IP: /* Ether + IPv4 */ + if (m->m_pkthdr.len <= sizeof(struct ether_header)) + break; + m_adj(m, sizeof(struct ether_header)); + m = l2tp_tcpmss4_clamp(ifp, m); + if (m == NULL) + return NULL; + /* restore ether header */ + M_PREPEND(m, sizeof(struct ether_header), M_DONTWAIT); + if (m == NULL) + return NULL; + *mtod(m, struct ether_header *) = *eh; + break; +#endif /* INET */ +#ifdef INET6 + case ETHERTYPE_IPV6: /* Ether + IPv6 */ + if (m->m_pkthdr.len <= sizeof(struct ether_header)) + break; + m_adj(m, sizeof(struct ether_header)); + m = l2tp_tcpmss6_clamp(ifp, m); + if (m == NULL) + return NULL; + /* restore ether header */ + M_PREPEND(m, sizeof(struct ether_header), M_DONTWAIT); + if (m == NULL) + return NULL; + *mtod(m, struct ether_header *) = *eh; + break; +#endif /* INET6 */ + default: + break; + } + } + + return m; +} + +static int +l2tp_need_tcpmss_clamp(struct ifnet *ifp) +{ + int ret = 0; + +#ifdef INET + if (ifp->if_tcpmss != 0) + ret = 1; +#endif /* INET */ + +#ifdef INET6 + if (ifp->if_tcpmss6 != 0) + ret = 1; +#endif /* INET6 */ + + return ret; +} + +#ifdef INET +static struct mbuf * +l2tp_tcpmss4_clamp(struct ifnet *ifp, struct mbuf *m) +{ + + if (ifp->if_tcpmss != 0) { + return ip_tcpmss(m, (ifp->if_tcpmss < 0) ? + ifp->if_mtu - IP_TCPMSS_EXTLEN : + ifp->if_tcpmss); + } + return m; +} +#endif /* INET */ + +#ifdef INET6 +static struct mbuf * +l2tp_tcpmss6_clamp(struct ifnet *ifp, struct mbuf *m) +{ + int ip6hdrlen; + + if (ifp->if_tcpmss6 != 0 && + ip6_tcpmss_applicable(m, &ip6hdrlen)) { + return ip6_tcpmss(m, ip6hdrlen, + (ifp->if_tcpmss6 < 0) ? + ifp->if_mtu - IP6_TCPMSS_EXTLEN : + ifp->if_tcpmss6); + } + return m; +} +#endif /* INET6 */ + +#endif /* IP_TCPMSS */ Index: src/sys/net/if_l2tp.h diff -u /dev/null src/sys/net/if_l2tp.h:1.1 --- /dev/null Thu Feb 16 08:23:36 2017 +++ src/sys/net/if_l2tp.h Thu Feb 16 08:23:35 2017 @@ -0,0 +1,210 @@ +/* $NetBSD: if_l2tp.h,v 1.1 2017/02/16 08:23:35 knakahara Exp $ */ + +/* + * Copyright (c) 2017 Internet Initiative Japan Inc. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 THE FOUNDATION OR CONTRIBUTORS + * 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. + */ + +/* + * L2TPv3 kernel interface + */ + +#ifndef _NET_IF_L2TP_H_ +#define _NET_IF_L2TP_H_ + +#include <sys/queue.h> +#include <sys/ioccom.h> +#ifdef _KERNEL +#include <sys/psref.h> +#include <sys/pslist.h> +#endif + +#include <net/if_ether.h> +#include <netinet/in.h> + +#define SIOCSL2TPSESSION _IOW('i', 151, struct l2tp_req) +#define SIOCDL2TPSESSION _IOW('i', 152, struct l2tp_req) +#define SIOCSL2TPCOOKIE _IOW('i', 153, struct l2tp_req) +#define SIOCDL2TPCOOKIE _IOW('i', 154, struct l2tp_req) +#define SIOCSL2TPSTATE _IOW('i', 155, struct l2tp_req) +#define SIOCGL2TP SIOCGIFGENERIC + +struct l2tp_req { + int state; + u_int my_cookie_len; + u_int peer_cookie_len; + uint32_t my_sess_id; + uint32_t peer_sess_id; + uint64_t my_cookie; + uint64_t peer_cookie; +}; + +#define L2TP_STATE_UP 1 +#define L2TP_STATE_DOWN 0 + +#define L2TP_COOKIE_ON 1 +#define L2TP_COOKIE_OFF 0 + +#ifdef _KERNEL +extern struct psref_class *lv_psref_class; + +struct l2tp_variant { + struct l2tp_softc *lv_softc; + + struct sockaddr *lv_psrc; /* Physical src addr */ + struct sockaddr *lv_pdst; /* Physical dst addr */ + const struct encaptab *lv_encap_cookie; + + /* L2TP session info */ + int lv_state; + uint32_t lv_my_sess_id; /* my session ID */ + uint32_t lv_peer_sess_id; /* peer session ID */ + + int lv_use_cookie; + u_int lv_my_cookie_len; + u_int lv_peer_cookie_len; + uint64_t lv_my_cookie; /* my cookie */ + uint64_t lv_peer_cookie; /* peer cookie */ + + struct psref_target lv_psref; +}; + +struct l2tp_ro { + struct route lr_ro; + kmutex_t lr_lock; +}; + +struct l2tp_softc { + struct ethercom l2tp_ec; /* common area - must be at the top */ + /* to use ether_input(), we must have this */ + percpu_t *l2tp_ro_percpu; /* struct l2tp_ro */ + struct l2tp_variant *l2tp_var; /* + * reader must use l2tp_getref_variant() + * instead of direct dereference. + */ + kmutex_t l2tp_lock; /* writer lock for l2tp_var */ + + LIST_ENTRY(l2tp_softc) l2tp_list; /* list of all l2tps */ + struct pslist_entry l2tp_hash; /* hashed list to lookup by session id */ +}; + +#define L2TP_ROUTE_TTL 10 + +#define L2TP_MTU (1280) /* Default MTU */ +#define L2TP_MTU_MIN (1280) /* Minimum MTU */ +#define L2TP_MTU_MAX (8192) /* Maximum MTU */ + +/* + * Get l2tp_variant from l2tp_softc. + * + * l2tp_variant itself is protected not to be freed by lv_psref. + * In contrast, sc->sc_var can be changed to NULL even if reader critical + * section. see l2tp_variant_update(). + * So, once a reader dereference sc->sc_var by this API, the reader must not + * re-dereference form sc->sc_var. + */ +static inline struct l2tp_variant * +l2tp_getref_variant(struct l2tp_softc *sc, struct psref *psref) +{ + struct l2tp_variant *var; + int s; + + s = pserialize_read_enter(); + var = sc->l2tp_var; + if (var == NULL) { + pserialize_read_exit(s); + return NULL; + } + membar_datadep_consumer(); + psref_acquire(psref, &var->lv_psref, lv_psref_class); + pserialize_read_exit(s); + + return var; +} + +static inline void +l2tp_putref_variant(struct l2tp_variant *var, struct psref *psref) +{ + + if (var == NULL) + return; + psref_release(psref, &var->lv_psref, lv_psref_class); +} + +static inline bool +l2tp_heldref_variant(struct l2tp_variant *var) +{ + + return psref_held(&var->lv_psref, lv_psref_class); +} + + +/* Prototypes */ +void l2tpattach(int); +void l2tpattach0(struct l2tp_softc *); +void l2tp_input(struct mbuf *, struct ifnet *); +int l2tp_ioctl(struct ifnet *, u_long, void *); + +struct l2tp_variant *l2tp_lookup_session_ref(uint32_t, struct psref *); +int l2tp_check_nesting(struct ifnet *, struct mbuf *); + +/* TODO IP_TCPMSS support */ +#ifdef IP_TCPMSS +struct mbuf *l2tp_tcpmss_clamp(struct ifnet *, struct mbuf *); +#endif /* IP_TCPMSS */ +#endif /* _KERNEL */ + +/* + * Locking notes: + * + l2tp_softc_list is protected by l2tp_list_lock (an adaptive mutex) + * l2tp_softc_list is list of all l2tp_softcs, and it is used to avoid + * unload while busy. + * + l2tp_hashed_list is protected by + * - l2tp_hash_lock (an adaptive mutex) for writer + * - pserialize for reader + * l2tp_hashed_list is hashed list of all l2tp_softcs, and it is used by + * input processing to find appropriate softc. + * + l2tp_softc->l2tp_var is protected by + * - l2tp_softc->l2tp_lock (an adaptive mutex) for writer + * - l2tp_var->lv_psref for reader + * l2tp_softc->l2tp_var is used for variant values while the l2tp tunnel + * exists. + * + struct l2tp_ro->lr_ro is protected by struct l2tp_ro->lr_lock. + * This lock is required to exclude softnet/0 lwp(such as output + * processing softint) and processing lwp(such as DAD timer processing). + * + * Locking order: + * - encap_lock => struct l2tp_softc->l2tp_lock + * Other mutexes must not hold simultaneously. + * + * NOTICE + * - l2tp_softc must not have a variant value while the l2tp tunnel exists. + * Such variant values must be in l2tp_softc->l2tp_var. + * - l2tp_softc->l2tp_var is modified by atomic_swap_ptr() like + * read-copy-update. So, once we dereference l2tp_softc->l2tp_var, we must + * keep the pointer during the same context. If we re-derefence + * l2tp_softc->l2tp_var, the l2tp_var may be other one because of + * concurrent writer processing. + */ +#endif /* _NET_IF_L2TP_H_ */ Index: src/sys/netinet/in_l2tp.c diff -u /dev/null src/sys/netinet/in_l2tp.c:1.1 --- /dev/null Thu Feb 16 08:23:36 2017 +++ src/sys/netinet/in_l2tp.c Thu Feb 16 08:23:35 2017 @@ -0,0 +1,419 @@ +/* $NetBSD: in_l2tp.c,v 1.1 2017/02/16 08:23:35 knakahara Exp $ */ + +/* + * Copyright (c) 2017 Internet Initiative Japan Inc. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 THE FOUNDATION OR CONTRIBUTORS + * 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. + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: in_l2tp.c,v 1.1 2017/02/16 08:23:35 knakahara Exp $"); + +#ifdef _KERNEL_OPT +#include "opt_l2tp.h" +#endif + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> +#include <sys/errno.h> +#include <sys/ioctl.h> +#include <sys/syslog.h> +#include <sys/kernel.h> + +#include <net/if.h> +#include <net/route.h> +#include <net/if_ether.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> +#include <netinet/ip_private.h> +#include <netinet/in_l2tp.h> +#include <netinet/in_var.h> +#include <netinet/ip_encap.h> + +#ifdef ALTQ +#include <altq/altq.h> +#endif + +/* TODO: IP_TCPMSS support */ +#undef IP_TCPMSS +#ifdef IP_TCPMSS +#include <netinet/ip_tcpmss.h> +#endif + +#include <net/if_l2tp.h> + +#include <net/net_osdep.h> + +int ip_l2tp_ttl = L2TP_TTL; + +static void in_l2tp_input(struct mbuf *, int, int); + +static const struct encapsw in_l2tp_encapsw = { + .encapsw4 = { + .pr_input = in_l2tp_input, + .pr_ctlinput = NULL, + } +}; + +static int in_l2tp_match(struct mbuf *, int, int, void *); + +int +in_l2tp_output(struct l2tp_variant *var, struct mbuf *m) +{ + struct l2tp_softc *sc; + struct ifnet *ifp; + struct sockaddr_in *sin_src = satosin(var->lv_psrc); + struct sockaddr_in *sin_dst = satosin(var->lv_pdst); + struct ip iphdr; /* capsule IP header, host byte ordered */ + struct rtentry *rt; + struct l2tp_ro *lro; + int error; + uint32_t sess_id; + + KASSERT(var != NULL); + KASSERT(l2tp_heldref_variant(var)); + KASSERT(sin_src != NULL && sin_dst != NULL); + KASSERT(sin_src->sin_family == AF_INET + && sin_dst->sin_family == AF_INET); + + sc = var->lv_softc; + if (sc == NULL) + return ENETUNREACH; + + ifp = &sc->l2tp_ec.ec_if; + error = l2tp_check_nesting(ifp, m); + if (error) + goto looped; + +#ifdef NETYET +/* TODO: support ALTQ for innner frame */ +#ifdef ALTQ + ALTQ_SAVE_PAYLOAD(m, AF_ETHER); +#endif +#endif + + memset(&iphdr, 0, sizeof(iphdr)); + iphdr.ip_src = sin_src->sin_addr; + /* bidirectional configured tunnel mode */ + if (sin_dst->sin_addr.s_addr != INADDR_ANY) + iphdr.ip_dst = sin_dst->sin_addr; + else { + m_freem(m); + if ((ifp->if_flags & IFF_DEBUG) != 0) + log(LOG_DEBUG, "%s: ENETUNREACH\n", __func__); + error = ENETUNREACH; + goto out; + } + iphdr.ip_p = IPPROTO_L2TP; + /* version will be set in ip_output() */ + iphdr.ip_ttl = ip_l2tp_ttl; + /* outer IP header length */ + iphdr.ip_len = sizeof(struct ip); + /* session-id length */ + iphdr.ip_len += sizeof(uint32_t); + if (var->lv_use_cookie == L2TP_COOKIE_ON) { + /* cookie length */ + iphdr.ip_len += var->lv_peer_cookie_len; + } + +/* TODO: IP_TCPMSS support */ +#ifdef IP_TCPMSS + m = l2tp_tcpmss_clamp(ifp, m); + if (m == NULL) { + error = EINVAL; + goto out; + } +#endif + /* + * payload length + * NOTE: Payload length may be changed in ip_tcpmss(). + * Typical case is missing of TCP mss option in original + * TCP header. + */ + iphdr.ip_len += m->m_pkthdr.len; + HTONS(iphdr.ip_len); + + if (var->lv_use_cookie == L2TP_COOKIE_ON) { + /* prepend session cookie */ + uint32_t cookie_32; + uint64_t cookie_64; + M_PREPEND(m, var->lv_peer_cookie_len, M_DONTWAIT); + if (m && m->m_len < var->lv_peer_cookie_len) + m = m_pullup(m, var->lv_peer_cookie_len); + if (m == NULL) { + error = ENOBUFS; + goto out; + } + if (var->lv_peer_cookie_len == 4) { + cookie_32 = htonl((uint32_t)var->lv_peer_cookie); + memcpy(mtod(m, void *), &cookie_32, + sizeof(uint32_t)); + } else { + cookie_64 = htobe64(var->lv_peer_cookie); + memcpy(mtod(m, void *), &cookie_64, + sizeof(uint64_t)); + } + } + + /* prepend session-ID */ + sess_id = htonl(var->lv_peer_sess_id); + M_PREPEND(m, sizeof(uint32_t), M_DONTWAIT); + if (m && m->m_len < sizeof(uint32_t)) + m = m_pullup(m, sizeof(uint32_t)); + if (m == NULL) { + error = ENOBUFS; + goto out; + } + memcpy(mtod(m, uint32_t *), &sess_id, sizeof(uint32_t)); + + /* prepend new IP header */ + M_PREPEND(m, sizeof(struct ip), M_DONTWAIT); + if (IP_HDR_ALIGNED_P(mtod(m, void *)) == 0) { + if (m) + m = m_copyup(m, sizeof(struct ip), 0); + } else { + if (m && m->m_len < sizeof(struct ip)) + m = m_pullup(m, sizeof(struct ip)); + } + if (m == NULL) { + error = ENOBUFS; + goto out; + } + memcpy(mtod(m, struct ip *), &iphdr, sizeof(struct ip)); + + lro = percpu_getref(sc->l2tp_ro_percpu); + mutex_enter(&lro->lr_lock); + if ((rt = rtcache_lookup(&lro->lr_ro, var->lv_pdst)) == NULL) { + mutex_exit(&lro->lr_lock); + percpu_putref(sc->l2tp_ro_percpu); + m_freem(m); + error = ENETUNREACH; + goto out; + } + + if (rt->rt_ifp == ifp) { + rtcache_unref(rt, &lro->lr_ro); + rtcache_free(&lro->lr_ro); + mutex_exit(&lro->lr_lock); + percpu_putref(sc->l2tp_ro_percpu); + m_freem(m); + error = ENETUNREACH; /*XXX*/ + goto out; + } + rtcache_unref(rt, &lro->lr_ro); + + /* + * To avoid inappropriate rewrite of checksum, + * clear csum flags. + */ + m->m_pkthdr.csum_flags = 0; + + error = ip_output(m, NULL, &lro->lr_ro, 0, NULL, NULL); + mutex_exit(&lro->lr_lock); + percpu_putref(sc->l2tp_ro_percpu); + return error; + +looped: + if (error) + ifp->if_oerrors++; + +out: + return error; +} + +static void +in_l2tp_input(struct mbuf *m, int off, int proto) +{ + struct ifnet *l2tpp = NULL; + struct l2tp_softc *sc; + uint32_t sess_id; + uint32_t cookie_32; + uint64_t cookie_64; + struct psref psref; + struct l2tp_variant *var; + + if (m->m_len < off + sizeof(uint32_t)) { + m = m_pullup(m, off + sizeof(uint32_t)); + if (!m) { + /* if payload length < 4 octets */ + return; + } + } + + /* get L2TP session ID */ + m_copydata(m, off, sizeof(uint32_t), (void *)&sess_id); + NTOHL(sess_id); +#ifdef L2TP_DEBUG + log(LOG_DEBUG, "%s: sess_id = %" PRIu32 "\n", __func__, sess_id); +#endif + if (sess_id == 0) { + /* + * L2TPv3 control packet received. + * userland daemon(l2tpd?) should process. + */ + rip_input(m, off, proto); + return; + } + + var = l2tp_lookup_session_ref(sess_id, &psref); + if (var == NULL) { + m_freem(m); + ip_statinc(IP_STAT_NOL2TP); + return; + } else { + sc = var->lv_softc; + l2tpp = &(sc->l2tp_ec.ec_if); + + if (l2tpp == NULL || (l2tpp->if_flags & IFF_UP) == 0) { +#ifdef L2TP_DEBUG + if (l2tpp == NULL) + log(LOG_DEBUG, "%s: l2tpp is NULL\n", __func__); + else + log(LOG_DEBUG, "%s: l2tpp is down\n", __func__); +#endif + m_freem(m); + ip_statinc(IP_STAT_NOL2TP); + goto out; + } + + /* other CPU do l2tp_delete_tunnel */ + if (var->lv_psrc == NULL || var->lv_pdst == NULL) { + m_freem(m); + ip_statinc(IP_STAT_NOL2TP); + goto out; + } + } + + if (var->lv_state != L2TP_STATE_UP) { + m_freem(m); + goto out; + } + + if (sess_id != var->lv_my_sess_id) { + m_freem(m); + goto out; + } + + m_adj(m, off + sizeof(uint32_t)); + + if (var->lv_use_cookie == L2TP_COOKIE_ON) { + if (var->lv_my_cookie_len == 4) { + m_copydata(m, 0, sizeof(uint32_t), (void *)&cookie_32); + NTOHL(cookie_32); + if (cookie_32 != var->lv_my_cookie) { + m_freem(m); + goto out; + } + m_adj(m, sizeof(uint32_t)); + } else { + m_copydata(m, 0, sizeof(uint64_t), (void *)&cookie_64); + BE64TOH(cookie_64); + if (cookie_64 != var->lv_my_cookie) { + m_freem(m); + goto out; + } + m_adj(m, sizeof(uint64_t)); + } + } + +/* TODO: IP_TCPMSS support */ +#ifdef IP_TCPMSS + m = l2tp_tcpmss_clamp(l2tpp, m); + if (m == NULL) + goto out; +#endif + l2tp_input(m, l2tpp); + +out: + l2tp_putref_variant(var, &psref); + return; +} + +/* + * This function is used by encap4_lookup() to decide priority of the encaptab. + * This priority is compared to the match length between mbuf's source/destination + * IPv4 address pair and encaptab's one. + * l2tp(4) does not use address pairs to search matched encaptab, so this + * function must return the length bigger than or equals to IPv4 address pair to + * avoid wrong encaptab. + */ +static int +in_l2tp_match(struct mbuf *m, int off, int proto, void *arg) +{ + struct l2tp_variant *var = arg; + uint32_t sess_id; + + KASSERT(proto == IPPROTO_L2TP); + + if (m->m_len < off + sizeof(uint32_t)) { + m = m_pullup(m, off + sizeof(uint32_t)); + if (!m) { + /* if payload length < 4 octets */ + return 0; + } + } + + /* get L2TP session ID */ + m_copydata(m, off, sizeof(uint32_t), (void *)&sess_id); + NTOHL(sess_id); + if (sess_id == 0) { + /* + * L2TPv3 control packet received. + * userland daemon(l2tpd?) should process. + */ + return 32 * 2; + } else if (sess_id == var->lv_my_sess_id) + return 32 * 2; + else + return 0; +} + +int +in_l2tp_attach(struct l2tp_variant *var) +{ + + var->lv_encap_cookie = encap_attach_func(AF_INET, IPPROTO_L2TP, + in_l2tp_match, &in_l2tp_encapsw, var); + if (var->lv_encap_cookie == NULL) + return EEXIST; + + return 0; +} + +int +in_l2tp_detach(struct l2tp_variant *var) +{ + int error; + + error = encap_detach(var->lv_encap_cookie); + if (error == 0) + var->lv_encap_cookie = NULL; + + return error; +} Index: src/sys/netinet/in_l2tp.h diff -u /dev/null src/sys/netinet/in_l2tp.h:1.1 --- /dev/null Thu Feb 16 08:23:36 2017 +++ src/sys/netinet/in_l2tp.h Thu Feb 16 08:23:35 2017 @@ -0,0 +1,41 @@ +/* $NetBSD: in_l2tp.h,v 1.1 2017/02/16 08:23:35 knakahara Exp $ */ + +/* + * Copyright (c) 2017 Internet Initiative Japan Inc. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 THE FOUNDATION OR CONTRIBUTORS + * 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. + */ + +#ifndef _NETINET_IN_L2TP_H_ +#define _NETINET_IN_L2TP_H_ + +#include <net/if.h> +#include <net/if_l2tp.h> + +#define L2TP_TTL 64 + +int in_l2tp_output(struct l2tp_variant *, struct mbuf *); +int in_l2tp_attach(struct l2tp_variant *); +int in_l2tp_detach(struct l2tp_variant *); + +#endif /*_NETINET_IN_L2TP_H_*/ Index: src/sys/netinet6/in6_l2tp.c diff -u /dev/null src/sys/netinet6/in6_l2tp.c:1.1 --- /dev/null Thu Feb 16 08:23:36 2017 +++ src/sys/netinet6/in6_l2tp.c Thu Feb 16 08:23:36 2017 @@ -0,0 +1,414 @@ +/* $NetBSD: in6_l2tp.c,v 1.1 2017/02/16 08:23:36 knakahara Exp $ */ + +/* + * Copyright (c) 2017 Internet Initiative Japan Inc. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 THE FOUNDATION OR CONTRIBUTORS + * 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. + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: in6_l2tp.c,v 1.1 2017/02/16 08:23:36 knakahara Exp $"); + +#ifdef _KERNEL_OPT +#include "opt_l2tp.h" +#endif + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> +#include <sys/errno.h> +#include <sys/ioctl.h> +#include <sys/syslog.h> +#include <sys/kernel.h> + +#include <net/if.h> +#include <net/route.h> +#include <net/if_ether.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> +#include <netinet/ip_private.h> +#include <netinet/in_l2tp.h> +#include <netinet/in_var.h> +#include <netinet/ip_encap.h> + +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet6/in6_l2tp.h> + +#ifdef ALTQ +#include <altq/altq.h> +#endif + +#include <net/if_vlanvar.h> + +/* TODO: IP_TCPMSS support */ +#undef IP_TCPMSS +#ifdef IP_TCPMSS +#include <netinet/ip_tcpmss.h> +#endif + +#include <net/if_l2tp.h> + +#define L2TP_HLIM6 64 +int ip6_l2tp_hlim = L2TP_HLIM6; + +static int in6_l2tp_input(struct mbuf **, int *, int); + +static const struct encapsw in6_l2tp_encapsw = { + .encapsw6 = { + .pr_input = in6_l2tp_input, + .pr_ctlinput = NULL, + } +}; + +static int in6_l2tp_match(struct mbuf *, int, int, void *); + +int +in6_l2tp_output(struct l2tp_variant *var, struct mbuf *m) +{ + struct rtentry *rt; + struct l2tp_ro *lro; + struct l2tp_softc *sc; + struct ifnet *ifp; + struct sockaddr_in6 *sin6_src = satosin6(var->lv_psrc); + struct sockaddr_in6 *sin6_dst = satosin6(var->lv_pdst); + struct ip6_hdr ip6hdr; /* capsule IP header, host byte ordered */ + int error; + uint32_t sess_id; + + KASSERT(var != NULL); + KASSERT(l2tp_heldref_variant(var)); + KASSERT(sin6_src != NULL && sin6_dst != NULL); + KASSERT(sin6_src->sin6_family == AF_INET6 + && sin6_dst->sin6_family == AF_INET6); + + sc = var->lv_softc; + ifp = &sc->l2tp_ec.ec_if; + error = l2tp_check_nesting(ifp, m); + if (error) + goto looped; + +#ifdef NOTYET +/* TODO: support ALTQ for innner frame */ +#ifdef ALTQ + ALTQ_SAVE_PAYLOAD(m, AF_ETHER); +#endif +#endif + + memset(&ip6hdr, 0, sizeof(ip6hdr)); + ip6hdr.ip6_src = sin6_src->sin6_addr; + /* bidirectional configured tunnel mode */ + if (!IN6_IS_ADDR_UNSPECIFIED(&sin6_dst->sin6_addr)) + ip6hdr.ip6_dst = sin6_dst->sin6_addr; + else { + m_freem(m); + if ((ifp->if_flags & IFF_DEBUG) != 0) + log(LOG_DEBUG, "%s: ENETUNREACH\n", __func__); + return ENETUNREACH; + } + /* unlike IPv4, IP version must be filled by caller of ip6_output() */ + ip6hdr.ip6_vfc = 0x60; + ip6hdr.ip6_nxt = IPPROTO_L2TP; + ip6hdr.ip6_hlim = ip6_l2tp_hlim; + /* outer IP payload length */ + ip6hdr.ip6_plen = 0; + /* session-id length */ + ip6hdr.ip6_plen += sizeof(uint32_t); + if (var->lv_use_cookie == L2TP_COOKIE_ON) { + /* cookie length */ + ip6hdr.ip6_plen += var->lv_peer_cookie_len; + } + +/* TODO: IP_TCPMSS support */ +#ifdef IP_TCPMSS + m = l2tp_tcpmss_clamp(ifp, m); + if (m == NULL) + return EINVAL; +#endif + + /* + * payload length + * NOTE: Payload length may be changed in ip_tcpmss(). + * Typical case is missing of TCP mss option in original + * TCP header. + */ + ip6hdr.ip6_plen += m->m_pkthdr.len; + HTONS(ip6hdr.ip6_plen); + + if (var->lv_use_cookie == L2TP_COOKIE_ON) { + /* prepend session cookie */ + uint32_t cookie_32; + uint64_t cookie_64; + M_PREPEND(m, var->lv_peer_cookie_len, M_DONTWAIT); + if (m && m->m_len < var->lv_peer_cookie_len) + m = m_pullup(m, var->lv_peer_cookie_len); + if (m == NULL) + return ENOBUFS; + if (var->lv_peer_cookie_len == 4) { + cookie_32 = htonl((uint32_t)var->lv_peer_cookie); + memcpy(mtod(m, void *), &cookie_32, + sizeof(uint32_t)); + } else { + cookie_64 = htobe64(var->lv_peer_cookie); + memcpy(mtod(m, void *), &cookie_64, + sizeof(uint64_t)); + } + } + + /* prepend session-ID */ + sess_id = htonl(var->lv_peer_sess_id); + M_PREPEND(m, sizeof(uint32_t), M_DONTWAIT); + if (m && m->m_len < sizeof(uint32_t)) + m = m_pullup(m, sizeof(uint32_t)); + if (m == NULL) + return ENOBUFS; + memcpy(mtod(m, uint32_t *), &sess_id, sizeof(uint32_t)); + + /* prepend new IP header */ + M_PREPEND(m, sizeof(struct ip6_hdr), M_DONTWAIT); + if (IP_HDR_ALIGNED_P(mtod(m, void *)) == 0) { + if (m) + m = m_copyup(m, sizeof(struct ip), 0); + } else { + if (m && m->m_len < sizeof(struct ip6_hdr)) + m = m_pullup(m, sizeof(struct ip6_hdr)); + } + if (m == NULL) + return ENOBUFS; + memcpy(mtod(m, struct ip6_hdr *), &ip6hdr, sizeof(struct ip6_hdr)); + + lro = percpu_getref(sc->l2tp_ro_percpu); + mutex_enter(&lro->lr_lock); + if ((rt = rtcache_lookup(&lro->lr_ro, var->lv_pdst)) == NULL) { + mutex_exit(&lro->lr_lock); + percpu_putref(sc->l2tp_ro_percpu); + m_freem(m); + return ENETUNREACH; + } + + /* If the route constitutes infinite encapsulation, punt. */ + if (rt->rt_ifp == ifp) { + rtcache_unref(rt, &lro->lr_ro); + rtcache_free(&lro->lr_ro); + mutex_exit(&lro->lr_lock); + percpu_putref(sc->l2tp_ro_percpu); + m_freem(m); + return ENETUNREACH; /* XXX */ + } + rtcache_unref(rt, &lro->lr_ro); + + /* + * To avoid inappropriate rewrite of checksum, + * clear csum flags. + */ + m->m_pkthdr.csum_flags = 0; + + error = ip6_output(m, 0, &lro->lr_ro, 0, NULL, NULL, NULL); + mutex_exit(&lro->lr_lock); + percpu_putref(sc->l2tp_ro_percpu); + return(error); + +looped: + if (error) + ifp->if_oerrors++; + + return error; +} + +static int +in6_l2tp_input(struct mbuf **mp, int *offp, int proto) +{ + struct mbuf *m = *mp; + int off = *offp; + + struct ifnet *l2tpp = NULL; + struct l2tp_softc *sc; + struct l2tp_variant *var; + uint32_t sess_id; + uint32_t cookie_32; + uint64_t cookie_64; + struct psref psref; + + if (m->m_len < off + sizeof(uint32_t)) { + m = m_pullup(m, off + sizeof(uint32_t)); + if (!m) { + /* if payload length < 4 octets */ + return IPPROTO_DONE; + } + *mp = m; + } + + /* get L2TP session ID */ + m_copydata(m, off, sizeof(uint32_t), (void *)&sess_id); + NTOHL(sess_id); +#ifdef L2TP_DEBUG + log(LOG_DEBUG, "%s: sess_id = %" PRIu32 "\n", __func__, sess_id); +#endif + if (sess_id == 0) { + /* + * L2TPv3 control packet received. + * userland daemon(l2tpd?) should process. + */ + return rip6_input(mp, offp, proto); + } + + var = l2tp_lookup_session_ref(sess_id, &psref); + if (var == NULL) { + m_freem(m); + IP_STATINC(IP_STAT_NOL2TP); + return IPPROTO_DONE; + } else { + sc = var->lv_softc; + l2tpp = &(sc->l2tp_ec.ec_if); + + if (l2tpp == NULL || (l2tpp->if_flags & IFF_UP) == 0) { +#ifdef L2TP_DEBUG + if (l2tpp == NULL) + log(LOG_DEBUG, "%s: l2tpp is NULL\n", __func__); + else + log(LOG_DEBUG, "%s: l2tpp is down\n", __func__); +#endif + m_freem(m); + IP_STATINC(IP_STAT_NOL2TP); + goto out; + } + /* other CPU do l2tp_delete_tunnel */ + if (var->lv_psrc == NULL || var->lv_pdst == NULL) { + m_freem(m); + ip_statinc(IP_STAT_NOL2TP); + goto out; + } + } + + if (var->lv_state != L2TP_STATE_UP) { + m_freem(m); + goto out; + } + + if (sess_id != var->lv_my_sess_id) { + m_freem(m); + goto out; + } + + m_adj(m, off + sizeof(uint32_t)); + + if (var->lv_use_cookie == L2TP_COOKIE_ON) { + if (var->lv_my_cookie_len == 4) { + m_copydata(m, 0, sizeof(uint32_t), (void *)&cookie_32); + NTOHL(cookie_32); + if (cookie_32 != var->lv_my_cookie) { + m_freem(m); + goto out; + } + m_adj(m, sizeof(uint32_t)); + } else { + m_copydata(m, 0, sizeof(uint64_t), (void *)&cookie_64); + BE64TOH(cookie_64); + if (cookie_64 != var->lv_my_cookie) { + m_freem(m); + goto out; + } + m_adj(m, sizeof(uint64_t)); + } + } + +/* TODO: IP_TCPMSS support */ +#ifdef IP_TCPMSS + m = l2tp_tcpmss_clamp(l2tpp, m); + if (m == NULL) + goto out; +#endif + l2tp_input(m, l2tpp); + +out: + l2tp_putref_variant(var, &psref); + return IPPROTO_DONE; +} + +/* + * This function is used by encap6_lookup() to decide priority of the encaptab. + * This priority is compared to the match length between mbuf's source/destination + * IPv6 address pair and encaptab's one. + * l2tp(4) does not use address pairs to search matched encaptab, so this + * function must return the length bigger than or equals to IPv6 address pair to + * avoid wrong encaptab. + */ +static int +in6_l2tp_match(struct mbuf *m, int off, int proto, void *arg) +{ + struct l2tp_variant *var = arg; + uint32_t sess_id; + + KASSERT(proto == IPPROTO_L2TP); + + if (m->m_len < off + sizeof(uint32_t)) { + m = m_pullup(m, off + sizeof(uint32_t)); + if (!m) { + /* if payload length < 4 octets */ + return 0; + } + } + + /* get L2TP session ID */ + m_copydata(m, off, sizeof(uint32_t), (void *)&sess_id); + NTOHL(sess_id); + if (sess_id == 0) { + /* + * L2TPv3 control packet received. + * userland daemon(l2tpd?) should process. + */ + return 128 * 2; + } else if (sess_id == var->lv_my_sess_id) + return 128 * 2; + else + return 0; +} + +int +in6_l2tp_attach(struct l2tp_variant *var) +{ + + var->lv_encap_cookie = encap_attach_func(AF_INET6, IPPROTO_L2TP, + in6_l2tp_match, &in6_l2tp_encapsw, var); + if (var->lv_encap_cookie == NULL) + return EEXIST; + + return 0; +} + +int +in6_l2tp_detach(struct l2tp_variant *var) +{ + int error; + + error = encap_detach(var->lv_encap_cookie); + if (error == 0) + var->lv_encap_cookie = NULL; + + return error; +} Index: src/sys/netinet6/in6_l2tp.h diff -u /dev/null src/sys/netinet6/in6_l2tp.h:1.1 --- /dev/null Thu Feb 16 08:23:36 2017 +++ src/sys/netinet6/in6_l2tp.h Thu Feb 16 08:23:36 2017 @@ -0,0 +1,39 @@ +/* $NetBSD: in6_l2tp.h,v 1.1 2017/02/16 08:23:36 knakahara Exp $ */ + +/* + * Copyright (c) 2017 Internet Initiative Japan Inc. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 THE FOUNDATION OR CONTRIBUTORS + * 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. + */ + +#ifndef _NETINET6_IN6_L2TP_H_ +#define _NETINET6_IN6_L2TP_H_ + +#include <net/if.h> +#include <net/if_l2tp.h> + +int in6_l2tp_output(struct l2tp_variant *, struct mbuf *); +int in6_l2tp_attach(struct l2tp_variant *); +int in6_l2tp_detach(struct l2tp_variant *); + +#endif /*_NETINET6_IN6_L2TP_H_*/