diff --git a/distrib/sets/lists/base/shl.mi b/distrib/sets/lists/base/shl.mi index d149f3f..0ff731a 100644 --- a/distrib/sets/lists/base/shl.mi +++ b/distrib/sets/lists/base/shl.mi @@ -700,6 +700,9 @@ ./usr/lib/librumpnet_gif.so base-rump-shlib rump ./usr/lib/librumpnet_gif.so.0 base-rump-shlib rump ./usr/lib/librumpnet_gif.so.0.0 base-rump-shlib rump +./usr/lib/librumpnet_l2tp.so base-rump-shlib rump +./usr/lib/librumpnet_l2tp.so.0 base-rump-shlib rump +./usr/lib/librumpnet_l2tp.so.0.0 base-rump-shlib rump ./usr/lib/librumpnet_local.so base-rump-shlib rump ./usr/lib/librumpnet_local.so.0 base-rump-shlib rump ./usr/lib/librumpnet_local.so.0.0 base-rump-shlib rump diff --git a/distrib/sets/lists/comp/mi b/distrib/sets/lists/comp/mi index 6b7cb52..75c64fe 100644 --- a/distrib/sets/lists/comp/mi +++ b/distrib/sets/lists/comp/mi @@ -2214,6 +2214,7 @@ ./usr/include/net/if_hippi.h comp-c-include ./usr/include/net/if_ieee1394.h comp-c-include ./usr/include/net/if_ieee80211.h comp-obsolete obsolete +./usr/include/net/if_l2tp.h comp-c-include ./usr/include/net/if_llc.h comp-c-include ./usr/include/net/if_media.h comp-c-include ./usr/include/net/if_mpls.h comp-c-include @@ -2295,6 +2296,7 @@ ./usr/include/netinet/igmp_var.h comp-c-include ./usr/include/netinet/in.h comp-c-include ./usr/include/netinet/in_gif.h comp-c-include +./usr/include/netinet/in_l2tp.h comp-c-include ./usr/include/netinet/in_pcb.h comp-c-include ./usr/include/netinet/in_pcb_hdr.h comp-c-include ./usr/include/netinet/in_route.h comp-obsolete obsolete @@ -2348,6 +2350,8 @@ ./usr/include/netinet6/in6_gif.h comp-c-include ./usr/include/netinet6/in6_ifattach.h comp-c-include ./usr/include/netinet6/in6_pcb.h comp-c-include +./usr/include/netinet6/in6_gif.h comp-c-include +./usr/include/netinet6/in6_l2tp.h comp-c-include ./usr/include/netinet6/in6_var.h comp-c-include ./usr/include/netinet6/ip6.h comp-obsolete obsolete ./usr/include/netinet6/ip6_mroute.h comp-c-include @@ -3547,6 +3551,8 @@ ./usr/lib/librumpnet_bridge_p.a comp-c-proflib rump,profile ./usr/lib/librumpnet_gif.a comp-c-lib rump ./usr/lib/librumpnet_gif_p.a comp-c-proflib rump,profile +./usr/lib/librumpnet_l2tp.a comp-c-lib rump +./usr/lib/librumpnet_l2tp_p.a comp-c-proflib rump,profile ./usr/lib/librumpnet_local.a comp-c-lib rump ./usr/lib/librumpnet_local_p.a comp-c-proflib rump,profile ./usr/lib/librumpnet_net.a comp-c-lib rump diff --git a/distrib/sets/lists/comp/shl.mi b/distrib/sets/lists/comp/shl.mi index e51e4fd..eb23b97 100644 --- a/distrib/sets/lists/comp/shl.mi +++ b/distrib/sets/lists/comp/shl.mi @@ -218,6 +218,7 @@ ./usr/lib/librumpnet_bpfjit_pic.a comp-c-piclib picinstall,rump,sljit ./usr/lib/librumpnet_bridge_pic.a comp-c-piclib picinstall,rump ./usr/lib/librumpnet_gif_pic.a comp-c-piclib picinstall,rump +./usr/lib/librumpnet_l2tp_pic.a comp-c-piclib picinstall,rump ./usr/lib/librumpnet_local_pic.a comp-c-piclib picinstall,rump ./usr/lib/librumpnet_net80211_pic.a comp-c-piclib picinstall,rump ./usr/lib/librumpnet_net_pic.a comp-c-piclib picinstall,rump diff --git a/distrib/sets/lists/debug/mi b/distrib/sets/lists/debug/mi index b355b9c..fb75a22 100644 --- a/distrib/sets/lists/debug/mi +++ b/distrib/sets/lists/debug/mi @@ -211,6 +211,7 @@ ./usr/lib/librumpnet_bridge_g.a comp-c-debuglib debuglib,rump ./usr/lib/librumpnet_g.a comp-c-debuglib debuglib,compatfile,rump ./usr/lib/librumpnet_gif_g.a comp-c-debuglib debuglib,rump +./usr/lib/librumpnet_l2tp_g.a comp-c-debuglib debuglib,rump ./usr/lib/librumpnet_local_g.a comp-c-debuglib debuglib,rump ./usr/lib/librumpnet_net80211_g.a comp-c-debuglib debuglib,rump ./usr/lib/librumpnet_net_g.a comp-c-debuglib debuglib,rump diff --git a/distrib/sets/lists/debug/shl.mi b/distrib/sets/lists/debug/shl.mi index 6067227..1fba425 100644 --- a/distrib/sets/lists/debug/shl.mi +++ b/distrib/sets/lists/debug/shl.mi @@ -239,6 +239,7 @@ ./usr/libdata/debug/usr/lib/librumpnet_bpfjit.so.0.0.debug comp-rump-debug debug,rump,sljit ./usr/libdata/debug/usr/lib/librumpnet_bridge.so.0.0.debug comp-rump-debug debug,rump ./usr/libdata/debug/usr/lib/librumpnet_gif.so.0.0.debug comp-rump-debug debug,rump +./usr/libdata/debug/usr/lib/librumpnet_l2tp.so.0.0.debug comp-rump-debug debug,rump ./usr/libdata/debug/usr/lib/librumpnet_local.so.0.0.debug comp-rump-debug debug,rump ./usr/libdata/debug/usr/lib/librumpnet_net.so.0.0.debug comp-rump-debug debug,rump ./usr/libdata/debug/usr/lib/librumpnet_net80211.so.0.0.debug comp-rump-debug debug,rump diff --git a/distrib/sets/lists/man/mi b/distrib/sets/lists/man/mi index 40ac808..29096ef 100644 --- a/distrib/sets/lists/man/mi +++ b/distrib/sets/lists/man/mi @@ -1359,6 +1359,7 @@ ./usr/share/man/cat4/ksyms.0 man-sys-catman .cat ./usr/share/man/cat4/kttcp.0 man-sys-catman .cat ./usr/share/man/cat4/kue.0 man-sys-catman .cat +./usr/share/man/cat4/l2tp.0 man-sys-catman .cat ./usr/share/man/cat4/lc.0 man-sys-catman .cat ./usr/share/man/cat4/ld.0 man-sys-catman .cat ./usr/share/man/cat4/le.0 man-sys-catman .cat @@ -4429,6 +4430,7 @@ ./usr/share/man/html4/ksyms.html man-sys-htmlman html ./usr/share/man/html4/kttcp.html man-sys-htmlman html ./usr/share/man/html4/kue.html man-sys-htmlman html +./usr/share/man/html4/l2tp.html man-sys-htmlman html ./usr/share/man/html4/lc.html man-sys-htmlman html ./usr/share/man/html4/ld.html man-sys-htmlman html ./usr/share/man/html4/le.html man-sys-htmlman html @@ -7339,6 +7341,7 @@ ./usr/share/man/man4/ksyms.4 man-sys-man .man ./usr/share/man/man4/kttcp.4 man-sys-man .man ./usr/share/man/man4/kue.4 man-sys-man .man +./usr/share/man/man4/l2tp.4 man-sys-man .man ./usr/share/man/man4/lc.4 man-sys-man .man ./usr/share/man/man4/ld.4 man-sys-man .man ./usr/share/man/man4/le.4 man-sys-man .man diff --git a/distrib/sets/lists/modules/mi b/distrib/sets/lists/modules/mi index d3417b4..62b648c 100644 --- a/distrib/sets/lists/modules/mi +++ b/distrib/sets/lists/modules/mi @@ -150,6 +150,8 @@ ./@MODULEDIR@/if_gif/if_gif.kmod base-kernel-modules kmod ./@MODULEDIR@/if_gre base-kernel-modules kmod ./@MODULEDIR@/if_gre/if_gre.kmod base-kernel-modules kmod +./@MODULEDIR@/if_l2tp base-kernel-modules kmod +./@MODULEDIR@/if_l2tp/if_l2tp.kmod base-kernel-modules kmod ./@MODULEDIR@/if_loop base-kernel-modules kmod ./@MODULEDIR@/if_loop/if_loop.kmod base-kernel-modules kmod ./@MODULEDIR@/if_mpls base-kernel-modules kmod diff --git a/distrib/sets/lists/tests/mi b/distrib/sets/lists/tests/mi index 67909c5..00f0d99 100644 --- a/distrib/sets/lists/tests/mi +++ b/distrib/sets/lists/tests/mi @@ -3250,6 +3250,10 @@ ./usr/tests/net/if_gif/Atffile tests-net-tests atf,rump ./usr/tests/net/if_gif/Kyuafile tests-net-tests atf,rump,kyua ./usr/tests/net/if_gif/t_gif tests-net-tests atf,rump +./usr/tests/net/if_l2tp tests-net-tests compattestfile,atf +./usr/tests/net/if_l2tp/Atffile tests-net-tests atf,rump +./usr/tests/net/if_l2tp/Kyuafile tests-net-tests atf,rump,kyua +./usr/tests/net/if_l2tp/t_l2tp tests-net-tests atf,rump ./usr/tests/net/if_loop tests-net-tests compattestfile,atf ./usr/tests/net/if_loop/Atffile tests-net-tests atf,rump ./usr/tests/net/if_loop/Kyuafile tests-net-tests atf,rump,kyua diff --git a/etc/mtree/NetBSD.dist.tests b/etc/mtree/NetBSD.dist.tests index 1fdcf0e..7ceca06 100644 --- a/etc/mtree/NetBSD.dist.tests +++ b/etc/mtree/NetBSD.dist.tests @@ -339,6 +339,7 @@ ./usr/tests/net/if ./usr/tests/net/if_bridge ./usr/tests/net/if_gif +./usr/tests/net/if_l2tp ./usr/tests/net/if_loop ./usr/tests/net/if_pppoe ./usr/tests/net/if_tap diff --git a/sbin/ifconfig/Makefile.inc b/sbin/ifconfig/Makefile.inc index 60245ae..47bbdd3 100644 --- a/sbin/ifconfig/Makefile.inc +++ b/sbin/ifconfig/Makefile.inc @@ -21,3 +21,4 @@ SRCS+= parse.c SRCS+= tunnel.c SRCS+= util.c SRCS+= vlan.c +SRCS+= l2tp.c diff --git a/sbin/ifconfig/ifconfig.8 b/sbin/ifconfig/ifconfig.8 index 8df166c..e44d12e 100644 --- a/sbin/ifconfig/ifconfig.8 +++ b/sbin/ifconfig/ifconfig.8 @@ -29,7 +29,7 @@ .\" .\" @(#)ifconfig.8 8.4 (Berkeley) 6/1/94 .\" -.Dd January 7, 2016 +.Dd January 19, 2017 .Dt IFCONFIG 8 .Os .Sh NAME @@ -534,6 +534,26 @@ UDP header. Unconfigure the physical source and destination address for IP tunnel interfaces previously configured with .Cm tunnel . +.It Cm session Ar local_session Ar remote_session +.Oc +.Pq L2TPv3 devices only +Configure local session id and remote session id for L2TPv3 +interface. The length of session id is 4 bytes. +.It Cm deletesession +Unconfigure the local session id and remote session id for +L2TPv3 interface previously configured with +.Cm session . +.It Cm cookie Ar local_cookie_length Ar local_cookie Ar remote_cookie_length Ar remote_cookie +.Oc +.Pq L2TPv3 devices only +Configure local cookie and remote cookie for L2TPv3 interface. +The cookie length must be 4 or 8 bytes. +Generally, cookies are mangaed by daemon. So, this command would +be used for test or debug only. +.It Cm deletecookie +Unconfigure the local cookie and remote cookie for L2TPv3 +interface previously configured with +.Cm cookie . .It Cm create Create the specified network pseudo-device. .It Cm destroy diff --git a/sbin/ifconfig/l2tp.c b/sbin/ifconfig/l2tp.c new file mode 100644 index 0000000..597dcdf --- /dev/null +++ b/sbin/ifconfig/l2tp.c @@ -0,0 +1,270 @@ +/* $NetBSD$ */ + +/* + * 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 +__RCSID("$NetBSD$"); + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "env.h" +#include "extern.h" +#include "util.h" + +static status_func_t status; +static usage_func_t usage; +static cmdloop_branch_t branch; + +static void l2tp_constructor(void) __attribute__((constructor)); +static void l2tp_status(prop_dictionary_t, prop_dictionary_t); + +static int setl2tpsession(prop_dictionary_t, prop_dictionary_t); +static int deletel2tpsession(prop_dictionary_t, prop_dictionary_t); +static int setl2tpcookie(prop_dictionary_t, prop_dictionary_t); +static int deletel2tpcookie(prop_dictionary_t, prop_dictionary_t); + +struct pinteger l2tpremotesession = PINTEGER_INITIALIZER1(&l2tpremotesession, + "remote session id", 0, UINT_MAX, 10, setl2tpsession, "l2tpremotesession", + &command_root.pb_parser); + +struct pinteger l2tplocalsession = PINTEGER_INITIALIZER1(&l2tplocalsession, + "local session id", 0, UINT_MAX, 10, NULL, "l2tplocalsession", + &l2tpremotesession.pi_parser); + +struct pinteger l2tpremotecookie = PINTEGER_INITIALIZER1(&l2tpremotecookie, + "remote cookie", INT64_MIN, INT64_MAX, 10, setl2tpcookie, "l2tpremotecookie", + &command_root.pb_parser); + +struct pinteger l2tpremotecookielen = PINTEGER_INITIALIZER1(&l2tpremotecookielen, + "remote cookie length", 0, UINT16_MAX, 10, NULL, "l2tpremotecookielen", + &l2tpremotecookie.pi_parser); + +struct pinteger l2tplocalcookie = PINTEGER_INITIALIZER1(&l2tplocalcookie, + "local cookie", INT64_MIN, INT64_MAX, 10, NULL, "l2tplocalcookie", + &l2tpremotecookielen.pi_parser); + +struct pinteger l2tplocalcookielen = PINTEGER_INITIALIZER1(&l2tplocalcookielen, + "local cookie length", 0, UINT16_MAX, 10, NULL, "l2tplocalcookielen", + &l2tplocalcookie.pi_parser); + +static const struct kwinst l2tpkw[] = { + {.k_word = "cookie", .k_nextparser = &l2tplocalcookielen.pi_parser} + ,{.k_word = "deletecookie", .k_exec = deletel2tpcookie, + .k_nextparser = &command_root.pb_parser} + ,{.k_word = "session", .k_nextparser = &l2tplocalsession.pi_parser} + ,{.k_word = "deletesession", .k_exec = deletel2tpsession, + .k_nextparser = &command_root.pb_parser} +}; + +struct pkw l2tp = PKW_INITIALIZER(&l2tp, "l2tp", NULL, NULL, + l2tpkw, __arraycount(l2tpkw), NULL); + +#define L2TP_COOKIE_LOCAL 0 +#define L2TP_COOKIE_REMOTE 1 + +static int +checkifname(prop_dictionary_t env) +{ + const char *ifname; + + if ((ifname = getifname(env)) == NULL) + return 1; + + return strncmp(ifname, "l2tp", 4) != 0 || + !isdigit((unsigned char)ifname[4]); +} + +static int +getl2tp(prop_dictionary_t env, struct l2tp_req *l2tpr, bool quiet) +{ + memset(l2tpr, 0, sizeof(*l2tpr)); + + if (checkifname(env)) { + if (quiet) + return -1; + errx(EXIT_FAILURE, "valid only with l2tp(4) interfaces"); + } + + if (indirect_ioctl(env, SIOCGL2TP, l2tpr) == -1) + return -1; + + return 0; +} + +int +deletel2tpsession(prop_dictionary_t env, prop_dictionary_t oenv) +{ + struct l2tp_req l2tpr; + + memset(&l2tpr, 0, sizeof(l2tpr)); + + if (indirect_ioctl(env, SIOCDL2TPSESSION, &l2tpr) == -1) + return -1; + + l2tpr.state = L2TP_STATE_DOWN; + + if (indirect_ioctl(env, SIOCSL2TPSTATE, &l2tpr) == -1) + return -1; + + + return 0; +} + +int +setl2tpsession(prop_dictionary_t env, prop_dictionary_t oenv) +{ + struct l2tp_req l2tpr; + int64_t local_session; + int64_t remote_session; + + memset(&l2tpr, 0, sizeof(l2tpr)); + + if (!prop_dictionary_get_int64(env, "l2tplocalsession", + &local_session)) { + errno = ENOENT; + return -1; + } + + if (!prop_dictionary_get_int64(env, "l2tpremotesession", + &remote_session)) { + errno = ENOENT; + return -1; + } + + l2tpr.my_sess_id = local_session; + l2tpr.peer_sess_id = remote_session; + + if (indirect_ioctl(env, SIOCSL2TPSESSION, &l2tpr) == -1) + return -1; + + l2tpr.state = L2TP_STATE_UP; + + if (indirect_ioctl(env, SIOCSL2TPSTATE, &l2tpr) == -1) + return -1; + + return 0; +} + +int +deletel2tpcookie(prop_dictionary_t env, prop_dictionary_t oenv) +{ + struct l2tp_req l2tpr; + + memset(&l2tpr, 0, sizeof(l2tpr)); + + if (indirect_ioctl(env, SIOCDL2TPCOOKIE, &l2tpr) == -1) + return -1; + + return 0; +} + +int +setl2tpcookie(prop_dictionary_t env, prop_dictionary_t oenv) +{ + struct l2tp_req l2tpr; + uint16_t cookielen; + uint64_t cookie; + + memset(&l2tpr, 0, sizeof(l2tpr)); + + if (!prop_dictionary_get_uint16(env, "l2tplocalcookielen", &cookielen)) { + errno = ENOENT; + return -1; + } + if (!prop_dictionary_get_uint64(env, "l2tplocalcookie", &cookie)) { + errno = ENOENT; + return -1; + } + l2tpr.my_cookie_len = cookielen; + l2tpr.my_cookie = cookie; + + if (!prop_dictionary_get_uint16(env, "l2tpremotecookielen", &cookielen)) { + errno = ENOENT; + return -1; + } + if (!prop_dictionary_get_uint64(env, "l2tpremotecookie", &cookie)) { + errno = ENOENT; + return -1; + } + l2tpr.peer_cookie_len = cookielen; + l2tpr.peer_cookie = cookie; + + if (indirect_ioctl(env, SIOCSL2TPCOOKIE, &l2tpr) == -1) + return -1; + + return 0; +} + +static void +l2tp_status(prop_dictionary_t env, prop_dictionary_t oenv) +{ + struct l2tp_req l2tpr; + + if (getl2tp(env, &l2tpr, true) == -1) + return; + + if (l2tpr.my_sess_id != 0 || l2tpr.peer_sess_id != 0) { + printf("\tlocal-session-id: %u\n", l2tpr.my_sess_id); + printf("\tremote-session-id: %u\n", l2tpr.peer_sess_id); + } + + if (l2tpr.my_cookie != 0 || l2tpr.peer_cookie != 0) { + printf("\tlocal-cookie: %" PRIu64 "\n", l2tpr.my_cookie); + printf("\tremote-cookie: %" PRIu64 "\n", l2tpr.peer_cookie); + } +} + +static void +l2tp_usage(prop_dictionary_t env) +{ + fprintf(stderr, "\t[ session local-session-id remote-session-id ]\n"); + fprintf(stderr, "\t[ cookie local-cookie-length local-cookie remote-cookie-length remote-cookie ]\n"); +} + +static void +l2tp_constructor(void) +{ + cmdloop_branch_init(&branch, &l2tp.pk_parser); + register_cmdloop_branch(&branch); + status_func_init(&status, l2tp_status); + usage_func_init(&usage, l2tp_usage); + register_status(&status); + register_usage(&usage); +} diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile index bcfe683..a64e385 100644 --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -38,7 +38,7 @@ MAN= aac.4 ac97.4 acardide.4 aceride.4 acphy.4 \ ixpide.4 \ jme.4 jmide.4 joy.4 \ kloader.4 kse.4 ksyms.4 kttcp.4 \ - lc.4 ld.4 lii.4 lo.4 lua.4 lxtphy.4 \ + l2tp.4 lc.4 ld.4 lii.4 lo.4 lua.4 lxtphy.4 \ mainbus.4 makphy.4 malo.4 mbe.4 mca.4 mcclock.4 md.4 mfb.4 \ mfi.4 mhzc.4 \ micphy.4 midi.4 mii.4 mk48txx.4 mlx.4 mly.4 mpls.4 mpii.4 mpt.4 mpu.4 mtd.4 \ diff --git a/share/man/man4/l2tp.4 b/share/man/man4/l2tp.4 new file mode 100644 index 0000000..0c7702a --- /dev/null +++ b/share/man/man4/l2tp.4 @@ -0,0 +1,178 @@ +.\" $NetBSD$ +.\" +.\" 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. +.\" 3. Neither the name of the project nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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. +.\" +.Dd January 19, 2017 +.Dt L2TP 4 +.Os +.Sh NAME +.Nm l2tp +.Nd layer two tunneling protocol version 3 +.Sh SYNOPSIS +.Cd "pseudo-device l2tp" +.Sh DESCRIPTION +The +.Nm +interface is a version 3 of the Layer Two Tunneling Protocol(L2TPv3) +pseudo device. It can tunnel layer 2 protocol traffic over IPv4 or IPv6, +as specified in +.Li RFC3931 . +.Pp +L2TPv3 protocol is comprised of two types of messages, control messages +and data messages. Control messages are used in the establishment, +maintenace, and clearing of control connections and sessions. +.Xr l2tp 4 +interface can send control messages and data messages, furthermore +the management of control messages is entrusted to userland daemon. +Without management daemon, +.Xr l2tp 4 +interface can send data messages by using +.Xr ifconfig 8 +.Cm tunnel +and +.Cm session +subcommands, or +.Dv SIOCSIFPHYADDR +and +.Dv SIOCSL2TPSESSION +ioctls. Additionally, it can use cookie specified in +.Li RFC3931 +by using +.Xr ifconfig 8 +.Cm cookie +subcommand, or +.Dv SIOCSL2TPCOOKIE +ioctl. +.Ss Packet format +Layer 2 frames are prepended with a L2TPv3 header as described by +RFC 3931. +The resulting L2TPv3 packets will be encapsulated in an outer packet, +which may be either an IPv4 or IPv6 packet, with IP protocol number 115. +.Sh EXAMPLES +Configuration example: +.Bd -literal +Host X--NetBSD A ----------------tunnel---------- NetBSD B------Host E + \\ | + \\ / + +-----Router B--------Router C---------+ + +.Ed +configuration example without cookies, +.Pp +On +.Nx +system A +.Bd -literal +# ifconfig wm0 inet 192.168.0.1/24 +# ifconfig l2tp0 create +# ifconfig l2tp0 tunnel 192.168.0.1 192.168.0.2 +# ifconfig l2tp0 session 1234 4321 +# ifconfig bridge0 create +# brconfig bridge0 add wm1 +# brconfig bridge0 add l2tp0 +# ifconfig l2tp0 up +# ifconfig wm1 up +# ifconfig bridge0 up +.Ed +.Pp +.Ed +On +.Nx +system B +.Bd -literal +# ifconfig wm0 inet 192.168.0.2/24 +# ifconfig l2tp0 create +# ifconfig l2tp0 tunnel 192.168.0.2 192.168.0.1 +# ifconfig l2tp0 session 4321 1234 +# ifconfig bridge0 create +# brconfig bridge0 add wm1 +# brconfig bridge0 add l2tp0 +# ifconfig l2tp0 up +# ifconfig wm1 up +# ifconfig bridge0 up +.Ed +.Pp +configuration example with cookies, +.Pp +On +.Nx +system A +.Bd -literal +# ifconfig wm0 inet 192.168.0.1/24 +# ifconfig l2tp0 create +# ifconfig l2tp0 tunnel 192.168.0.1 192.168.0.2 +# ifconfig l2tp0 session 1234 4321 +# ifconfig l2tp0 cookie 4 12345 4 54321 +# ifconfig bridge0 create +# brconfig bridge0 add wm1 +# brconfig bridge0 add l2tp0 +# ifconfig l2tp0 up +# ifconfig wm1 up +# ifconfig bridge0 up +.Ed +.Pp +.Ed +On +.Nx +system B +.Bd -literal +# ifconfig wm0 inet 192.168.0.2/24 +# ifconfig l2tp0 create +# ifconfig l2tp0 tunnel 192.168.0.2 192.168.0.1 +# ifconfig l2tp0 session 4321 1234 +# ifconfig l2tp0 cookie 4 54321 4 12345 +# ifconfig bridge0 create +# brconfig bridge0 add wm1 +# brconfig bridge0 add l2tp0 +# ifconfig l2tp0 up +# ifconfig wm1 up +# ifconfig bridge0 up +.Ed + +.Sh SEE ALSO +.Xr inet 4 , +.Xr inet6 4 , +.Xr ifconfig 8 +.Rs +.%A J. Lau, Ed. +.%A M. Townsley, Ed. +.%A I. Goyret, Ed. +.%B RFC 3931 +.%T Layer Two Tunneling Protocol - Version 3 (L2TPv3) +.%D March 2005 +.%U ftp://ftp.ietf.org/rfc/rfc3931.txt +.Re +.Sh HISTORY +The +.Nm +device first appeared in +.Nx 8.0 . +.Sh BUGS +Currently, +.Nm +interface support ethernet frame over IPv4 or IPv6 only. diff --git a/sys/arch/amd64/conf/ALL b/sys/arch/amd64/conf/ALL index b407442..d5d7945 100644 --- a/sys/arch/amd64/conf/ALL +++ b/sys/arch/amd64/conf/ALL @@ -1573,6 +1573,7 @@ pseudo-device vlan # IEEE 802.1q encapsulation pseudo-device bridge # simple inter-network bridging options BRIDGE_IPF # bridge uses IP/IPv6 pfil hooks too pseudo-device agr # IEEE 802.3ad link aggregation +pseudo-device l2tp # L2TPv3 interface #pseudo-device pf # PF packet filter #pseudo-device pflog # PF log if #pseudo-device pfsync # PF sync if diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC index 4c3a58c..18db8bc 100644 --- a/sys/arch/amd64/conf/GENERIC +++ b/sys/arch/amd64/conf/GENERIC @@ -1271,6 +1271,7 @@ pseudo-device vlan # IEEE 802.1q encapsulation pseudo-device bridge # simple inter-network bridging #options BRIDGE_IPF # bridge uses IP/IPv6 pfil hooks too pseudo-device agr # IEEE 802.3ad link aggregation +pseudo-device l2tp # L2TPv3 interface #pseudo-device pf # PF packet filter #pseudo-device pflog # PF log if #pseudo-device pfsync # PF sync if diff --git a/sys/conf/files b/sys/conf/files index 49e8b6c..0559377 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -120,6 +120,8 @@ defflag opt_fileassoc.h FILEASSOC defflag opt_gre.h GRE_DEBUG +defparam opt_l2tp.h L2TP_ID_HASH_SIZE + # Write Ahead Physical Block Logging defflag opt_wapbl.h WAPBL WAPBL_DEBUG defparam opt_wapbl.h WAPBL_DEBUG_PRINT @@ -1435,6 +1437,7 @@ defpseudo stf: ifnet defpseudodev tap: ifnet, ether, arp defpseudo carp: ifnet, ether, arp defpseudodev etherip: ifnet, ether, arp +defpseudodev l2tp: ifnet, ether, arp defpseudo sequencer defpseudo clockctl diff --git a/sys/modules/Makefile b/sys/modules/Makefile index ec39f42..56ccf38 100644 --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -55,6 +55,7 @@ SUBDIR+= if_axen SUBDIR+= if_faith SUBDIR+= if_gif SUBDIR+= if_gre +SUBDIR+= if_l2tp SUBDIR+= if_loop SUBDIR+= if_mpls SUBDIR+= if_npflog diff --git a/sys/modules/if_l2tp/Makefile b/sys/modules/if_l2tp/Makefile new file mode 100644 index 0000000..a565523 --- /dev/null +++ b/sys/modules/if_l2tp/Makefile @@ -0,0 +1,14 @@ +# $NetBSD$ + +.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 diff --git a/sys/modules/if_l2tp/l2tp.ioconf b/sys/modules/if_l2tp/l2tp.ioconf new file mode 100644 index 0000000..500b0f40 --- /dev/null +++ b/sys/modules/if_l2tp/l2tp.ioconf @@ -0,0 +1,7 @@ +# $NetBSD$ + +ioconf l2tp + +include "conf/files" + +pseudo-device l2tp diff --git a/sys/net/Makefile b/sys/net/Makefile index 7ba90fa..e5816a0 100644 --- a/sys/net/Makefile +++ b/sys/net/Makefile @@ -5,7 +5,7 @@ INCSDIR= /usr/include/net INCS= bpf.h bpfjit.h bpfdesc.h dlt.h ethertypes.h if.h if_arc.h if_arp.h \ if_atm.h if_bridgevar.h if_dl.h if_ether.h if_etherip.h if_fddi.h if_gif.h \ if_gre.h if_hippi.h if_ieee1394.h if_llc.h if_media.h if_mpls.h \ - if_pflog.h if_ppp.h if_pppoe.h if_sppp.h if_srt.h if_stf.h \ + if_pflog.h if_ppp.h if_pppoe.h if_l2tp.h if_sppp.h if_srt.h if_stf.h \ if_tap.h if_token.h if_tun.h if_types.h if_vlanvar.h net_stats.h \ netisr.h pfil.h pfkeyv2.h pfvar.h ppp-comp.h ppp_defs.h radix.h \ raw_cb.h route.h slcompress.h slip.h zlib.h diff --git a/sys/net/files.net b/sys/net/files.net index 44440f4..454a061 100644 --- a/sys/net/files.net +++ b/sys/net/files.net @@ -23,6 +23,7 @@ file net/if_gre.c gre needs-flag file net/if_hippisubr.c hippi needs-flag file net/if_ieee1394subr.c ieee1394 file net/if_llatbl.c inet | inet6 +file net/if_l2tp.c l2tp needs-flag file net/if_loop.c loop file net/if_media.c net file net/if_mpls.c mpls needs-flag @@ -58,12 +59,14 @@ file netinet/if_atm.c atm file netinet/in4_cksum.c inet file netinet/in_cksum.c inet file netinet/in_gif.c gif & inet +file netinet/in_l2tp.c l2tp & inet file netinet/ip_carp.c carp & (inet | inet6) needs-flag file netinet/ip_ecn.c ipsec | gif | stf file netinet/ip_encap.c inet | inet6 file netinet/ip_etherip.c etherip & inet file netinet6/ip6_etherip.c etherip & inet6 file netinet6/in6_gif.c gif & inet6 +file netinet6/in6_l2tp.c l2tp & inet6 include "net/agr/files.agr" diff --git a/sys/net/if_bridge.c b/sys/net/if_bridge.c index 76f0ebd..0f2a116 100644 --- a/sys/net/if_bridge.c +++ b/sys/net/if_bridge.c @@ -735,11 +735,6 @@ bridge_ioctl_add(struct bridge_softc *sc, void *arg) if (ifs == NULL) return (ENOENT); - if (sc->sc_if.if_mtu != ifs->if_mtu) { - error = EINVAL; - goto out; - } - if (ifs->if_bridge == sc) { error = EEXIST; goto out; @@ -765,6 +760,12 @@ bridge_ioctl_add(struct bridge_softc *sc, void *arg) switch (ifs->if_type) { case IFT_ETHER: + if (sc->sc_if.if_mtu != ifs->if_mtu) { + error = EINVAL; + goto out; + } + /* FALLTHROUGH */ + case IFT_L2TP: if ((error = ether_enable_vlan_mtu(ifs)) > 0) goto out; /* @@ -840,6 +841,7 @@ bridge_ioctl_del(struct bridge_softc *sc, void *arg) switch (ifs->if_type) { case IFT_ETHER: + case IFT_L2TP: /* * Take the interface out of promiscuous mode. * Don't call it with holding a spin lock. @@ -898,6 +900,7 @@ bridge_ioctl_sifflags(struct bridge_softc *sc, void *arg) if (req->ifbr_ifsflags & IFBIF_STP) { switch (bif->bif_ifp->if_type) { case IFT_ETHER: + case IFT_L2TP: /* These can do spanning tree. */ break; diff --git a/sys/net/if_l2tp.c b/sys/net/if_l2tp.c new file mode 100644 index 0000000..f3ec953 --- /dev/null +++ b/sys/net/if_l2tp.c @@ -0,0 +1,1493 @@ +/* $NetBSD$ */ + +/* + * 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 +__KERNEL_RCSID(0, "$NetBSD$"); + +#ifdef _KERNEL_OPT +#include "opt_inet.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#ifdef INET +#include +#include +#endif /* INET */ +#ifdef INET6 +#include +#endif + +#include + +#if NVLAN > 0 +#include +#endif + +/* TODO: IP_TCPMSS support */ +#undef IP_TCPMSS +#ifdef IP_TCPMSS +#include +#endif + +#include +#include + +/* + * 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; + } + 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; + 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; + + 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; + + 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)); + + membar_producer(); + atomic_swap_ptr(&sc->l2tp_var, nvar); + pserialize_perform(l2tp_psz); + psref_target_destroy(&ovar->lv_psref, lv_psref_class); + + if (nvar->lv_psrc != NULL && nvar->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); + + memcpy(nvar, sc->l2tp_var, sizeof(*nvar)); + 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; + 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); + + memcpy(nvar, sc->l2tp_var, sizeof(*nvar)); + 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; + 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); + + memcpy(nvar, sc->l2tp_var, sizeof(*nvar)); + psref_target_init(&nvar->lv_psref, lv_psref_class); + nvar->lv_state = state; + + 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 */ diff --git a/sys/net/if_l2tp.h b/sys/net/if_l2tp.h new file mode 100644 index 0000000..55f0773 --- /dev/null +++ b/sys/net/if_l2tp.h @@ -0,0 +1,210 @@ +/* $NetBSD$ */ + +/* + * 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 +#include +#ifdef _KERNEL +#include +#include +#endif + +#include +#include + +#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_ */ diff --git a/sys/net/if_types.h b/sys/net/if_types.h index eff6d53..a3937fe 100644 --- a/sys/net/if_types.h +++ b/sys/net/if_types.h @@ -263,6 +263,7 @@ #define IFT_FAITH 0xf2 #define IFT_PFLOG 0xf5 /* Packet filter logging */ #define IFT_PFSYNC 0xf6 /* Packet filter state syncing */ +#define IFT_L2TP 0xf7 /* L2TPv3 I/F */ #define IFT_CARP 0xf8 /* Common Address Redundancy Protocol */ #endif /* !_NET_IF_TYPES_H_ */ diff --git a/sys/netinet/Makefile b/sys/netinet/Makefile index 17f500c..5357c28 100644 --- a/sys/netinet/Makefile +++ b/sys/netinet/Makefile @@ -3,7 +3,7 @@ INCSDIR= /usr/include/netinet INCS= dccp.h icmp6.h icmp_var.h if_atm.h if_ether.h if_inarp.h igmp.h \ - igmp_var.h in.h in_gif.h in_pcb.h in_pcb_hdr.h \ + igmp_var.h in.h in_gif.h in_l2tp.h in_pcb.h in_pcb_hdr.h \ in_selsrc.h in_systm.h \ in_var.h ip.h ip_carp.h ip6.h ip_ecn.h ip_encap.h \ ip_icmp.h ip_mroute.h ip_var.h pim.h pim_var.h portalgo.h \ diff --git a/sys/netinet/in.h b/sys/netinet/in.h index 85ac10d..c606e32 100644 --- a/sys/netinet/in.h +++ b/sys/netinet/in.h @@ -105,6 +105,7 @@ typedef __sa_family_t sa_family_t; #define IPPROTO_IPCOMP 108 /* IP Payload Comp. Protocol */ #define IPPROTO_VRRP 112 /* VRRP RFC 2338 */ #define IPPROTO_CARP 112 /* Common Address Resolution Protocol */ +#define IPPROTO_L2TP 115 /* L2TPv3 */ #define IPPROTO_SCTP 132 /* SCTP */ #define IPPROTO_PFSYNC 240 /* PFSYNC */ #define IPPROTO_RAW 255 /* raw IP packet */ diff --git a/sys/netinet/in_l2tp.c b/sys/netinet/in_l2tp.c new file mode 100644 index 0000000..5b43fa2 --- /dev/null +++ b/sys/netinet/in_l2tp.c @@ -0,0 +1,417 @@ +/* $NetBSD$ */ + +/* + * 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 +__KERNEL_RCSID(0, "$NetBSD$"); + +#ifdef _KERNEL_OPT +#include "opt_l2tp.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef ALTQ +#include +#endif + +/* TODO: IP_TCPMSS support */ +#undef IP_TCPMSS +#ifdef IP_TCPMSS +#include +#endif + +#include + +#include + +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; + +/* TODO: support ALTQ for innner frame */ +#ifdef ALTQ + ALTQ_SAVE_PAYLOAD(m, AF_ETHER); +#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; +} diff --git a/sys/netinet/in_l2tp.h b/sys/netinet/in_l2tp.h new file mode 100644 index 0000000..2a268fe --- /dev/null +++ b/sys/netinet/in_l2tp.h @@ -0,0 +1,41 @@ +/* $NetBSD$ */ + +/* + * 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 +#include + +#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_*/ diff --git a/sys/netinet/in_proto.c b/sys/netinet/in_proto.c index 5534847..e318a7b 100644 --- a/sys/netinet/in_proto.c +++ b/sys/netinet/in_proto.c @@ -360,6 +360,16 @@ const struct protosw inetsw[] = { .pr_init = carp_init, }, #endif /* NCARP > 0 */ +{ .pr_type = SOCK_RAW, + .pr_domain = &inetdomain, + .pr_protocol = IPPROTO_L2TP, + .pr_flags = PR_ATOMIC|PR_ADDR|PR_LASTHDR, + .pr_input = encap4_input, + .pr_ctlinput = rip_ctlinput, + .pr_ctloutput = rip_ctloutput, + .pr_usrreqs = &rip_usrreqs, /*XXX*/ + .pr_init = encap_init, +}, #if NPFSYNC > 0 { .pr_type = SOCK_RAW, .pr_domain = &inetdomain, diff --git a/sys/netinet/ip_var.h b/sys/netinet/ip_var.h index 5d5b5a7..78e1a77 100644 --- a/sys/netinet/ip_var.h +++ b/sys/netinet/ip_var.h @@ -157,8 +157,9 @@ struct ip_moptions { #define IP_STAT_TOOLONG 27 /* ip length > max ip packet size */ #define IP_STAT_NOGIF 28 /* no match gif found */ #define IP_STAT_BADADDR 29 /* invalid address on header */ +#define IP_STAT_NOL2TP 30 /* no match l2tp found */ -#define IP_NSTATS 30 +#define IP_NSTATS 31 #ifdef _KERNEL diff --git a/sys/netinet6/Makefile b/sys/netinet6/Makefile index 189a43a..e2fb55e 100644 --- a/sys/netinet6/Makefile +++ b/sys/netinet6/Makefile @@ -2,7 +2,7 @@ INCSDIR= /usr/include/netinet6 -INCS= in6.h in6_gif.h in6_ifattach.h in6_pcb.h \ +INCS= in6.h in6_gif.h in6_l2tp.h in6_ifattach.h in6_pcb.h \ in6_var.h ip6_mroute.h ip6_var.h ip6protosw.h \ mld6_var.h nd6.h pim6.h pim6_var.h \ raw_ip6.h udp6.h udp6_var.h diff --git a/sys/netinet6/in6_ifattach.c b/sys/netinet6/in6_ifattach.c index 3b8a651..5b7545b 100644 --- a/sys/netinet6/in6_ifattach.c +++ b/sys/netinet6/in6_ifattach.c @@ -712,6 +712,7 @@ in6_ifattach(struct ifnet *ifp, struct ifnet *altifp) /* some of the interfaces are inherently not IPv6 capable */ switch (ifp->if_type) { case IFT_BRIDGE: + case IFT_L2TP: #ifdef IFT_PFLOG case IFT_PFLOG: #endif diff --git a/sys/netinet6/in6_l2tp.c b/sys/netinet6/in6_l2tp.c new file mode 100644 index 0000000..9a4dd03 --- /dev/null +++ b/sys/netinet6/in6_l2tp.c @@ -0,0 +1,412 @@ +/* $NetBSD$ */ + +/* + * 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 +__KERNEL_RCSID(0, "$NetBSD$"); + +#ifdef _KERNEL_OPT +#include "opt_l2tp.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef ALTQ +#include +#endif + +#include + +/* TODO: IP_TCPMSS support */ +#undef IP_TCPMSS +#ifdef IP_TCPMSS +#include +#endif + +#include + +#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; + +/* TODO: support ALTQ for innner frame */ +#ifdef ALTQ + ALTQ_SAVE_PAYLOAD(m, AF_ETHER); +#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; +} diff --git a/sys/netinet6/in6_l2tp.h b/sys/netinet6/in6_l2tp.h new file mode 100644 index 0000000..92e1305 --- /dev/null +++ b/sys/netinet6/in6_l2tp.h @@ -0,0 +1,39 @@ +/* $NetBSD$ */ + +/* + * 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 +#include + +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_*/ diff --git a/sys/netinet6/in6_proto.c b/sys/netinet6/in6_proto.c index f685b05..fb29eb3 100644 --- a/sys/netinet6/in6_proto.c +++ b/sys/netinet6/in6_proto.c @@ -390,6 +390,16 @@ const struct ip6protosw inet6sw[] = { #endif /* NCARP */ { .pr_type = SOCK_RAW, .pr_domain = &inet6domain, + .pr_protocol = IPPROTO_L2TP, + .pr_flags = PR_ATOMIC|PR_ADDR|PR_LASTHDR, + .pr_input = encap6_input, + .pr_ctlinput = rip6_ctlinput, + .pr_ctloutput = rip6_ctloutput, + .pr_usrreqs = &rip6_usrreqs, + .pr_init = encap_init, +}, +{ .pr_type = SOCK_RAW, + .pr_domain = &inet6domain, .pr_protocol = IPPROTO_PIM, .pr_flags = PR_ATOMIC|PR_ADDR|PR_LASTHDR, .pr_input = pim6_input, diff --git a/sys/rump/include/opt/l2tp.h b/sys/rump/include/opt/l2tp.h new file mode 100644 index 0000000..9efaf37 --- /dev/null +++ b/sys/rump/include/opt/l2tp.h @@ -0,0 +1,3 @@ +/* $NetBSD$ */ + +/* dummy */ diff --git a/sys/rump/net/Makefile.rumpnetcomp b/sys/rump/net/Makefile.rumpnetcomp index 26eb36b..a5cc269 100644 --- a/sys/rump/net/Makefile.rumpnetcomp +++ b/sys/rump/net/Makefile.rumpnetcomp @@ -4,7 +4,7 @@ .include RUMPNETCOMP= agr bridge net net80211 netbt netinet netinet6 -RUMPNETCOMP+= gif netmpls npf local pppoe shmif tap tun vlan +RUMPNETCOMP+= gif netmpls npf l2tp local pppoe shmif tap tun vlan .if ${MKSLJIT} != "no" || make(rumpdescribe) RUMPNETCOMP+= bpfjit diff --git a/sys/rump/net/lib/libl2tp/L2TP.ioconf b/sys/rump/net/lib/libl2tp/L2TP.ioconf new file mode 100644 index 0000000..500b0f40 --- /dev/null +++ b/sys/rump/net/lib/libl2tp/L2TP.ioconf @@ -0,0 +1,7 @@ +# $NetBSD$ + +ioconf l2tp + +include "conf/files" + +pseudo-device l2tp diff --git a/sys/rump/net/lib/libl2tp/Makefile b/sys/rump/net/lib/libl2tp/Makefile new file mode 100644 index 0000000..10dd151 --- /dev/null +++ b/sys/rump/net/lib/libl2tp/Makefile @@ -0,0 +1,16 @@ +# $NetBSD$ +# + +.PATH: ${.CURDIR}/../../../../net ${.CURDIR}/../../../../netinet \ + ${.CURDIR}/../../../../netinet6 + +LIB= rumpnet_l2tp +COMMENT= L2TPv3 interface + +IOCONF= L2TP.ioconf +SRCS= if_l2tp.c in_l2tp.c in6_l2tp.c + +SRCS+= l2tp_component.c + +.include +.include diff --git a/sys/rump/net/lib/libl2tp/l2tp_component.c b/sys/rump/net/lib/libl2tp/l2tp_component.c new file mode 100644 index 0000000..8e5833c --- /dev/null +++ b/sys/rump/net/lib/libl2tp/l2tp_component.c @@ -0,0 +1,42 @@ +/* $NetBSD$ */ + +/* + * 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 AUTHOR ``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 AUTHOR 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 +__KERNEL_RCSID(0, "$NetBSD$"); + +#include + +#include + +int l2tpattach(int); + +RUMP_COMPONENT(RUMP_COMPONENT_NET_IF) +{ + + l2tpattach(0); +} diff --git a/sys/rump/net/lib/libnet/net_component.c b/sys/rump/net/lib/libnet/net_component.c index 9af5e0f..cedd0a0 100644 --- a/sys/rump/net/lib/libnet/net_component.c +++ b/sys/rump/net/lib/libnet/net_component.c @@ -36,6 +36,7 @@ __KERNEL_RCSID(0, "$NetBSD: net_component.c,v 1.8 2017/01/17 02:02:27 christos E #include #include +#include #include #include diff --git a/tests/net/Makefile b/tests/net/Makefile index 8268e4b..e03682f 100644 --- a/tests/net/Makefile +++ b/tests/net/Makefile @@ -6,8 +6,9 @@ TESTSDIR= ${TESTSBASE}/net TESTS_SUBDIRS= fdpass in_cksum net sys .if (${MKRUMP} != "no") && !defined(BSD_MK_COMPAT_FILE) -TESTS_SUBDIRS+= arp bpf bpfilter carp icmp if if_bridge if_gif if_loop -TESTS_SUBDIRS+= if_pppoe if_tap if_tun mcast mpls ndp npf route if_vlan +TESTS_SUBDIRS+= arp bpf bpfilter carp icmp if if_bridge if_gif if_l2tp +TESTS_SUBDIRS+= if_loop if_pppoe if_tap if_tun mcast mpls ndp npf route +TESTS_SUBDIRS+= if_vlan .if (${MKSLJIT} != "no") TESTS_SUBDIRS+= bpfjit .endif diff --git a/tests/net/if_l2tp/Makefile b/tests/net/if_l2tp/Makefile new file mode 100644 index 0000000..73c6c3c --- /dev/null +++ b/tests/net/if_l2tp/Makefile @@ -0,0 +1,13 @@ +# $NetBSD$ +# + +.include + +TESTSDIR= ${TESTSBASE}/net/if_l2tp + +.for name in l2tp +TESTS_SH+= t_${name} +TESTS_SH_SRC_t_${name}= ../net_common.sh t_${name}.sh +.endfor + +.include diff --git a/tests/net/if_l2tp/t_l2tp.sh b/tests/net/if_l2tp/t_l2tp.sh new file mode 100644 index 0000000..5ba3ec0 --- /dev/null +++ b/tests/net/if_l2tp/t_l2tp.sh @@ -0,0 +1,440 @@ +# $NetBSD$ +# +# 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. +# + +LAC1SOCK=unix://commsock1 +LAC2SOCK=unix://commsock2 +CLIENT1SOCK=unix://commsock3 +CLIENT2SOCK=unix://commsock4 + +WAN_LINK=bus0 +LAC1_LAN_LINK=bus1 +LAC2_LAN_LINK=bus2 + +LAC1_WANIP=10.0.0.1 +LAC1_SESSION=1234 +CLIENT1_LANIP=192.168.1.1 +LAC2_WANIP=10.0.0.2 +LAC2_SESSION=4321 +CLIENT2_LANIP=192.168.1.2 + +LAC1_WANIP6=fc00::1 +CLIENT1_LANIP6=fc00:1::1 +LAC2_WANIP6=fc00::2 +CLIENT2_LANIP6=fc00:1::2 + +TIMEOUT=5 +DEBUG=${DEBUG:-false} + +setup_lac() +{ + sock=${1} + lanlink=${2} + wan=${3} + wan_mode=${4} + + + rump_server_add_iface ${sock} shmif0 ${lanlink} + rump_server_add_iface ${sock} shmif1 ${WAN_LINK} + + export RUMP_SERVER=${sock} + + if [ ${wan_mode} = "ipv6" ]; then + atf_check -s exit:0 rump.ifconfig shmif1 inet6 ${wan} + else + atf_check -s exit:0 rump.ifconfig shmif1 inet ${wan} netmask 0xff000000 + fi + atf_check -s exit:0 rump.ifconfig shmif0 up + atf_check -s exit:0 rump.ifconfig shmif1 up + + unset RUMP_SERVER +} + +test_lac() +{ + sock=${1} + wan=${2} + wan_mode=${3} + + export RUMP_SERVER=${sock} + + atf_check -s exit:0 -o match:shmif0 rump.ifconfig + atf_check -s exit:0 -o match:shmif1 rump.ifconfig + if [ ${wan_mode} = "ipv6" ]; then + atf_check -s exit:0 -o ignore rump.ping6 -n -c 1 -X $TIMEOUT ${wan} + else + atf_check -s exit:0 -o ignore rump.ping -n -c 1 -w $TIMEOUT ${wan} + fi + + unset RUMP_SERVER +} + +setup_client() +{ + sock=${1} + lanlink=${2} + lan=${3} + lan_mode=${4} + + rump_server_add_iface ${sock} shmif0 ${lanlink} + + export RUMP_SERVER=${sock} + if [ ${lan_mode} = "ipv6" ]; then + atf_check -s exit:0 rump.ifconfig shmif0 inet6 ${lan} + else + atf_check -s exit:0 rump.ifconfig shmif0 inet ${lan} netmask 0xffffff00 + fi + atf_check -s exit:0 rump.ifconfig shmif0 up + + unset RUMP_SERVER +} + +test_client() +{ + sock=${1} + lan=${2} + lan_mode=${3} + + export RUMP_SERVER=${sock} + + atf_check -s exit:0 -o match:shmif0 rump.ifconfig + if [ ${lan_mode} = "ipv6" ]; then + atf_check -s exit:0 -o ignore rump.ping6 -n -c 1 -X $TIMEOUT ${lan} + else + atf_check -s exit:0 -o ignore rump.ping -n -c 1 -w $TIMEOUT ${lan} + fi + + unset RUMP_SERVER +} + +setup() +{ + lan_mode=${1} + wan_mode=${2} + + rump_server_start $LAC1SOCK netinet6 bridge l2tp + rump_server_start $LAC2SOCK netinet6 bridge l2tp + rump_server_start $CLIENT1SOCK netinet6 bridge l2tp + rump_server_start $CLIENT2SOCK netinet6 bridge l2tp + + client1_lan="" + client2_lan="" + if [ ${lan_mode} = "ipv6" ]; then + client1_lan=${CLIENT1_LANIP6} + client2_lan=${CLIENT2_LANIP6} + else + client1_lan=${CLIENT1_LANIP} + client2_lan=${CLIENT2_LANIP} + fi + + if [ ${wan_mode} = "ipv6" ]; then + setup_lac $LAC1SOCK $LAC1_LAN_LINK $LAC1_WANIP6 ${wan_mode} + setup_lac $LAC2SOCK $LAC2_LAN_LINK $LAC2_WANIP6 ${wan_mode} + setup_client $CLIENT1SOCK $LAC1_LAN_LINK \ + ${client1_lan} ${lan_mode} + setup_client $CLIENT2SOCK $LAC2_LAN_LINK \ + ${client2_lan} ${lan_mode} + else + setup_lac $LAC1SOCK $LAC1_LAN_LINK $LAC1_WANIP ${wan_mode} + setup_lac $LAC2SOCK $LAC2_LAN_LINK $LAC2_WANIP ${wan_mode} + setup_client $CLIENT1SOCK $LAC1_LAN_LINK \ + ${client1_lan} ${lan_mode} + setup_client $CLIENT2SOCK $LAC2_LAN_LINK \ + ${client2_lan} ${lan_mode} + fi +} + +test_setup() +{ + lan_mode=${1} + wan_mode=${2} + + client1_lan="" + client2_lan="" + if [ ${lan_mode} = "ipv6" ]; then + client1_lan=$CLIENT1_LANIP6 + client2_lan=$CLIENT2_LANIP6 + else + client1_lan=$CLIENT1_LANIP + client2_lan=$CLIENT2_LANIP + fi + if [ ${wan_mode} = "ipv6" ]; then + test_lac ${LAC1SOCK} $LAC1_WANIP6 ${wan_mode} + test_lac ${LAC2SOCK} $LAC2_WANIP6 ${wan_mode} + test_client ${CLIENT1SOCK} ${client1_lan} ${lan_mode} + test_client ${CLIENT2SOCK} ${client2_lan} ${lan_mode} + else + test_lac ${LAC1SOCK} $LAC1_WANIP ${wan_mode} + test_lac ${LAC2SOCK} $LAC2_WANIP ${wan_mode} + test_client ${CLIENT1SOCK} ${client1_lan} ${lan_mode} + test_client ${CLIENT2SOCK} ${client2_lan} ${lan_mode} + fi +} + +setup_if_l2tp() +{ + sock=${1} + src=${2} + dst=${3} + src_session=${4} + dst_session=${5} + + export RUMP_SERVER=${sock} + + atf_check -s exit:0 rump.ifconfig l2tp0 create + atf_check -s exit:0 rump.ifconfig l2tp0 tunnel ${src} ${dst} + atf_check -s exit:0 rump.ifconfig l2tp0 session ${src_session} ${dst_session} + atf_check -s exit:0 rump.ifconfig l2tp0 up + + atf_check -s exit:0 rump.ifconfig bridge0 create + atf_check -s exit:0 rump.ifconfig bridge0 up + export LD_PRELOAD=/usr/lib/librumphijack.so + atf_check -s exit:0 brconfig bridge0 add shmif0 + atf_check -s exit:0 brconfig bridge0 add l2tp0 + unset LD_PRELOAD + + $DEBUG && rump.ifconfig -v l2tp0 + $DEBUG && rump.ifconfig -v bridge0 + + unset RUMP_SERVER +} + +setup_tunnel() +{ + wan_mode=${1} + + src="" + dst="" + src_session="" + dst_session="" + + if [ ${wan_mode} = "ipv6" ]; then + src=$LAC1_WANIP6 + dst=$LAC2_WANIP6 + else + src=$LAC1_WANIP + dst=$LAC2_WANIP + fi + src_session=${LAC1_SESSION} + dst_session=${LAC2_SESSION} + setup_if_l2tp $LAC1SOCK ${src} ${dst} ${src_session} ${dst_session} + + if [ ${wan_mode} = "ipv6" ]; then + src=$LAC2_WANIP6 + dst=$LAC1_WANIP6 + else + src=$LAC2_WANIP + dst=$LAC1_WANIP + fi + src_session=${LAC2_SESSION} + dst_session=${LAC1_SESSION} + setup_if_l2tp $LAC2SOCK ${src} ${dst} ${src_session} ${dst_session} +} + +test_setup_tunnel() +{ + mode=${1} + + if [ ${mode} = "ipv6" ]; then + lac1_wan=$LAC1_WANIP6 + lac2_wan=$LAC2_WANIP6 + else + lac1_wan=$LAC1_WANIP + lac2_wan=$LAC2_WANIP + fi + export RUMP_SERVER=$LAC1SOCK + atf_check -s exit:0 -o match:l2tp0 rump.ifconfig + if [ ${mode} = "ipv6" ]; then + atf_check -s exit:0 -o ignore rump.ping6 -n -c 1 -X $TIMEOUT ${lac2_wan} + else + atf_check -s exit:0 -o ignore rump.ping -n -c 1 -w $TIMEOUT ${lac2_wan} + fi + + export RUMP_SERVER=$LAC2SOCK + atf_check -s exit:0 -o match:l2tp0 rump.ifconfig + if [ ${mode} = "ipv6" ]; then + atf_check -s exit:0 -o ignore rump.ping6 -n -c 1 -X $TIMEOUT ${lac1_wan} + else + atf_check -s exit:0 -o ignore rump.ping -n -c 1 -w $TIMEOUT ${lac1_wan} + fi + + unset RUMP_SERVER +} + +teardown_tunnel() +{ + export RUMP_SERVER=$LAC1SOCK + atf_check -s exit:0 rump.ifconfig bridge0 destroy + atf_check -s exit:0 rump.ifconfig l2tp0 deletetunnel + atf_check -s exit:0 rump.ifconfig l2tp0 destroy + + export RUMP_SERVER=$LAC2SOCK + atf_check -s exit:0 rump.ifconfig bridge0 destroy + atf_check -s exit:0 rump.ifconfig l2tp0 deletetunnel + atf_check -s exit:0 rump.ifconfig l2tp0 destroy + + unset RUMP_SERVER +} + +test_ping_failure() +{ + mode=$1 + + export RUMP_SERVER=$CLIENT1SOCK + if [ ${mode} = "ipv6" ]; then + atf_check -s not-exit:0 -o ignore -e ignore \ + rump.ping6 -n -X $TIMEOUT -c 1 $CLIENT2_LANIP6 + else + atf_check -s not-exit:0 -o ignore -e ignore \ + rump.ping -n -w $TIMEOUT -c 1 $CLIENT2_LANIP + fi + + export RUMP_SERVER=$CLIENT2SOCK + if [ ${mode} = "ipv6" ]; then + atf_check -s not-exit:0 -o ignore -e ignore \ + rump.ping6 -n -X $TIMEOUT -c 1 $CLIENT1_LANIP6 + else + atf_check -s not-exit:0 -o ignore -e ignore \ + rump.ping -n -w $TIMEOUT -c 1 $CLIENT1_LANIP + fi + + unset RUMP_SERVER +} + +test_ping_success() +{ + mode=$1 + + export RUMP_SERVER=$CLIENT1SOCK + if [ ${mode} = "ipv6" ]; then + # XXX + # rump.ping6 rarely fails with the message that + # "failed to get receiving hop limit". + # This is a known issue being analyzed. + atf_check -s exit:0 -o ignore \ + rump.ping6 -n -X $TIMEOUT -c 1 $CLIENT2_LANIP6 + else + atf_check -s exit:0 -o ignore \ + rump.ping -n -w $TIMEOUT -c 1 $CLIENT2_LANIP + fi + export RUMP_SERVER=$LAC1SOCK + $DEBUG && rump.ifconfig -v l2tp0 + $DEBUG && rump.ifconfig -v bridge0 + $DEBUG && rump.ifconfig -v shmif0 + + export RUMP_SERVER=$CLIENT2SOCK + if [ ${mode} = "ipv6" ]; then + atf_check -s exit:0 -o ignore \ + rump.ping6 -n -X $TIMEOUT -c 1 $CLIENT1_LANIP6 + else + atf_check -s exit:0 -o ignore \ + rump.ping -n -w $TIMEOUT -c 1 $CLIENT1_LANIP + fi + export RUMP_SERVER=$LAC2SOCK + $DEBUG && rump.ifconfig -v l2tp0 + $DEBUG && rump.ifconfig -v bridge0 + $DEBUG && rump.ifconfig -v shmif0 + + unset RUMP_SERVER +} + +basic_setup() +{ + lan_mode=$1 + wan_mode=$2 + + setup ${lan_mode} ${wan_mode} + test_setup ${lan_mode} ${wan_mode} + + # Enable once PR kern/49219 is fixed + #test_ping_failure + + setup_tunnel ${wan_mode} + sleep 1 + test_setup_tunnel ${wan_mode} +} + +basic_test() +{ + lan_mode=$1 + wan_mode=$2 # not use + + test_ping_success ${lan_mode} +} + +basic_teardown() +{ + lan_mode=$1 + wan_mode=$2 # not use + + teardown_tunnel + test_ping_failure ${lan_mode} +} + +add_test() +{ + category=$1 + desc=$2 + lan_mode=$3 + wan_mode=$4 + + name="${category}${lan_mode}over${wan_mode}" + fulldesc="Does ${lan_mode} over ${wan_mode} if_l2tp ${desc}" + + atf_test_case ${name} cleanup + eval "${name}_head() { \ + atf_set \"descr\" \"${fulldesc}\"; \ + atf_set \"require.progs\" \"rump_server\"; \ + }; \ + ${name}_body() { \ + ${category}_setup ${lan_mode} ${wan_mode}; \ + ${category}_test ${lan_mode} ${wan_mode}; \ + ${category}_teardown ${lan_mode} ${wan_mode}; \ + rump_server_destroy_ifaces; \ + }; \ + ${name}_cleanup() { \ + $DEBUG && dump; \ + cleanup; \ + }" + atf_add_test_case ${name} +} + +add_test_allproto() +{ + category=$1 + desc=$2 + + add_test ${category} "${desc}" ipv4 ipv4 + add_test ${category} "${desc}" ipv4 ipv6 + add_test ${category} "${desc}" ipv6 ipv4 + add_test ${category} "${desc}" ipv6 ipv6 +} + +atf_init_test_cases() +{ + add_test_allproto basic "basic tests" +# add_test_allproto recursive "recursive check tests" +}