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_*/