pax_global_header00006660000000000000000000000064145647106400014521gustar00rootroot0000000000000052 comment=157d703baf507340bd6e215cd909d4eb1f45722e golang-github-katalix-go-l2tp-0.1.7/000077500000000000000000000000001456471064000171725ustar00rootroot00000000000000golang-github-katalix-go-l2tp-0.1.7/.gitignore000066400000000000000000000001201456471064000211530ustar00rootroot00000000000000l2tp/cover_nonroot.out l2tp/cover_root.out l2tp/coverage.html l2tp/coverage.out golang-github-katalix-go-l2tp-0.1.7/CHANGELOG.md000066400000000000000000000010401456471064000207760ustar00rootroot00000000000000## v0.17 - Skip L2TPIP6 transport test, which is now failing in Debian/Ubuntu due to a kernel regression which has been backported into various stable kernels. ## v0.1.6 - Fix up manpage sections in markdown/pandoc metadata. ## v0.1.5 - Fix up manpage install location. ## v0.1.4 - Fix up manpage roff syntax. ## v0.1.3 - Documentation spelling fix. ## v0.1.2 - Skip IP encap transport tests when the host lacks protocol support. ## v0.1.1 - Add manpages as a helper for distro packagers. ## v0.1.0 - Initial unstable release. golang-github-katalix-go-l2tp-0.1.7/LICENSE000066400000000000000000000020771456471064000202050ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2020 Katalix Systems Ltd. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-katalix-go-l2tp-0.1.7/README.md000066400000000000000000000131321456471064000204510ustar00rootroot00000000000000# go-l2tp **go-l2tp** is suite of Go libraries for building [L2TP](https://en.wikipedia.org/wiki/Layer_2_Tunneling_Protocol) applications on Linux systems. ## Features * [L2TPv2 (RFC2661)](https://tools.ietf.org/html/rfc2661) and [L2TPv3 (RFC3931)](https://tools.ietf.org/html/rfc3931) data plane via. Linux L2TP subsystem * AF_INET and AF_INET6 tunnel addresses * UDP and L2TPIP tunnel encapsulation * L2TPv2 control plane in client/LAC mode * [PPPoE (RFC2561)](https://tools.ietf.org/html/rfc2516) control and data plane via. Linux L2TP subsystem. ## Installation If you're familiar with Go, you can skip this section. Prior to installing go-l2tp, install the [Go language distribution](https://golang.org/dl/) which includes the compiler and other tooling required to install Go programs. Please follow the instructions from the Go project to get your installation up and running. You can now install go-l2tp as follows: go install github.com/katalix/go-l2tp/...@latest Read on for instructions on coding using the library. ## Import import ( "github.com/katalix/go-l2tp/l2tp" "github.com/katalix/go-l2tp/pppoe" "github.com/katalix/go-l2tp/config" ) ## Usage # Note we're ignoring errors for brevity. # Read configuration using the config package. # This is optional: you can build your own configuration # structures if you prefer. config, _ := config.LoadFile("./my-l2tp-config.toml") # Creation of L2TP instances requires an L2TP context. # We're disabling logging and using the default Linux data plane. l2tpctx, _ := l2tp.NewContext(l2tp.LinuxNetlinkDataPlane, nil) # Create tunnel and session instances based on the config for _, tcfg := range config.Tunnels { tunl, _ := l2tpctx.NewStaticTunnel(tcfg.Name, tcfg.Config) for _, scfg := range tcfg.Sessions { _, _, := tunl.NewSession(scfg.Name, scfg.Config) } } ## Tools go-l2tp includes three tools which build on the library. ### ql2tpd **ql2tpd** is a minimal daemon for creating static L2TPv3 sessions. This tool requires root permissions to run, and is driven by a configuration file which details the tunnel and session instances to create. Each tunnel may run as a purely static instance. In this mode **ql2tpd** represents a more convenient way to bring up static sessions than **ip l2tp** commands. If a tunnel has a ***hello_timeout*** set, the tunnel will send a periodic keep-alive packet over a minimal implementation of the RFC3931 reliable control message transport. This allows for the detection of tunnel failure, which will then tear down the sessions running in that tunnel. ***hello_timeout*** should only be enabled if the peer is also running **ql2tpd**. ### kl2tpd **kl2tpd** is a client/LAC-mode daemon for creating L2TPv2 sessions. It spawns the standard Linux **pppd** for PPP protocol support. Similar to **ql2tpd**, **kl2tpd** requires root permissions to run, and is driven by a configuration file which details the tunnel and session instances to create. In addition to the configuration parameters documented by package config, **kl2tpd** supports an extra session parameter, ***pppd_args*** which calls out an argument file for extra **pppd** command line arguments. Here is an example configuration for establishing a single tunnel containing a single session: [tunnel.t1] peer = "42.102.77.204:1701" version = "l2tpv2" encap = "udp" [tunnel.t1.session.s1] pseudowire = "ppp" pppd_args = "/home/bob/pppd.args" ### kpppoed **kpppoed** is a PPPoE daemon for creating L2TPv2 Access Concentrator sessions in response to PPPoE requests. It spawns **kl2tpd** for L2TP protocol support. **kpppoed** uses a minimal configuration file format which calls out the interface to listen on for PPPoE packets, the list of PPPoE services to offer, and the IP address of the LNS to use for establishing L2TPv2 sessions. Here is an example configuration: ac_name = "kpppoed-1.0" interface_name = "eth0" services = [ "myservice" ] lns_ipaddr = "192.168.1.69:1701" ## Documentation The go-l2tp library and tools are documented using Go's documentation tool. A top-level description of the various libraries can be viewed as follows: go doc l2tp go doc pppoe go doc config This top level document provides a summary of the main APIs the library exposes. You can view documentation of a particular API or type like this: go doc l2tp.Context Finally, documentation of various commands like this: go doc cmd/ql2tpd go doc cmd/kl2tpd go doc cmd/kpppoed ## Testing go-l2tp has unit tests which can be run using go test: go test ./... Some tests instantiate tunnels and sessions in the Linux kernel's L2TP subsystem, and hence require root permissions to run. By default these tests are skipped if run as a normal user. The tests requiring root can be run as follows: go test -exec sudo -run TestRequiresRoot ./... The tests are run using ***sudo***, which will need to be set up for your user, and require the Linux kernel L2TP modules to be loaded. For the l2tp library tests: modprobe l2tp_core l2tp_netlink l2tp_eth l2tp_ip l2tp_ip6 And for the pppoe library tests: modprobe l2tp_ac_pppoe Depending on your Linux distribution it may be necessary to install an extra package to get the L2TP subsystem modules. For example on Ubuntu: sudo apt-get install linux-modules-extra-$(uname -r) The script ***runtests.sh*** automates running all the tests (both those requiring root and not) and generates a html test coverage report: ./runtests.sh && firefox ./coverage.html golang-github-katalix-go-l2tp-0.1.7/cmd/000077500000000000000000000000001456471064000177355ustar00rootroot00000000000000golang-github-katalix-go-l2tp-0.1.7/cmd/kl2tpd/000077500000000000000000000000001456471064000211355ustar00rootroot00000000000000golang-github-katalix-go-l2tp-0.1.7/cmd/kl2tpd/kl2tpd.go000066400000000000000000000256371456471064000227010ustar00rootroot00000000000000/* The kl2tpd command is a daemon for creating dynamic L2TPv2 tunnels and sessions. Package l2tp is used for the L2TPv2 control protocol and Linux kernel dataplane operations. For established sessions, kl2tpd spawns pppd(8) instances to run the PPP protocol and bring up a network interface. kl2tpd is driven by a configuration file which describes the tunnel and session instances to create. For more information on the configuration file format please refer to package config's documentation. In addition to the configuration options offered by package config, kl2tpd extends the session configuration table to allow for the configuration of pppd: [tunnel.t1.session.s1] pppd_args = "/etc/pppd_args.txt" The pppd_args parameter specifies a file to read for pppd arguments. These should either be whitespace or newline delimited, and should call out pppd command line arguments as described in the pppd manpage. kl2tpd augments the arguments from the command file with arguments specific to the establishment of the PPPoL2TP session using the pppd pppol2tp plugin. */ package main import ( "bufio" "flag" "fmt" stdlog "log" "os" "os/signal" "strings" "sync" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/katalix/go-l2tp/config" "github.com/katalix/go-l2tp/l2tp" "golang.org/x/sys/unix" ) type sessionPPPArgs struct { pppdArgs []string } type kl2tpdConfig struct { config *config.Config // pppArgs[tunnel_name][session_name] pppArgs map[string]map[string]*sessionPPPArgs } // An interface for managing a pseudowire instance. // The core l2tp code creates the L2TP session control plane // and kernel l2tp subsystem data plane instance. // kl2tpd may then need to e.g. instantiate pppd for a PPP // pseudowire, or bridge pppox sockets for a PPPAC pseudowire. // This interface abstracts that away from kl2tpd core. type pseudowire interface { close() getSession() l2tp.Session } type application struct { cfg *kl2tpdConfig logger log.Logger l2tpCtx *l2tp.Context // sessionPW[tunnel_name][session_name] sessionPW map[string]map[string]pseudowire sigChan chan os.Signal pwCompleteChan chan pseudowire closeChan chan interface{} wg sync.WaitGroup } func newKl2tpdConfig() (cfg *kl2tpdConfig) { return &kl2tpdConfig{ pppArgs: make(map[string]map[string]*sessionPPPArgs), } } func (cfg *kl2tpdConfig) readPPPdArgsFile(path string) ([]string, error) { file, err := os.Open(path) if err != nil { return []string{}, fmt.Errorf("failed to open pppd arguments file %q: %v", path, err) } defer file.Close() args := []string{} scanner := bufio.NewScanner(file) for scanner.Scan() { args = append(args, strings.Split(scanner.Text(), " ")...) } return args, nil } func (cfg *kl2tpdConfig) addSession(tunnelName, sessionName string) { if _, ok := cfg.pppArgs[tunnelName]; !ok { cfg.pppArgs[tunnelName] = make(map[string]*sessionPPPArgs) } if _, ok := cfg.pppArgs[tunnelName][sessionName]; !ok { cfg.pppArgs[tunnelName][sessionName] = &sessionPPPArgs{} } } func (cfg *kl2tpdConfig) setSessionPPPdArgs(tunnelName, sessionName string, args []string) { cfg.addSession(tunnelName, sessionName) cfg.pppArgs[tunnelName][sessionName].pppdArgs = args } func (cfg *kl2tpdConfig) ParseParameter(key string, value interface{}) error { return fmt.Errorf("unrecognised parameter %v", key) } func (cfg *kl2tpdConfig) ParseTunnelParameter(tunnel *config.NamedTunnel, key string, value interface{}) error { return fmt.Errorf("unrecognised parameter %v", key) } func (cfg *kl2tpdConfig) ParseSessionParameter(tunnel *config.NamedTunnel, session *config.NamedSession, key string, value interface{}) error { switch key { case "pppd_args": path, ok := value.(string) if !ok { return fmt.Errorf("failed to parse pppd_args parameter for session %s as a string", session.Name) } args, err := cfg.readPPPdArgsFile(path) if err != nil { return err } cfg.setSessionPPPdArgs(tunnel.Name, session.Name, args) return nil } return fmt.Errorf("unrecognised parameter %v", key) } func newApplication(cfg *kl2tpdConfig, verbose, nullDataplane bool) (app *application, err error) { app = &application{ cfg: cfg, sigChan: make(chan os.Signal, 1), sessionPW: make(map[string]map[string]pseudowire), pwCompleteChan: make(chan pseudowire), closeChan: make(chan interface{}), } signal.Notify(app.sigChan, unix.SIGINT, unix.SIGTERM) logger := log.NewLogfmtLogger(os.Stderr) if verbose { app.logger = level.NewFilter(logger, level.AllowDebug()) } else { app.logger = level.NewFilter(logger, level.AllowInfo()) } dataplane := l2tp.LinuxNetlinkDataPlane if nullDataplane { dataplane = nil } app.l2tpCtx, err = l2tp.NewContext(dataplane, logger) if err != nil { return nil, fmt.Errorf("failed to create L2TP context: %v", err) } return app, nil } func (app *application) getSessionPPPArgs(tunnelName, sessionName string) (args *sessionPPPArgs) { _, ok := app.cfg.pppArgs[tunnelName] if !ok { goto fail } args, ok = app.cfg.pppArgs[tunnelName][sessionName] if !ok { goto fail } return fail: level.Info(app.logger).Log( "message", "no pppd args specified in session config", "tunnel_name", tunnelName, "session_name", sessionName) return &sessionPPPArgs{} } func (app *application) instantiatePPPPseudowire(ev *l2tp.SessionUpEvent) (pw pseudowire) { pppd, err := newPPPDaemon(ev.Session, ev.TunnelConfig.TunnelID, ev.SessionConfig.SessionID, ev.TunnelConfig.PeerTunnelID, ev.SessionConfig.PeerSessionID) if err != nil { level.Error(app.logger).Log( "message", "failed to create pppol2tp instance", "error", err) return nil } pppArgs := app.getSessionPPPArgs(ev.TunnelName, ev.SessionName) pppd.cmd.Args = append(pppd.cmd.Args, pppArgs.pppdArgs...) err = pppd.cmd.Start() if err != nil { level.Error(app.logger).Log( "message", "pppd failed to start", "error", err, "error_message", pppdExitCodeString(err), "stderr", pppd.stderrBuf.String()) return nil } app.sessionPW[ev.TunnelName][ev.SessionName] = pppd app.wg.Add(1) go func() { defer app.wg.Done() err = pppd.cmd.Wait() if err != nil { level.Error(app.logger).Log( "message", "pppd exited with an error code", "error", err, "error_message", pppdExitCodeString(err)) } app.pwCompleteChan <- pppd }() return pppd } func (app *application) instantiatePPPACPseudowire(ev *l2tp.SessionUpEvent) (pw pseudowire) { pb, err := newPPPBridge(ev.Session, ev.TunnelConfig.TunnelID, ev.SessionConfig.SessionID, ev.TunnelConfig.PeerTunnelID, ev.SessionConfig.PeerSessionID, ev.SessionConfig.PPPoESessionId, ev.SessionConfig.PPPoEPeerMac, ev.SessionConfig.InterfaceName) if err != nil { level.Error(app.logger).Log( "message", "ppp/ac bridge failed to start", "error", err) return nil } return pb } func (app *application) instantiatePseudowire(ev *l2tp.SessionUpEvent) (pw pseudowire) { switch ev.SessionConfig.Pseudowire { case l2tp.PseudowireTypePPP: return app.instantiatePPPPseudowire(ev) case l2tp.PseudowireTypePPPAC: return app.instantiatePPPACPseudowire(ev) } level.Error(app.logger).Log( "message", "unsupported pseudowire type", "pseudowire_type", ev.SessionConfig.Pseudowire) return nil } func (app *application) HandleEvent(event interface{}) { switch ev := event.(type) { case *l2tp.TunnelUpEvent: if _, ok := app.sessionPW[ev.TunnelName]; !ok { app.sessionPW[ev.TunnelName] = make(map[string]pseudowire) } case *l2tp.TunnelDownEvent: delete(app.sessionPW, ev.TunnelName) case *l2tp.SessionUpEvent: level.Info(app.logger).Log( "message", "session up", "tunnel_name", ev.TunnelName, "session_name", ev.SessionName, "tunnel_id", ev.TunnelConfig.TunnelID, "session_id", ev.SessionConfig.SessionID, "peer_tunnel_id", ev.TunnelConfig.PeerTunnelID, "peer_session_id", ev.SessionConfig.PeerSessionID) app.sessionPW[ev.TunnelName][ev.SessionName] = app.instantiatePseudowire(ev) if app.sessionPW[ev.TunnelName][ev.SessionName] == nil { app.closeSession(ev.Session) } case *l2tp.SessionDownEvent: level.Info(app.logger).Log( "message", "session down", "result", ev.Result, "tunnel_name", ev.TunnelName, "session_name", ev.SessionName, "tunnel_id", ev.TunnelConfig.TunnelID, "session_id", ev.SessionConfig.SessionID, "peer_tunnel_id", ev.TunnelConfig.PeerTunnelID, "peer_session_id", ev.SessionConfig.PeerSessionID) if app.sessionPW[ev.TunnelName][ev.SessionName] != nil { level.Info(app.logger).Log("message", "killing pseudowire") app.sessionPW[ev.TunnelName][ev.SessionName].close() delete(app.sessionPW[ev.TunnelName], ev.SessionName) } } } func (app *application) closeSession(s l2tp.Session) { app.wg.Add(1) go func() { defer app.wg.Done() s.Close() }() } func (app *application) run() int { // Listen for L2TP events app.l2tpCtx.RegisterEventHandler(app) // Instantiate tunnels and sessions from the config file for _, tcfg := range app.cfg.config.Tunnels { // Only support l2tpv2/ppp if tcfg.Config.Version != l2tp.ProtocolVersion2 { level.Error(app.logger).Log( "message", "unsupported tunnel protocol version", "version", tcfg.Config.Version) return 1 } tunl, err := app.l2tpCtx.NewDynamicTunnel(tcfg.Name, tcfg.Config) if err != nil { level.Error(app.logger).Log( "message", "failed to create tunnel", "tunnel_name", tcfg.Name, "error", err) return 1 } for _, scfg := range tcfg.Sessions { _, err := tunl.NewSession(scfg.Name, scfg.Config) if err != nil { level.Error(app.logger).Log( "message", "failed to create session", "session_name", scfg.Name, "error", err) return 1 } } } var shutdown bool for { select { case <-app.sigChan: if !shutdown { level.Info(app.logger).Log("message", "received signal, shutting down") shutdown = true go func() { app.l2tpCtx.Close() app.wg.Wait() level.Info(app.logger).Log("message", "graceful shutdown complete") close(app.closeChan) }() } else { level.Info(app.logger).Log("message", "pending graceful shutdown") } case pw, ok := <-app.pwCompleteChan: if !ok { close(app.closeChan) } level.Info(app.logger).Log("message", "pseudowire terminated") if !shutdown { app.closeSession(pw.getSession()) } case <-app.closeChan: return 0 } } } func main() { mycfg := newKl2tpdConfig() cfgPathPtr := flag.String("config", "/etc/kl2tpd/kl2tpd.toml", "specify configuration file path") verbosePtr := flag.Bool("verbose", false, "toggle verbose log output") nullDataPlanePtr := flag.Bool("null", false, "toggle null data plane") flag.Parse() config, err := config.LoadFileWithCustomParser(*cfgPathPtr, mycfg) if err != nil { stdlog.Fatalf("failed to load configuration: %v", err) } mycfg.config = config app, err := newApplication(mycfg, *verbosePtr, *nullDataPlanePtr) if err != nil { stdlog.Fatalf("failed to instantiate application: %v", err) } os.Exit(app.run()) } golang-github-katalix-go-l2tp-0.1.7/cmd/kl2tpd/kl2tpd_test.go000066400000000000000000000031501456471064000237220ustar00rootroot00000000000000package main import ( "fmt" "os" "reflect" "strings" "testing" "github.com/katalix/go-l2tp/config" ) func TestConfigParser(t *testing.T) { pppdArgsPath := "/tmp/test.pppd.args" pppdArgs := "noauth 10.42.0.1:10.42.0.2" f, err := os.Create(pppdArgsPath) if err != nil { t.Fatalf("os.Create(%v): %v", pppdArgsPath, err) } _, err = f.WriteString(pppdArgs) if err != nil { t.Fatalf("f.WriteString(%v): %v", pppdArgs, err) } err = f.Close() if err != nil { t.Fatalf("f.Close(): %v", err) } cases := []struct { name string in string expectFail bool out *kl2tpdConfig }{ { name: "pppdargs0", in: fmt.Sprintf(`[tunnel.t1] peer = "127.0.0.1:9000" version = "l2tpv2" encap = "udp" [tunnel.t1.session.s1] pseudowire = "ppp" pppd_args = "%s"`, pppdArgsPath), out: &kl2tpdConfig{ pppArgs: map[string]map[string]*sessionPPPArgs{ "t1": map[string]*sessionPPPArgs{ "s1": &sessionPPPArgs{ pppdArgs: strings.Split(pppdArgs, " "), }, }, }, }, }, { name: "pppac0", in: `[tunnel.t1] peer = "127.0.0.1:9000" version = "l2tpv2" encap = "udp" [tunnel.t1.session.s1] pseudowire = "ppp" `, out: &kl2tpdConfig{ pppArgs: map[string]map[string]*sessionPPPArgs{}, }, }, } for _, c := range cases { cfg := newKl2tpdConfig() _, err := config.LoadStringWithCustomParser(c.in, cfg) if err != nil { t.Fatalf("LoadStringWithCustomParser: %v", err) } if !reflect.DeepEqual(cfg, c.out) { t.Fatalf("expect %v, got %v", c.out, cfg) } } os.Remove(pppdArgsPath) } golang-github-katalix-go-l2tp-0.1.7/cmd/kl2tpd/ppp.go000066400000000000000000000023161456471064000222650ustar00rootroot00000000000000package main import ( "fmt" "golang.org/x/sys/unix" ) type pppChannel struct { pppoxSk int pppSk int channelIndex int } func newPPPChannel(pppoxSk int) (c *pppChannel, err error) { idx, err := unix.IoctlGetUint32(pppoxSk, unix.PPPIOCGCHAN) if err != nil { return nil, fmt.Errorf("failed to get pppox channel index: %v", err) } pppSk, err := unix.Open("/dev/ppp", unix.O_RDWR, 0) if err != nil { return nil, fmt.Errorf("failed to open /dev/ppp: %v", err) } err = unix.IoctlSetPointerInt(pppSk, unix.PPPIOCATTCHAN, int(idx)) if err != nil { unix.Close(pppSk) return nil, fmt.Errorf("failed to attach to channel %v: %v", idx, err) } return &pppChannel{ pppoxSk: pppoxSk, pppSk: pppSk, channelIndex: int(idx), }, nil } func (c *pppChannel) bridge(to *pppChannel) (err error) { /* FIXME: not upstream yet! */ var PPPIOCBRIDGECHAN uint = 0x40047435 err = unix.IoctlSetPointerInt(c.pppSk, PPPIOCBRIDGECHAN, to.channelIndex) if err != nil { return fmt.Errorf("failed to bridge ppp channels: %v", err) } return nil } func (c *pppChannel) close() { if c != nil { if c.pppoxSk >= 0 { unix.Close(c.pppoxSk) } if c.pppSk >= 0 { unix.Close(c.pppSk) } } } golang-github-katalix-go-l2tp-0.1.7/cmd/kl2tpd/pppbridge.go000066400000000000000000000030551456471064000234430ustar00rootroot00000000000000package main import ( "fmt" "github.com/katalix/go-l2tp/l2tp" "golang.org/x/sys/unix" ) var _ pseudowire = (*pppBridge)(nil) type pppBridge struct { session l2tp.Session pppoe, pppol2tp *pppChannel } func newPPPBridge(session l2tp.Session, tunnelID, sessionID, peerTunnelID, peerSessionID l2tp.ControlConnID, pppoeSessionID uint16, pppoePeerMAC [6]byte, pppoeInterfaceName string) (*pppBridge, error) { pppoeSk, err := socketPPPoE(pppoeSessionID, pppoePeerMAC, pppoeInterfaceName) if err != nil { return nil, fmt.Errorf("failed to create PPPoE socket: %v", err) } pppoeChan, err := newPPPChannel(pppoeSk) if err != nil { unix.Close(pppoeSk) return nil, fmt.Errorf("failed to create PPPoE channel: %v", err) } pppol2tpSk, err := socketPPPoL2TPv4(tunnelID, sessionID, peerTunnelID, peerSessionID) if err != nil { pppoeChan.close() return nil, fmt.Errorf("failed to create PPPoL2TP socket: %v", err) } pppol2tpChan, err := newPPPChannel(pppol2tpSk) if err != nil { pppoeChan.close() return nil, fmt.Errorf("failed to create PPPoL2TP channel: %v", err) } err = pppoeChan.bridge(pppol2tpChan) if err != nil { pppoeChan.close() pppol2tpChan.close() return nil, fmt.Errorf("failed to bridge PPPoE to PPPoL2TP channel: %v", err) } return &pppBridge{ session: session, pppoe: pppoeChan, pppol2tp: pppol2tpChan, }, nil } func (pb *pppBridge) close() { if pb.pppoe != nil { pb.pppoe.close() } if pb.pppol2tp != nil { pb.pppol2tp.close() } } func (pb *pppBridge) getSession() l2tp.Session { return pb.session } golang-github-katalix-go-l2tp-0.1.7/cmd/kl2tpd/pppd.go000066400000000000000000000064411456471064000224340ustar00rootroot00000000000000package main import ( "bytes" "fmt" "os" "os/exec" "github.com/katalix/go-l2tp/l2tp" ) var _ pseudowire = (*pppDaemon)(nil) type pppDaemon struct { session l2tp.Session fd int file *os.File cmd *exec.Cmd stdoutBuf *bytes.Buffer stderrBuf *bytes.Buffer } func pppdExitCodeString(err error) string { // ref: pppd(8) section EXIT STATUS switch err.Error() { case "exit status 0": return "pppd established successfully and terminated at peer's request" case "exit status 1": return "immediately fatal error (e.g. essential system call failed, or out of memory)" case "exit status 2": return "error detected during options parsing/processing" case "exit status 3": return "pppd is not setuid-root and the invoking user is not root" case "exit status 4": return "the kernel does not support PPP (possibly the module is not loaded or unavailable)" case "exit status 5": return "pppd terminated due to SIGINT, SIGTERM, or SIGHUP signal" case "exit status 6": return "the serial port could not be locked" case "exit status 7": return "the serial port could not be opened" case "exit status 8": return "the connect script returned a non-zero exit status" case "exit status 9": return "the command specified as an argument to the pty option could not be run" case "exit status 10": return "PPP negotiation failed (that is, didn't reach the point where at least one network protocol was running)" case "exit status 11": return "the peer system failed or refused to authenticate itself" case "exit status 12": return "the link was established successfully and terminated because it was idle" case "exit status 13": return "the link was established successfully and terminated because the connect time limit was reached" case "exit status 14": return "callback was negotiated and an incoming call should arrive shortly" case "exit status 15": return "the link was terminated because the peer is not responding to echo requests" case "exit status 16": return "the link was terminated by the modem hanging up" case "exit status 17": return "the ppp negotiation failed because serial loopback was detected" case "exit status 18": return "the init script returned a non-zero exit status" case "exit status 19": return "we failed to authenticate ourselves to the peer" } return err.Error() } func newPPPDaemon(session l2tp.Session, tunnelID, sessionID, peerTunnelID, peerSessionID l2tp.ControlConnID) (*pppDaemon, error) { fd, err := socketPPPoL2TPv4(tunnelID, sessionID, peerTunnelID, peerSessionID) if err != nil { return nil, fmt.Errorf("failed to create PPPoL2TP socket: %v", err) } var stdout, stderr bytes.Buffer file := os.NewFile(uintptr(fd), "pppol2tp") cmd := exec.Command( "/usr/sbin/pppd", "plugin", "pppol2tp.so", "pppol2tp", "3", "pppol2tp_tunnel_id", fmt.Sprintf("%v", tunnelID), "pppol2tp_session_id", fmt.Sprintf("%v", sessionID), "nodetach") cmd.Stdout = &stdout cmd.Stderr = &stderr cmd.ExtraFiles = append(cmd.ExtraFiles, file) return &pppDaemon{ session: session, fd: int(fd), file: file, cmd: cmd, stdoutBuf: &stdout, stderrBuf: &stderr, }, nil } func (pppd *pppDaemon) close() { pppd.cmd.Process.Signal(os.Interrupt) } func (pppd *pppDaemon) getSession() l2tp.Session { return pppd.session } golang-github-katalix-go-l2tp-0.1.7/cmd/kl2tpd/pppox.go000066400000000000000000000124301456471064000226320ustar00rootroot00000000000000package main // #include // #include // #include import "C" import ( "encoding/binary" "fmt" "unsafe" "github.com/katalix/go-l2tp/l2tp" ) /* struct sockaddr_pppox uses the gcc attribute "packed", so cgo isn't able to infer the fields of the structure. We have to manually pack it. typedef __be16 sid_t; struct pppoe_addr { sid_t sid; unsigned char remote[ETH_ALEN]; char dev[IFNAMSIZ]; }; struct sockaddr_pppox { __kernel_sa_family_t sa_family; unsigned int sa_protocol; union { struct pppoe_addr pppoe; struct pptp_addr pptp; } sa_addr; } __attribute__((packed)); */ func newSockaddrPPPoE(sessionID uint16, destHWAddr [6]byte, interfaceName string) ( addr *C.struct_sockaddr, addrLen C.socklen_t, err error) { if sessionID == 0 { return nil, 0, fmt.Errorf("session ID must be greater than zero") } if interfaceName == "" { return nil, 0, fmt.Errorf("interface name cannot be empty") } if len(interfaceName) > C.IFNAMSIZ-1 { return nil, 0, fmt.Errorf("interface name length cannot be greater than IFNAMSIZ") } var sa C.struct_sockaddr_pppox buf := (*[C.sizeof_struct_sockaddr_pppox]byte)(unsafe.Pointer(&sa)) idx := 0 // struct sockaddr_pppox -> sa_family *(*C.ushort)(unsafe.Pointer(&buf[idx])) = C.AF_PPPOX idx += C.sizeof_ushort // struct sockaddr_pppox -> sa_protocol *(*C.uint)(unsafe.Pointer(&buf[idx])) = C.PX_PROTO_OE idx += C.sizeof_uint // struct sockaddr_pppox -> sa_addr -> pppoe -> sid binary.BigEndian.PutUint16(buf[idx:idx+C.sizeof___u16], sessionID) idx += C.sizeof___u16 // struct sockaddr_pppox -> sa_addr -> pppoe -> remote copy(buf[idx:idx+len(destHWAddr)], destHWAddr[:]) idx += len(destHWAddr) // struct sockaddr_pppox -> sa_addr -> pppoe -> dev copy(buf[idx:], interfaceName) return (*C.struct_sockaddr)(unsafe.Pointer(&sa)), C.sizeof_struct_sockaddr_pppox, nil } /* struct sockaddr_pppol2tp uses the gcc attribute "packed", so cgo isn't able to infer the fields of the structure. We have to manually pack it. struct pppol2tp_addr { __kernel_pid_t pid; int fd; struct sockaddr_in addr; __u16 s_tunnel, s_session; __u16 d_tunnel, d_session; }; struct sockaddr_pppol2tp { __kernel_sa_family_t sa_family; unsigned int sa_protocol; struct pppol2tp_addr pppol2tp; } __attribute__((packed)); */ func newSockaddrPPPoL2TP4(tunnelID, sessionID, peerTunnelID, peerSessionID l2tp.ControlConnID) ( addr *C.struct_sockaddr, addrLen C.socklen_t, err error) { if tunnelID == 0 || tunnelID > 65535 { return nil, 0, fmt.Errorf("tunnel ID %v out of range", tunnelID) } if sessionID == 0 || sessionID > 65535 { return nil, 0, fmt.Errorf("session ID %v out of range", sessionID) } if peerTunnelID == 0 || peerTunnelID > 65535 { return nil, 0, fmt.Errorf("peerTunnel ID %v out of range", peerTunnelID) } if peerSessionID == 0 || peerSessionID > 65535 { return nil, 0, fmt.Errorf("peerSession ID %v out of range", peerSessionID) } var sa C.struct_sockaddr_pppol2tp buf := (*[C.sizeof_struct_sockaddr_pppol2tp]byte)(unsafe.Pointer(&sa)) idx := 0 // struct sockaddr_pppol2tp -> sa_family *(*C.ushort)(unsafe.Pointer(&buf[idx])) = C.AF_PPPOX idx += C.sizeof_ushort // struct sockaddr_pppol2tp -> sa_protocol *(*C.uint)(unsafe.Pointer(&buf[idx])) = C.PX_PROTO_OL2TP idx += C.sizeof_uint // struct pppol2tp_addr -> pid *(*C.int)(unsafe.Pointer(&buf[idx])) = C.int(0) idx += C.sizeof_int // struct pppol2tp_addr -> fd *(*C.int)(unsafe.Pointer(&buf[idx])) = C.int(-1) idx += C.sizeof_int // struct pppol2tp_addr -> addr idx += C.sizeof_struct_sockaddr_in // struct pppol2tp_addr -> s_tunnel *(*C.__u16)(unsafe.Pointer(&buf[idx])) = C.__u16(tunnelID) idx += C.sizeof___u16 // struct pppol2tp_addr -> s_session *(*C.__u16)(unsafe.Pointer(&buf[idx])) = C.__u16(sessionID) idx += C.sizeof___u16 // struct pppol2tp_addr -> d_tunnel *(*C.__u16)(unsafe.Pointer(&buf[idx])) = C.__u16(peerTunnelID) idx += C.sizeof___u16 // struct pppol2tp_addr -> d_session *(*C.__u16)(unsafe.Pointer(&buf[idx])) = C.__u16(peerSessionID) idx += C.sizeof___u16 return (*C.struct_sockaddr)(unsafe.Pointer(&sa)), C.sizeof_struct_sockaddr_pppol2tp, nil } func socketPPPoL2TPv4(tunnelID, sessionID, peerTunnelID, peerSessionID l2tp.ControlConnID) (int, error) { addr, addrLen, err := newSockaddrPPPoL2TP4(tunnelID, sessionID, peerTunnelID, peerSessionID) if err != nil { return -1, fmt.Errorf("failed to build struct sockaddr_pppol2tp: %v", err) } fd, err := C.socket(C.AF_PPPOX, C.SOCK_DGRAM, C.PX_PROTO_OL2TP) if fd < 0 { return -1, fmt.Errorf("failed to open pppox socket: %v", err) } ret, err := C.connect(fd, addr, addrLen) if ret < 0 { C.close(fd) return -1, fmt.Errorf("failed to connect pppox socket: %v", err) } return int(fd), nil } func socketPPPoE(sessionID uint16, destHWAddr [6]byte, interfaceName string) (int, error) { addr, addrLen, err := newSockaddrPPPoE(sessionID, destHWAddr, interfaceName) if err != nil { return -1, fmt.Errorf("failed to build struct sockaddr_pppox: %v", err) } fd, err := C.socket(C.AF_PPPOX, C.SOCK_DGRAM, C.PX_PROTO_OE) if fd < 0 { return -1, fmt.Errorf("failed to open pppox socket: %v", err) } ret, err := C.connect(fd, addr, addrLen) if ret < 0 { C.close(fd) return -1, fmt.Errorf("failed to connect pppox socket: %v", err) } return int(fd), nil } golang-github-katalix-go-l2tp-0.1.7/cmd/kpppoed/000077500000000000000000000000001456471064000213775ustar00rootroot00000000000000golang-github-katalix-go-l2tp-0.1.7/cmd/kpppoed/kpppoed.go000066400000000000000000000355301456471064000233760ustar00rootroot00000000000000/* The kpppoed command is a PPPoE Access Concentrator daemon for switching PPPoE sessions into L2TPv2 sessions. Package pppoe is used for managing a PPPoE connection, and for building and parsing PPPoE discovery protocol messages. Management of the L2TP protocol is via. the kl2tpd daemon, which must be installed at the well-known path /usr/sbin/kl2tpd. kpppoed is configured using a simple TOML file. This example configuration shows the parameters that are accepted: # ac_name is the name that kpppoed will use in the PPPoE AC Name tag sent # in PADO packets. If not specified it will default to "kpppoed". ac_name = "MyAccessConcentrator.2000" # interface_name is the name of the network interface that kpppoed will listen # on for PPPoE discovery packets. It must be specified. interface_name = "eth0" # services is a list of service names that kpppoed will advertise in PADO packets # At least one service must be specified. services = [ "serviceA", "serviceB", "serviceC" ] # lns_ipaddr is the IP address and port of the L2TP server to tunnel # pppoe sessions to. The LNS address must be specified. lns_ipaddr = "3.22.1.9:1701" */ package main import ( "flag" "fmt" stdlog "log" "math/rand" "os" "os/signal" "sync" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/katalix/go-l2tp/config" "github.com/katalix/go-l2tp/pppoe" "golang.org/x/sys/unix" ) type kpppoedConfig struct { acName string ifName string services []string lnsIPAddr string } type pppoeSession struct { lock *sync.Mutex logger log.Logger isOpen bool l2tpTid, l2tpSid uint32 sid pppoe.PPPoESessionID peerHWAddr [6]byte l2tpd l2tpd } type application struct { wg sync.WaitGroup config *kpppoedConfig logger log.Logger conn *pppoe.PPPoEConn l2tpdRunner l2tpdRunner sessions map[pppoe.PPPoESessionID]*pppoeSession sigChan chan os.Signal rxChan chan []byte l2tpdEvtChan chan interface{} closeChan chan interface{} l2tpCompleteChan chan *pppoeSession } func ifaceToString(key string, v interface{}) (s string, err error) { s, ok := v.(string) if !ok { return "", fmt.Errorf("failed to parse %s as a string", key) } return } func ifaceToStringList(key string, v interface{}) (sl []string, err error) { l, ok := v.([]interface{}) if !ok { return nil, fmt.Errorf("failed to parse %s as an array", key) } for _, vv := range l { s, err := ifaceToString(fmt.Sprintf("%v in %s", vv, key), vv) if err != nil { return nil, err } sl = append(sl, s) } return } func (cfg *kpppoedConfig) ParseParameter(key string, value interface{}) (err error) { var n string switch key { case "ac_name": n, err = ifaceToString(key, value) if err != nil { return } if cfg.acName != "" { return fmt.Errorf("cannot specify ac_name multiple times in configuration") } cfg.acName = n case "interface_name": n, err = ifaceToString(key, value) if err != nil { return } if cfg.ifName != "" { return fmt.Errorf("cannot specify interface_name multiple times in configuration") } cfg.ifName = n case "services": cfg.services, err = ifaceToStringList(key, value) if err != nil { return } case "lns_ipaddr": cfg.lnsIPAddr, err = ifaceToString(key, value) if err != nil { return } default: return fmt.Errorf("unrecognised parameter %v", key) } return nil } func (cfg *kpppoedConfig) ParseTunnelParameter(tunnel *config.NamedTunnel, key string, value interface{}) error { return fmt.Errorf("unrecognised parameter %v", key) } func (cfg *kpppoedConfig) ParseSessionParameter(tunnel *config.NamedTunnel, session *config.NamedSession, key string, value interface{}) error { return fmt.Errorf("unrecognised parameter %v", key) } func newApplication(l2tpdRunner l2tpdRunner, cfg *kpppoedConfig, verbose bool) (app *application, err error) { app = &application{ l2tpdRunner: l2tpdRunner, config: cfg, sessions: make(map[pppoe.PPPoESessionID]*pppoeSession), sigChan: make(chan os.Signal, 1), rxChan: make(chan []byte), l2tpdEvtChan: make(chan interface{}, 5), closeChan: make(chan interface{}), l2tpCompleteChan: make(chan *pppoeSession), } if l2tpdRunner == nil { return nil, fmt.Errorf("must have l2tpd runner") } if cfg == nil { return nil, fmt.Errorf("must have application configuration") } signal.Notify(app.sigChan, unix.SIGINT, unix.SIGTERM) rand.Seed(time.Now().UnixNano()) logger := log.NewLogfmtLogger(os.Stderr) if verbose { app.logger = level.NewFilter(logger, level.AllowDebug()) } else { app.logger = level.NewFilter(logger, level.AllowInfo()) } app.conn, err = pppoe.NewDiscoveryConnection(app.config.ifName) if err != nil { return nil, fmt.Errorf("failed to create PPPoE connection: %v", err) } return } func (app *application) sendPacket(pkt *pppoe.PPPoEPacket) (err error) { err = pkt.Validate() if err != nil { return fmt.Errorf("failed to validate %s: %v", pkt.Code, err) } b, err := pkt.ToBytes() if err != nil { return fmt.Errorf("unable to encode %s: %v", pkt.Code, err) } level.Debug(app.logger).Log("message", "send", "packet", pkt) _, err = app.conn.Send(b) return } func (app *application) mapServiceName(requested string) (got string, err error) { // empty service name is a wildcard, so anything will do if requested == "" { return requested, nil } for _, sn := range app.config.services { if sn == requested { return requested, nil } } return requested, fmt.Errorf("requested service \"%s\" not available", requested) } func (app *application) getPacketServiceName(pkt *pppoe.PPPoEPacket) (sn string, err error) { serviceNameTag, err := pkt.GetTag(pppoe.PPPoETagTypeServiceName) if err != nil { // Don't expect this to occur since service name is mandatory and // pppoe.Validate() is done as a part of parsing incoming messages. // TODO: panic? return } return app.mapServiceName(string(serviceNameTag.Data)) } func (app *application) appendEchoedTags(in, out *pppoe.PPPoEPacket) (err error) { hostUniqTag, err := in.GetTag(pppoe.PPPoETagTypeHostUniq) if err == nil { err = out.AddHostUniqTag(hostUniqTag.Data) if err != nil { return fmt.Errorf("failed to add host uniq tag to %s: %v", out.Code, err) } } relaySessionIDTag, err := in.GetTag(pppoe.PPPoETagTypeRelaySessionID) if err == nil { err = out.AddTag(pppoe.PPPoETagTypeRelaySessionID, relaySessionIDTag.Data) if err != nil { return fmt.Errorf("failed to add relay session ID tag to %s: %v", out.Code, err) } } return nil } func (app *application) genSessionID() (sid pppoe.PPPoESessionID, err error) { for i := 0; i < 100; i++ { // session ID is a 16 bit number, but 0 is not a valid ID sid = pppoe.PPPoESessionID(1 + rand.Intn(65534)) // don't duplicate an existing session ID if _, ok := app.sessions[sid]; !ok { return } } return pppoe.PPPoESessionID(0), fmt.Errorf("exhausted session ID space") } func (app *application) handlePADI(pkt *pppoe.PPPoEPacket) (err error) { serviceName, err := app.getPacketServiceName(pkt) if err != nil { // We don't like the service name, so just ignore the request. return } pado, err := pppoe.NewPADO( app.conn.HWAddr(), pkt.SrcHWAddr, serviceName, app.config.acName) if err != nil { return fmt.Errorf("failed to build PADO: %v", err) } err = app.appendEchoedTags(pkt, pado) if err != nil { return } /* TODO: AC cookie */ return app.sendPacket(pado) } func (app *application) handlePADR(pkt *pppoe.PPPoEPacket) (err error) { sessionID := pppoe.PPPoESessionID(0) errorReason := "" // If we don't like the service name or fail to allocate resources, // we need to send a PADS indicating the error condition. serviceName, err := app.getPacketServiceName(pkt) if err != nil { errorReason = err.Error() } if errorReason == "" { sessionID, err = app.genSessionID() if err != nil { errorReason = fmt.Sprintf("failed to allocate session ID: %v", err) } } sessionLogger := log.With(app.logger, "pppoe_session_id", sessionID) // Spawn an l2tpd instance to bring up the L2TP tunnel and sessions l2tpd, err := app.l2tpdRunner.spawn(sessionID, app.config.ifName, pkt.SrcHWAddr, app.config.lnsIPAddr, sessionLogger, app) if err != nil { errorReason = fmt.Sprintf("failed to instantiate L2TP daemon: %v", err) } // If we fail to build the PADS or send it, there's not much we can // do to let the peer know, so just fail silently. pads, err := pppoe.NewPADS( app.conn.HWAddr(), pkt.SrcHWAddr, serviceName, sessionID) if err != nil { return } err = app.appendEchoedTags(pkt, pads) if err != nil { return } if errorReason != "" { err = pads.AddServiceNameErrorTag(errorReason) if err != nil { return } } err = app.sendPacket(pads) if err != nil { return } // Keep track of the session now sess := &pppoeSession{ lock: &sync.Mutex{}, logger: sessionLogger, isOpen: true, sid: sessionID, peerHWAddr: pkt.SrcHWAddr, l2tpd: l2tpd, } level.Info(sess.logger).Log("message", "pppoe session established, bringing up L2TP") app.sessions[sessionID] = sess app.wg.Add(1) go func() { defer app.wg.Done() err = sess.l2tpd.wait() if err != nil { level.Error(sess.logger).Log( "message", "l2tp daemon exited with an error code", "error", err) } app.l2tpCompleteChan <- sess }() return } func (app *application) handlePADT(pkt *pppoe.PPPoEPacket) (err error) { _, ok := app.sessions[pkt.SessionID] if !ok { return fmt.Errorf("unrecognised session ID %v", pkt.SessionID) } app.closePPPoESession(pkt.SessionID, "peer sent PADT", false) return } func (app *application) handlePacket(pkt *pppoe.PPPoEPacket) (err error) { level.Debug(app.logger).Log("message", "recv", "packet", pkt) switch pkt.Code { case pppoe.PPPoECodePADI: return app.handlePADI(pkt) case pppoe.PPPoECodePADR: return app.handlePADR(pkt) case pppoe.PPPoECodePADT: return app.handlePADT(pkt) case pppoe.PPPoECodePADO, pppoe.PPPoECodePADS: return fmt.Errorf("unexpected PPPoE %v packet", pkt.Code) } return fmt.Errorf("unhandled PPPoE code %v", pkt.Code) } func (app *application) sendPADT(sess *pppoeSession, reason string) (err error) { padt, err := pppoe.NewPADT(app.conn.HWAddr(), sess.peerHWAddr, sess.sid) if err != nil { return } if reason != "" { err = padt.AddGenericErrorTag(reason) if err != nil { return } } return app.sendPacket(padt) } func (app *application) closePPPoESession(sid pppoe.PPPoESessionID, reason string, sendPADT bool) { sess, ok := app.sessions[sid] if !ok { level.Warn(app.logger).Log( "message", "attempted to close unrecognised session", "session_id", sid) return } isOpen := false sess.lock.Lock() isOpen = sess.isOpen sess.isOpen = false sess.lock.Unlock() // Don't do anything if the session is closed already. if !isOpen { return } var req string if sendPADT { req = "local" } else { req = "network" } level.Info(sess.logger).Log( "message", "close", "request_origin", req, "shutdown_reason", reason) if sendPADT { err := app.sendPADT(sess, reason) if err != nil { level.Error(sess.logger).Log( "message", "failed to send PADT", "error", err) } } // Kill off l2tpd level.Info(sess.logger).Log("message", "terminate l2tpd") app.wg.Add(1) go func() { defer app.wg.Done() sess.l2tpd.terminate() }() } func (app *application) onL2TPEstablished(sess *pppoeSession, tunnelID, sessionID uint32) { level.Info(sess.logger).Log( "message", "l2tp established", "l2tp_tunnel_id", tunnelID, "l2tp_session_id", sessionID) sess.l2tpTid = tunnelID sess.l2tpSid = sessionID return } func (app *application) onL2TPDown(sess *pppoeSession) { level.Info(sess.logger).Log("message", "l2tp down") app.closePPPoESession(sess.sid, "l2tp session went down", true) } // l2tpd event handler func (app *application) handleEvent(ev interface{}) { app.l2tpdEvtChan <- ev } func (app *application) run() int { app.wg.Add(1) go func() { defer app.wg.Done() for { buf := make([]byte, 1500) _, err := app.conn.Recv(buf) if err != nil { level.Error(app.logger).Log( "message", "recv on PPPoE discovery connection failed", "error", err) break } app.rxChan <- buf } }() var shutdown bool for { select { case <-app.sigChan: if !shutdown { level.Info(app.logger).Log("message", "received signal, shutting down") shutdown = true go func() { app.conn.Close() for sid := range app.sessions { app.closePPPoESession(sid, "application shutdown due to signal", true) } app.wg.Wait() level.Info(app.logger).Log("message", "graceful shutdown complete") close(app.closeChan) }() } else { level.Info(app.logger).Log("message", "pending graceful shutdown") } case sess, ok := <-app.l2tpCompleteChan: if ok { app.closePPPoESession(sess.sid, "l2tp daemon exited", true) delete(app.sessions, sess.sid) } case rx, ok := <-app.rxChan: if ok { pkts, err := pppoe.ParsePacketBuffer(rx) if err != nil { level.Error(app.logger).Log( "message", "failed to parse received message(s)", "error", err) continue } for _, pkt := range pkts { err = app.handlePacket(pkt) if err != nil { level.Error(app.logger).Log("message", "failed to handle message", "type", pkt.Code, "error", err) } } } case ev, ok := <-app.l2tpdEvtChan: if ok { switch event := ev.(type) { case *l2tpSessionUp: if session, got := app.sessions[event.pppoeSessionID]; got { app.onL2TPEstablished(session, event.l2tpTunnelID, event.l2tpSessionID) } case *l2tpSessionDown: if session, got := app.sessions[event.pppoeSessionID]; got { app.onL2TPDown(session) } } } case <-app.closeChan: return 0 } } } func main() { cfg := kpppoedConfig{} cfgPathPtr := flag.String("config", "/etc/kpppoed/kpppoed.toml", "specify configuration file path") verbosePtr := flag.Bool("verbose", false, "toggle verbose log output") flag.Parse() _, err := config.LoadFileWithCustomParser(*cfgPathPtr, &cfg) if err != nil { stdlog.Fatalf("failed to load configuration: %v", err) } if len(cfg.services) == 0 { stdlog.Fatalf("no services called out in the configuration file") } if cfg.ifName == "" { stdlog.Fatalf("no interface name called out in the configuration file") } if cfg.lnsIPAddr == "" { stdlog.Fatalf("no LNS IP address called out in the configuration file") } if cfg.acName == "" { cfg.acName = "kpppoed" } l2tpdRunner, err := newKl2tpdRunner() if err != nil { stdlog.Fatalf("failed to instantiate kl2tpd runner: %v", err) } app, err := newApplication(l2tpdRunner, &cfg, *verbosePtr) if err != nil { stdlog.Fatalf("failed to instantiate application: %v", err) } os.Exit(app.run()) } golang-github-katalix-go-l2tp-0.1.7/cmd/kpppoed/kpppoed_test.go000066400000000000000000000312161456471064000244320ustar00rootroot00000000000000package main import ( "fmt" "os/exec" "os/user" "reflect" "sync" "testing" "time" "github.com/katalix/go-l2tp/config" "github.com/katalix/go-l2tp/pppoe" ) const ( testVeth0 = "vetest0" testVeth1 = "vetest1" ) func createTestVethPair() (err error) { cmd := exec.Command("sudo", "ip", "link", "add", "dev", testVeth0, "type", "veth", "peer", "name", testVeth1) err = cmd.Run() if err != nil { return fmt.Errorf("unable to create veth pair: %v", err) } cmd = exec.Command("sudo", "ip", "link", "set", testVeth0, "up") err = cmd.Run() if err != nil { return fmt.Errorf("unable to set %s up: %v", testVeth0, err) } cmd = exec.Command("sudo", "ip", "link", "set", testVeth1, "up") err = cmd.Run() if err != nil { return fmt.Errorf("unable to set %s up: %v", testVeth1, err) } return nil } func deleteTestVethPair() (err error) { cmd := exec.Command("sudo", "ip", "link", "delete", "dev", testVeth0) err = cmd.Run() if err != nil { return fmt.Errorf("failed to delete veth interface %s: %v", testVeth0, err) } return nil } type kpppoedTestApp struct { app *application wg sync.WaitGroup } func newKpppoedTestApp(cfg *kpppoedConfig) (testApp *kpppoedTestApp, err error) { testApp = &kpppoedTestApp{} testApp.app, err = newApplication(&nilL2tpdRunner{}, cfg, true) if err != nil { return nil, err } testApp.wg.Add(1) go func() { defer testApp.wg.Done() testApp.app.run() }() return } func (testApp *kpppoedTestApp) Close() { close(testApp.app.closeChan) testApp.wg.Wait() } type testClient struct { conn *pppoe.PPPoEConn wg sync.WaitGroup rxChan chan []byte } func newTestClient(ifName string) (tc *testClient, err error) { tc = &testClient{ rxChan: make(chan []byte, 5), } tc.conn, err = pppoe.NewDiscoveryConnection(ifName) if err != nil { return nil, err } tc.wg.Add(1) go func() { defer tc.wg.Done() for { buf := make([]byte, 1500) _, err := tc.conn.Recv(buf) if err != nil { close(tc.rxChan) break } tc.rxChan <- buf } }() return } func (tc *testClient) recvPacket(timeout time.Duration) (pkt *pppoe.PPPoEPacket, err error) { select { case buf, ok := <-tc.rxChan: if !ok { return nil, fmt.Errorf("connection recv channel closed") } parsed, err := pppoe.ParsePacketBuffer(buf) if err != nil { return nil, err } if len(parsed) != 1 { return nil, fmt.Errorf("expected 1 packet, got %d", len(parsed)) } return parsed[0], nil case <-time.After(timeout): return nil, fmt.Errorf("rx timed out after %v", timeout) } } func (tc *testClient) sendPacket(pkt *pppoe.PPPoEPacket) (err error) { b, err := pkt.ToBytes() if err != nil { return } _, err = tc.conn.Send(b) return } func (tc *testClient) Close() { tc.conn.Close() tc.wg.Wait() } type testTagIn struct { id pppoe.PPPoETagType data []byte } func checkPktType(pkt *pppoe.PPPoEPacket, typ pppoe.PPPoECode, t *testing.T) { if pkt.Code != typ { t.Errorf("received %s, expected %s", pkt.Code, typ) } } func checkRspIsPADO(pkt *pppoe.PPPoEPacket, t *testing.T) { checkPktType(pkt, pppoe.PPPoECodePADO, t) } func checkRspIsPADS(pkt *pppoe.PPPoEPacket, t *testing.T) { checkPktType(pkt, pppoe.PPPoECodePADS, t) } func checkHasTag(pkt *pppoe.PPPoEPacket, t *testing.T, typ pppoe.PPPoETagType) (tag *pppoe.PPPoETag) { tag, err := pkt.GetTag(typ) if err != nil { t.Fatalf("no tag %s", typ) } return } func checkTagData(pkt *pppoe.PPPoEPacket, t *testing.T, typ pppoe.PPPoETagType, data []byte) { tag := checkHasTag(pkt, t, typ) if !reflect.DeepEqual(tag.Data, data) { t.Fatalf("expected %s to contain %q, got %q", typ, data, tag.Data) } } func checkAddTags(pkt *pppoe.PPPoEPacket, tags []testTagIn, t *testing.T) { for _, tag := range tags { err := pkt.AddTag(tag.id, tag.data) if err != nil { t.Fatalf("AddTag: %v", err) } } } func checkSendRecv(client *testClient, pkt *pppoe.PPPoEPacket, timeout time.Duration, expectSilence bool, checkRsp func(pkt *pppoe.PPPoEPacket, t *testing.T), t *testing.T) (rsp *pppoe.PPPoEPacket) { err := client.sendPacket(pkt) if err != nil { t.Fatalf("sendPacket: %v", err) } rsp, err = client.recvPacket(timeout) if expectSilence { if err == nil { t.Errorf("recvPacket: expect timeout error but didn't get one") } if rsp != nil { t.Errorf("recvPacket: expected no reply, but got packet: %v", rsp) } } else { if err != nil { t.Fatalf("recvPacket: %v", err) } if checkRsp != nil { checkRsp(rsp, t) } } return } func testPADI(t *testing.T) { service0 := "Super_Internet_03A" service1 := "MyMagicalService2001" service2 := "transx.world.com.gateway" hostUniq0 := []byte{0x42, 0x12, 0xee, 0xf4, 0x91, 0x00, 0x72} hostUniq1 := []byte{} relaySessionID0 := []byte{ 0x01, 0x02, 0x03, 0x04, 0x11, 0x12, 0x13, 0x14, 0xa1, 0xa2, 0xa3, 0xa4, } dfltCfg := &kpppoedConfig{ acName: "bobby", services: []string{service1, service2, service0}, ifName: testVeth0, } cases := []struct { name string service string tags []testTagIn expectSilence bool checkRsp func(pkt *pppoe.PPPoEPacket, t *testing.T) }{ { name: "service0", service: service0, checkRsp: checkRspIsPADO, }, { name: "service1", service: service1, checkRsp: checkRspIsPADO, }, { name: "service2", service: service2, checkRsp: checkRspIsPADO, }, { name: "badservice", service: "badservice", expectSilence: true, }, { name: "hostUniq0", service: service0, tags: []testTagIn{ { id: pppoe.PPPoETagTypeHostUniq, data: hostUniq0, }, }, checkRsp: func(pkt *pppoe.PPPoEPacket, t *testing.T) { checkRspIsPADO(pkt, t) checkTagData(pkt, t, pppoe.PPPoETagTypeHostUniq, hostUniq0) }, }, { name: "hostUniq1", service: service0, tags: []testTagIn{ { id: pppoe.PPPoETagTypeHostUniq, data: hostUniq1, }, }, checkRsp: func(pkt *pppoe.PPPoEPacket, t *testing.T) { checkRspIsPADO(pkt, t) checkTagData(pkt, t, pppoe.PPPoETagTypeHostUniq, hostUniq1) }, }, { name: "relaySessionID0", service: service0, tags: []testTagIn{ { id: pppoe.PPPoETagTypeRelaySessionID, data: relaySessionID0, }, }, checkRsp: func(pkt *pppoe.PPPoEPacket, t *testing.T) { checkRspIsPADO(pkt, t) checkTagData(pkt, t, pppoe.PPPoETagTypeRelaySessionID, relaySessionID0) }, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { app, err := newKpppoedTestApp(dfltCfg) if err != nil { t.Fatalf("newKpppoedTestApp: %v", err) } defer app.Close() client, err := newTestClient(testVeth1) if err != nil { t.Fatalf("newTestClient: %v", err) } defer client.Close() padi, err := pppoe.NewPADI(client.conn.HWAddr(), c.service) if err != nil { t.Fatalf("NewPADI: %v", err) } checkAddTags(padi, c.tags, t) _ = checkSendRecv(client, padi, 250*time.Millisecond, c.expectSilence, c.checkRsp, t) }) } } type testPktIn struct { service string tags []testTagIn expectSilence bool checkRsp func(pkt *pppoe.PPPoEPacket, t *testing.T) } func testPADR(t *testing.T) { service0 := "Super_Internet_03A" service1 := "MyMagicalService2001" service2 := "transx.world.com.gateway" hostUniq0 := []byte{0x42, 0x12, 0xee, 0xf4, 0x91, 0x00, 0x72} relaySessionID0 := []byte{ 0x01, 0x02, 0x03, 0x04, 0x11, 0x12, 0x13, 0x14, 0xa1, 0xa2, 0xa3, 0xa4, } dfltCfg := &kpppoedConfig{ acName: "bobby", services: []string{service1, service2, service0}, ifName: testVeth0, } cases := []struct { name string padi *testPktIn padr *testPktIn }{ { name: "establish0", padi: &testPktIn{ service: service2, tags: []testTagIn{ { id: pppoe.PPPoETagTypeHostUniq, data: hostUniq0, }, }, checkRsp: func(pkt *pppoe.PPPoEPacket, t *testing.T) { checkRspIsPADO(pkt, t) checkTagData(pkt, t, pppoe.PPPoETagTypeHostUniq, hostUniq0) }, }, padr: &testPktIn{ service: service2, tags: []testTagIn{ { id: pppoe.PPPoETagTypeHostUniq, data: hostUniq0, }, }, checkRsp: func(pkt *pppoe.PPPoEPacket, t *testing.T) { checkRspIsPADS(pkt, t) checkTagData(pkt, t, pppoe.PPPoETagTypeHostUniq, hostUniq0) if pkt.SessionID == 0 { t.Errorf("expect non-zero session ID") } }, }, }, { name: "establish1", padi: &testPktIn{ service: service1, tags: []testTagIn{ { id: pppoe.PPPoETagTypeHostUniq, data: hostUniq0, }, { id: pppoe.PPPoETagTypeRelaySessionID, data: relaySessionID0, }, }, checkRsp: func(pkt *pppoe.PPPoEPacket, t *testing.T) { checkRspIsPADO(pkt, t) checkTagData(pkt, t, pppoe.PPPoETagTypeHostUniq, hostUniq0) checkTagData(pkt, t, pppoe.PPPoETagTypeRelaySessionID, relaySessionID0) }, }, padr: &testPktIn{ service: service1, tags: []testTagIn{ { id: pppoe.PPPoETagTypeHostUniq, data: hostUniq0, }, { id: pppoe.PPPoETagTypeRelaySessionID, data: relaySessionID0, }, }, checkRsp: func(pkt *pppoe.PPPoEPacket, t *testing.T) { checkRspIsPADS(pkt, t) checkTagData(pkt, t, pppoe.PPPoETagTypeHostUniq, hostUniq0) checkTagData(pkt, t, pppoe.PPPoETagTypeRelaySessionID, relaySessionID0) if pkt.SessionID == 0 { t.Errorf("expect non-zero session ID") } }, }, }, { name: "badservice", padi: &testPktIn{ service: service1, checkRsp: checkRspIsPADO, }, padr: &testPktIn{ service: "badservice", tags: []testTagIn{ { id: pppoe.PPPoETagTypeHostUniq, data: hostUniq0, }, { id: pppoe.PPPoETagTypeRelaySessionID, data: relaySessionID0, }, }, checkRsp: func(pkt *pppoe.PPPoEPacket, t *testing.T) { checkRspIsPADS(pkt, t) checkTagData(pkt, t, pppoe.PPPoETagTypeHostUniq, hostUniq0) checkTagData(pkt, t, pppoe.PPPoETagTypeRelaySessionID, relaySessionID0) if pkt.SessionID != 0 { t.Errorf("expect zero session ID") } checkHasTag(pkt, t, pppoe.PPPoETagTypeServiceNameError) }, }, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { app, err := newKpppoedTestApp(dfltCfg) if err != nil { t.Fatalf("newKpppoedTestApp: %v", err) } defer app.Close() client, err := newTestClient(testVeth1) if err != nil { t.Fatalf("newTestClient: %v", err) } defer client.Close() padi, err := pppoe.NewPADI(client.conn.HWAddr(), c.padi.service) if err != nil { t.Fatalf("NewPADI: %v", err) } checkAddTags(padi, c.padi.tags, t) pado := checkSendRecv(client, padi, 250*time.Millisecond, c.padi.expectSilence, c.padi.checkRsp, t) if c.padr != nil { padr, err := pppoe.NewPADR(client.conn.HWAddr(), pado.SrcHWAddr, c.padr.service) if err != nil { t.Fatalf("NewPADR: %v", err) } checkAddTags(padr, c.padr.tags, t) acCookieTag, err := pado.GetTag(pppoe.PPPoETagTypeACCookie) if err == nil { checkAddTags(padr, []testTagIn{{id: acCookieTag.Type, data: acCookieTag.Data}}, t) } checkSendRecv(client, padr, 250*time.Millisecond, c.padr.expectSilence, c.padr.checkRsp, t) } }) } } func TestRequiresRoot(t *testing.T) { // These tests need root permissions, so verify we have those first of all user, err := user.Current() if err != nil { t.Errorf("Unable to obtain current user: %q", err) } if user.Uid != "0" { t.Skip("skipping test because we don't have root permissions") } // Set up veth pair to use for connection tests _ = deleteTestVethPair() err = createTestVethPair() if err != nil { t.Fatalf("%v", err) } tests := []struct { name string testFn func(t *testing.T) }{ { name: "PADI", testFn: testPADI, }, { name: "PADR", testFn: testPADR, }, } for _, sub := range tests { t.Run(sub.name, sub.testFn) } // Tear down veth pair err = deleteTestVethPair() if err != nil { t.Errorf("%v", err) } } func TestConfigParser(t *testing.T) { cases := []struct { in string expectFail bool out *kpppoedConfig }{ { in: `ac_name = "wombles" interface_name = "eth0" services = [ "DeathStar", "tatoonie" ] lns_ipaddr = "192.168.21.12:1701" `, out: &kpppoedConfig{ acName: "wombles", ifName: "eth0", services: []string{"DeathStar", "tatoonie"}, lnsIPAddr: "192.168.21.12:1701", }, }, } for _, c := range cases { cfg := &kpppoedConfig{} _, err := config.LoadStringWithCustomParser(c.in, cfg) if err != nil { t.Fatalf("LoadStringWithCustomParser: %v", err) } if !reflect.DeepEqual(cfg, c.out) { t.Fatalf("expect %v, got %v", c.out, cfg) } } } golang-github-katalix-go-l2tp-0.1.7/cmd/kpppoed/l2tpd.go000066400000000000000000000011761456471064000227600ustar00rootroot00000000000000package main import ( "github.com/go-kit/kit/log" "github.com/katalix/go-l2tp/pppoe" ) type l2tpSessionUp struct { pppoeSessionID pppoe.PPPoESessionID l2tpTunnelID uint32 l2tpSessionID uint32 } type l2tpSessionDown struct { pppoeSessionID pppoe.PPPoESessionID l2tpTunnelID uint32 l2tpSessionID uint32 } type l2tpEventHandler interface { handleEvent(event interface{}) } type l2tpdRunner interface { spawn(sessionID pppoe.PPPoESessionID, ifName string, peerMAC [6]byte, lnsIPAddr string, logger log.Logger, eventHandler l2tpEventHandler) (l2tpd, error) } type l2tpd interface { wait() error terminate() } golang-github-katalix-go-l2tp-0.1.7/cmd/kpppoed/l2tpd_kl2tpd.go000066400000000000000000000147231456471064000242420ustar00rootroot00000000000000package main import ( "bufio" "fmt" "io" "io/ioutil" "os" "os/exec" "regexp" "strconv" "sync" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/katalix/go-l2tp/pppoe" ) var _ l2tpdRunner = (*kl2tpdRunner)(nil) var _ l2tpd = (*kl2tpd)(nil) type kl2tpEvent int const ( kl2tpdTunnelCreated kl2tpEvent = 0 kl2tpdSessionCreated kl2tpEvent = 1 kl2tpdSessionEstablished kl2tpEvent = 2 kl2tpdSessionDestroyed kl2tpEvent = 3 kl2tpdTunnelDestroyed kl2tpEvent = 4 kl2tpdErrorMessage kl2tpEvent = 5 ) func (e kl2tpEvent) String() string { switch e { case kl2tpdTunnelCreated: return "tunnel created" case kl2tpdSessionCreated: return "session created" case kl2tpdSessionEstablished: return "session established" case kl2tpdSessionDestroyed: return "session destroyed" case kl2tpdTunnelDestroyed: return "tunnel destroyed" case kl2tpdErrorMessage: return "error message" } return "unknown event" } type kl2tpdRunner struct { execPath string } type kl2tpd struct { logRegexp map[kl2tpEvent]*regexp.Regexp wg sync.WaitGroup eventHandler l2tpEventHandler sid pppoe.PPPoESessionID kl2tpd *exec.Cmd logger log.Logger } func newKl2tpdRunner() (runner *kl2tpdRunner, err error) { return &kl2tpdRunner{ // TODO: could search likely candidates to find kl2tpd execPath: "/usr/sbin/kl2tpd", }, nil } func (runner *kl2tpdRunner) genCfg(peerIPAddr string, sessionId pppoe.PPPoESessionID, ifName string, peerMac [6]byte, out *os.File) (err error) { cfg := []string{ `[tunnel.t1]`, fmt.Sprintf(`peer = "%s"`, peerIPAddr), `version = "l2tpv2"`, `encap = "udp"`, `[tunnel.t1.session.s1]`, `pseudowire = "pppac"`, fmt.Sprintf(`pppoe_session_id = %d`, sessionId), fmt.Sprintf(`interface_name = "%s"`, ifName), fmt.Sprintf(`pppoe_peer_mac = [ 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x ]`, peerMac[0], peerMac[1], peerMac[2], peerMac[3], peerMac[4], peerMac[5]), } for _, s := range cfg { _, err = out.WriteString(fmt.Sprintf("%s\n", s)) if err != nil { break } } return } func (runner *kl2tpdRunner) spawn(sessionID pppoe.PPPoESessionID, ifName string, peerMAC [6]byte, lnsIPAddr string, logger log.Logger, eventHandler l2tpEventHandler) (daemon l2tpd, err error) { d := &kl2tpd{ sid: sessionID, logRegexp: make(map[kl2tpEvent]*regexp.Regexp), logger: logger, eventHandler: eventHandler, } daemon = d /* Regular expressions for kl2tpd logging to derive events. To keep things simple we're relying on the fact that we have just one tunnel and one session, with the well-known names as per the autogenerated kl2tpd configuration file. If kl2tpd logging changes, or the configuration file changes, these expressions may need to be updated accordingly! */ d.logRegexp[kl2tpdTunnelCreated] = regexp.MustCompile( "^.*new dynamic tunnel.* tunnel_id=([0-9]+)") d.logRegexp[kl2tpdSessionCreated] = regexp.MustCompile( "^.*new dynamic session.* session_id=([0-9]+)") d.logRegexp[kl2tpdSessionEstablished] = regexp.MustCompile( "^.*session_name=s1 message=\"data plane established\"") d.logRegexp[kl2tpdSessionDestroyed] = regexp.MustCompile( "^.*session_name=s1 message=close") d.logRegexp[kl2tpdTunnelDestroyed] = regexp.MustCompile( "^.*tunnel_name=t1 message=close") d.logRegexp[kl2tpdErrorMessage] = regexp.MustCompile( "^.*level=error") cfgFile, err := ioutil.TempFile(os.TempDir(), "kpppoed.kl2tpd.") if err != nil { return nil, fmt.Errorf("failed to generate kl2tpd configuration: %v", err) } defer cfgFile.Close() err = runner.genCfg(lnsIPAddr, sessionID, ifName, peerMAC, cfgFile) if err != nil { return nil, fmt.Errorf("failed to generate kl2tpd configuration: %v", err) } d.kl2tpd = exec.Command( runner.execPath, "-config", cfgFile.Name(), ) stderrPipe, err := d.kl2tpd.StderrPipe() if err != nil { os.Remove(cfgFile.Name()) return nil, fmt.Errorf("failed to create kl2tpd log stream pipe: %v", err) } d.wg.Add(1) go func() { defer d.wg.Done() defer os.Remove(cfgFile.Name()) d.scanLog(stderrPipe) }() err = d.kl2tpd.Start() if err != nil { // wait for the goroutine d.terminate() return nil, fmt.Errorf("failed to start kl2tpd: %v", err) } return } func (daemon *kl2tpd) wait() error { return daemon.kl2tpd.Wait() } func (daemon *kl2tpd) terminate() { daemon.kl2tpd.Process.Signal(os.Interrupt) daemon.wg.Wait() } func (daemon *kl2tpd) onEvent(ev interface{}) { if daemon.eventHandler != nil { daemon.eventHandler.handleEvent(ev) } } func (daemon *kl2tpd) scanLog(stderrPipe io.ReadCloser) { scanner := bufio.NewScanner(stderrPipe) var l2tpTunnelID, l2tpSessionID int var err error isUp := false for scanner.Scan() { line := scanner.Text() // This is a bit verbose for normal usage, but handy for debugging /* level.Debug(daemon.logger).Log( "message", "kl2tpd log line received", "log", line) */ for et, re := range daemon.logRegexp { if re == nil { continue } match := re.FindStringSubmatch(line) if match == nil { continue } level.Debug(daemon.logger).Log( "message", "kl2tpd event received", "event_type", et) switch et { case kl2tpdTunnelCreated: l2tpTunnelID, err = strconv.Atoi(match[1]) if err != nil { level.Error(daemon.logger).Log( "message", "failed to parse l2tp tunnel ID", "error", err) continue } level.Debug(daemon.logger).Log( "message", "l2tp tunnel created", "tunnel_id", l2tpTunnelID) case kl2tpdSessionCreated: l2tpSessionID, err = strconv.Atoi(match[1]) if err != nil { level.Error(daemon.logger).Log( "message", "failed to parse l2tp session ID", "error", err) continue } level.Debug(daemon.logger).Log( "message", "l2tp session created", "session_id", l2tpSessionID) case kl2tpdSessionEstablished: if isUp { continue } isUp = true daemon.onEvent(&l2tpSessionUp{ pppoeSessionID: daemon.sid, l2tpTunnelID: uint32(l2tpTunnelID), l2tpSessionID: uint32(l2tpSessionID), }) case kl2tpdSessionDestroyed, kl2tpdTunnelDestroyed: isUp = false daemon.onEvent(&l2tpSessionDown{ pppoeSessionID: daemon.sid, l2tpTunnelID: uint32(l2tpTunnelID), l2tpSessionID: uint32(l2tpSessionID), }) daemon.kl2tpd.Process.Signal(os.Interrupt) case kl2tpdErrorMessage: level.Error(daemon.logger).Log( "message", "kl2tpd error", "log", line) } } } } golang-github-katalix-go-l2tp-0.1.7/cmd/kpppoed/l2tpd_nil.go000066400000000000000000000010271456471064000236150ustar00rootroot00000000000000package main import ( "github.com/go-kit/kit/log" "github.com/katalix/go-l2tp/pppoe" ) var _ l2tpdRunner = (*nilL2tpdRunner)(nil) var _ l2tpd = (*nilL2tpd)(nil) type nilL2tpdRunner struct { } type nilL2tpd struct { } func (runner *nilL2tpdRunner) spawn(sessionID pppoe.PPPoESessionID, ifName string, peerMAC [6]byte, lnsIPAddr string, logger log.Logger, eventHandler l2tpEventHandler) (l2tpd, error) { return &nilL2tpd{}, nil } func (l2tpd *nilL2tpd) wait() error { return nil } func (l2tpd *nilL2tpd) terminate() { } golang-github-katalix-go-l2tp-0.1.7/cmd/ql2tpd/000077500000000000000000000000001456471064000211435ustar00rootroot00000000000000golang-github-katalix-go-l2tp-0.1.7/cmd/ql2tpd/ql2tpd.go000066400000000000000000000047151456471064000227070ustar00rootroot00000000000000/* The ql2tpd command is a daemon for creating static L2TPv3 tunnels and sessions. ql2tpd is driven by a configuration file which describes the tunnel and session instances to create. For more information on the configuration file format please refer to package config's documentation. Run with the -help argument for documentation of the command line arguments. Two tunnel modes are supported. By default tunnels are created in static mode, which means that the kernel-space L2TP data plane is created, but no control messages are sent. When running in this mode ql2tpd provides functionality equivalent to the iproute2 l2tp commands. Alternatively, tunnels may be created with a hello_timeout configured, in which case a minimal control plane transport is set up to send and acknowledge keep-alive (HELLO) messages. This mode of operation extends static mode by allowing tunnel failure to be detected. If a given tunnel is determined to have failed (HELLO message transmission fails) then the sessions in that tunnel are automatically torn down. */ package main import ( "flag" stdlog "log" "os" "os/signal" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/katalix/go-l2tp/config" "github.com/katalix/go-l2tp/l2tp" "golang.org/x/sys/unix" ) func main() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, unix.SIGINT, unix.SIGTERM) cfgPathPtr := flag.String("config", "/etc/ql2tpd/ql2tpd.toml", "specify configuration file path") verbosePtr := flag.Bool("verbose", false, "toggle verbose log output") flag.Parse() config, err := config.LoadFile(*cfgPathPtr) if err != nil { stdlog.Fatalf("failed to load l2tp configuration: %v", err) } logger := log.NewLogfmtLogger(os.Stderr) if *verbosePtr { logger = level.NewFilter(logger, level.AllowInfo(), level.AllowDebug()) } else { logger = level.NewFilter(logger, level.AllowInfo()) } l2tpCtx, err := l2tp.NewContext(l2tp.LinuxNetlinkDataPlane, logger) if err != nil { stdlog.Fatalf("failed to load l2tp configuration: %v", err) } defer l2tpCtx.Close() for _, tcfg := range config.Tunnels { tunl, err := l2tpCtx.NewQuiescentTunnel(tcfg.Name, tcfg.Config) if err != nil { stdlog.Fatalf("failed to instantiate tunnel %v: %v", tcfg.Name, err) } for _, scfg := range tcfg.Sessions { _, err := tunl.NewSession(scfg.Name, scfg.Config) if err != nil { stdlog.Fatalf("failed to instantiate session %v in tunnel %v: %v", scfg.Name, tcfg.Name, err) } } } <-sigs } golang-github-katalix-go-l2tp-0.1.7/config/000077500000000000000000000000001456471064000204375ustar00rootroot00000000000000golang-github-katalix-go-l2tp-0.1.7/config/config.go000066400000000000000000000455421456471064000222450ustar00rootroot00000000000000/* Package config implements a parser for L2TP configuration represented in the TOML format: https://github.com/toml-lang/toml. Please refer to the TOML repos for an in-depth description of the syntax. Tunnel and session instances are called out in the configuration file using named TOML tables. Each tunnel or session instance table contains configuration parameters for that instance as key:value pairs. # This is a tunnel instance named "t1" [tunnel.t1] # local specifies the local address that the tunnel should # bind its socket to local = "127.0.0.1:5000" # peer specifies the address of the peer that the tunnel should # connect its socket to peer = "127.0.0.1:5001" # version specifies the version of the L2TP specification the # tunnel should use. # Currently supported values are "l2tpv2" and "l2tpv3" version = "l2tpv3" # encap specifies the encapsulation to be used for the tunnel. # Currently supported values are "udp" and "ip". # L2TPv2 tunnels are UDP only. encap = "udp" # tid specifies the local tunnel ID of the tunnel. # Tunnel IDs must be unique for the host. # L2TPv2 tunnel IDs are 16 bit, and may be in the range 1 - 65535. # L2TPv3 tunnel IDs are 32 bit, and may be in the range 1 - 4294967295. tid = 62719 # ptid specifies the peer's tunnel ID for the tunnel. # The peer's tunnel ID must be unique for the peer, and are unrelated # to the local tunnel ID. # The rules for tunnel ID range apply to the peer tunnel ID too. ptid = 72819 # window_size specifies the initial window size to use for the L2TP # reliable transport algorithm which is used for control protocol # messages. The window size dictates how many control messages the # tunnel may have "in flight" (i.e. pending an ACK from the peer) at # any one time. Tuning the window size can allow high-volume L2TP servers # to improve performance. Generally it won't be necessary to change # this from the default value of 4. window_size = 10 # control messages # hello_timeout if set enables L2TP keep-alive (HELLO) messages. # A hello message is sent N milliseconds after the last control # message was sent or received. It allows for early detection of # tunnel failure on quiet connections. # By default no keep-alive messages are sent. hello_timeout = 7500 # milliseconds # retry_timeout if set tweaks the starting retry timeout for the # reliable transport algorithm used for L2TP control messages. # The algorithm uses an exponential backoff when retrying messages. # By default a starting retry timeout of 1000ms is used. retry_timeout = 1500 # milliseconds # max_retries sets how many times a given control message may be # retried before the transport considers the message transmission to # have failed. # It may be useful to tune this value on unreliable network connections # to avoid suprious tunnel failure, or conversely to allow for quicker # tunnel failure detection on reliable links. # The default is 3 retries. max_retries 5 # host_name sets the host name the tunnel will advertise in the # Host Name AVP per RFC2661. # If unset the host's name will be queried and the returned value used. host_name "basilbrush.local" # framing_caps sets the framing capabilities the tunnel will advertise # in the Framing Capabilities AVP per RFC2661. # The default is to advertise both sync and async framing. framing_caps = ["sync","async"] # This is a session instance called "s1" within parent tunnel "t1". # Session instances are always created inside a parent tunnel. [tunnel.t1.session.s1] # sid specifies the local session ID of the session. # Session IDs must be unique to the tunnel for L2TPv2, or unique to # the peer for L2TPv3. # L2TPv2 session IDs are 16 bit, and may be in the range 1 - 65535. # L2TPv3 session IDs are 32 bit, and may be in the range 1 - 4294967295. sid = 12389 # psid specifies the peer's session ID for the session. # The peer's session ID is unrelated to the local session ID. # The rules for the session ID range apply to the peer session ID too. psid = 1234 # pseudowire specifies the type of layer 2 frames carried by the session. # Currently supported values are "ppp", "eth", and "pppac". # L2TPv2 tunnels support PPP and PPPAC pseudowires only. pseudowire = "eth" # seqnum, if set, enables the transmission of sequence numbers with # L2TP data messages. Use of sequence numbers enables the data plane # to reorder data packets to ensure they are delivered in sequence. # By default sequence numbers are not used. seqnum = false # cookie, if set, specifies the local L2TPv3 cookie for the session. # Cookies are a data verification mechanism intended to allow misdirected # data packets to be detected and rejected. # Transmitted data packets will include the local cookie in their header. # Cookies may be either 4 or 8 bytes long, and contain aribrary data. # By default no local cookie is set. cookie = [ 0x12, 0xe9, 0x54, 0x0f, 0xe2, 0x68, 0x72, 0xbc ] # peer_cookie, if set, specifies the L2TPv3 cookie the peer will send in # the header of its data messages. # Messages received without the peer's cookie (or with the wrong cookie) # will be rejected. # By default no peer cookie is set. peer_cookie = [ 0x74, 0x2e, 0x28, 0xa8 ] # interface_name, if set, specifies the network interface name to be # used for the session instance. # By default the Linux kernel autogenerates an interface name specific to # the pseudowire type, e.g. "l2tpeth0", "ppp0". # Setting the interface name can be useful when you need to be certain # of the interface name a given session will use. # By default the kernel autogenerates an interface name. interface_name = "l2tpeth42" # l2spec_type specifies the L2TPv3 Layer 2 specific sublayer field to # be used in data packet headers as per RFC3931 section 3.2.2. # Currently supported values are "none" and "default". # By default no Layer 2 specific sublayer is used. l2spec_type = "default" # pppoe_session_id specifies the assigned PPPoE session ID for the session. # Per RFC2516, the PPPoE session ID is in the range 1 - 65535 # This parameter only applies to pppac pseudowires. pppoe_session_id = 1234 # pppoe_peer_mac specifies the MAC address of the PPPoE peer for the session. # This parameter only applies to pppac pseudowires. pppoe_peer_mac = [ 0x02, 0x42, 0x94, 0xd1, 0x4e, 0x9a ] */ package config import ( "fmt" "time" "github.com/katalix/go-l2tp/l2tp" "github.com/pelletier/go-toml" ) // Config contains L2TP configuration for tunnel and session instances. type Config struct { // The entire tree as a map as parsed from the TOML representation. // Apps may access this tree to handle their own config tables. Map map[string]interface{} // All the tunnels defined in the configuration. Tunnels []NamedTunnel // Custom parser interface for caller to handle unrecognised key/value pairs. customParser ConfigParser } // NamedTunnel contains L2TP configuration for a tunnel instance, // and the sessions that tunnel contains. type NamedTunnel struct { // The tunnel's name as specified in the config file. Name string // The tunnel L2TP configuration. Config *l2tp.TunnelConfig // The sessions defined within this tunnel in the config file. Sessions []NamedSession } // NamedSession contains L2TP configuration for a session instance. type NamedSession struct { // The session's name as specified in the config file. Name string // The session L2TP configuration. Config *l2tp.SessionConfig } // ConfigParser allows for parsing of custom config file fields which // are not directly implemented by package config. // // This is useful to allow an application to embed custom configuration // into the configuration file. type ConfigParser interface { // ParseParameter is called for any unrecognised key/value pair not // within either a tunnel or session block. ParseParameter(key string, value interface{}) error // ParseTunnelParameter is called for an unrecognised key/value pair // within a tunnel block. ParseTunnelParameter(tunnel *NamedTunnel, key string, value interface{}) error // ParseSessionParameter is called for an unrecognised key/value pair // within a session block. ParseSessionParameter(tunnel *NamedTunnel, session *NamedSession, key string, value interface{}) error } type nilCustomParser struct { } func (np *nilCustomParser) ParseParameter(key string, value interface{}) error { return fmt.Errorf("unrecognised parameter %v", key) } func (np *nilCustomParser) ParseTunnelParameter(tunnel *NamedTunnel, key string, value interface{}) error { return fmt.Errorf("unrecognised parameter %v", key) } func (np *nilCustomParser) ParseSessionParameter(tunnel *NamedTunnel, session *NamedSession, key string, value interface{}) error { return fmt.Errorf("unrecognised parameter %v", key) } func toBool(v interface{}) (bool, error) { if b, ok := v.(bool); ok { return b, nil } return false, fmt.Errorf("supplied value could not be parsed as a bool") } // go-toml's ToMap function represents numbers as either uint64 or int64. // So when we are converting numbers, we need to figure out which one it // has picked and range check to ensure that the number from the config // fits within the range of the destination type. func toByte(v interface{}) (byte, error) { if b, ok := v.(int64); ok { if b < 0x0 || b > 0xff { return 0, fmt.Errorf("value %x out of range", b) } return byte(b), nil } else if b, ok := v.(uint64); ok { if b > 0xff { return 0, fmt.Errorf("value %x out of range", b) } return byte(b), nil } return 0, fmt.Errorf("unexpected %T value %v", v, v) } func toUint16(v interface{}) (uint16, error) { if b, ok := v.(int64); ok { if b < 0x0 || b > 0xffff { return 0, fmt.Errorf("value %x out of range", b) } return uint16(b), nil } else if b, ok := v.(uint64); ok { if b > 0xffff { return 0, fmt.Errorf("value %x out of range", b) } return uint16(b), nil } return 0, fmt.Errorf("unexpected %T value %v", v, v) } func toUint32(v interface{}) (uint32, error) { if b, ok := v.(int64); ok { if b < 0x0 || b > 0xffffffff { return 0, fmt.Errorf("value %x out of range", b) } return uint32(b), nil } else if b, ok := v.(uint64); ok { if b > 0xffffffff { return 0, fmt.Errorf("value %x out of range", b) } return uint32(b), nil } return 0, fmt.Errorf("unexpected %T value %v", v, v) } func toString(v interface{}) (string, error) { if s, ok := v.(string); ok { return s, nil } return "", fmt.Errorf("supplied value could not be parsed as a string") } func toDurationMs(v interface{}) (time.Duration, error) { u, err := toUint32(v) return time.Duration(u) * time.Millisecond, err } func toVersion(v interface{}) (l2tp.ProtocolVersion, error) { s, err := toString(v) if err == nil { switch s { case "l2tpv2": return l2tp.ProtocolVersion2, nil case "l2tpv3": return l2tp.ProtocolVersion3, nil } return 0, fmt.Errorf("expect 'l2tpv2' or 'l2tpv3'") } return 0, err } func toFramingCaps(v interface{}) (l2tp.FramingCapability, error) { var fc l2tp.FramingCapability // First ensure that the supplied value is actually an array caps, ok := v.([]interface{}) if !ok { return 0, fmt.Errorf("expected array value") } // TOML arrays can be mixed type, so we have to check on a value-by-value // basis that the value in the array can be represented as a string. for _, c := range caps { cs, err := toString(c) if err != nil { return 0, err } switch cs { case "sync": fc |= l2tp.FramingCapSync case "async": fc |= l2tp.FramingCapAsync default: return 0, fmt.Errorf("expect 'sync' or 'async'") } } return fc, nil } func toEncapType(v interface{}) (l2tp.EncapType, error) { s, err := toString(v) if err == nil { switch s { case "udp": return l2tp.EncapTypeUDP, nil case "ip": return l2tp.EncapTypeIP, nil } return 0, fmt.Errorf("expect 'udp' or 'ip'") } return 0, err } func toPseudowireType(v interface{}) (l2tp.PseudowireType, error) { s, err := toString(v) if err == nil { switch s { case "ppp": return l2tp.PseudowireTypePPP, nil case "eth": return l2tp.PseudowireTypeEth, nil case "pppac": return l2tp.PseudowireTypePPPAC, nil } return 0, fmt.Errorf("expect 'ppp', 'eth', or 'pppac'") } return 0, err } func toL2SpecType(v interface{}) (l2tp.L2SpecType, error) { s, err := toString(v) if err == nil { switch s { case "none": return l2tp.L2SpecTypeNone, nil case "default": return l2tp.L2SpecTypeDefault, nil } return 0, fmt.Errorf("expect 'none' or 'default'") } return l2tp.L2SpecTypeNone, err } func toCCID(v interface{}) (l2tp.ControlConnID, error) { u, err := toUint32(v) return l2tp.ControlConnID(u), err } func toBytes(v interface{}) ([]byte, error) { out := []byte{} // First ensure that the supplied value is actually an array numbers, ok := v.([]interface{}) if !ok { return nil, fmt.Errorf("expected array value") } // TOML arrays can be mixed type, so we have to check on a value-by-value // basis that the value in the array can be represented as a byte. for _, number := range numbers { b, err := toByte(number) if err != nil { return nil, err } out = append(out, b) } return out, nil } func (cfg *Config) newSessionConfig(tunnel *NamedTunnel, name string, scfg map[string]interface{}) (*NamedSession, error) { ns := &NamedSession{ Name: name, Config: &l2tp.SessionConfig{}, } for k, v := range scfg { var err error switch k { case "sid": ns.Config.SessionID, err = toCCID(v) case "psid": ns.Config.PeerSessionID, err = toCCID(v) case "pseudowire": ns.Config.Pseudowire, err = toPseudowireType(v) case "seqnum": ns.Config.SeqNum, err = toBool(v) case "reorder_timeout": ns.Config.ReorderTimeout, err = toDurationMs(v) case "cookie": ns.Config.Cookie, err = toBytes(v) case "peer_cookie": ns.Config.PeerCookie, err = toBytes(v) case "interface_name": ns.Config.InterfaceName, err = toString(v) case "l2spec_type": ns.Config.L2SpecType, err = toL2SpecType(v) case "pppoe_session_id": ns.Config.PPPoESessionId, err = toUint16(v) case "pppoe_peer_mac": mac, err := toBytes(v) if err == nil { if len(mac) == 6 { ns.Config.PPPoEPeerMac = [6]byte{mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]} } else { err = fmt.Errorf("MAC address must be 6 bytes long") } } default: err = cfg.customParser.ParseSessionParameter(tunnel, ns, k, v) } if err != nil { return nil, fmt.Errorf("failed to process %v: %v", k, err) } } return ns, nil } func (cfg *Config) loadSessions(tunnel *NamedTunnel, v interface{}) ([]NamedSession, error) { var out []NamedSession sessions, ok := v.(map[string]interface{}) if !ok { return nil, fmt.Errorf("session instances must be named, e.g. '[tunnel.mytunnel.session.mysession]'") } for name, got := range sessions { smap, ok := got.(map[string]interface{}) if !ok { return nil, fmt.Errorf("session instances must be named, e.g. '[tunnel.mytunnel.session.mysession]'") } scfg, err := cfg.newSessionConfig(tunnel, name, smap) if err != nil { return nil, fmt.Errorf("session %v: %v", name, err) } out = append(out, *scfg) } return out, nil } func (cfg *Config) newTunnelConfig(name string, tcfg map[string]interface{}) (*NamedTunnel, error) { nt := &NamedTunnel{ Name: name, Config: &l2tp.TunnelConfig{ FramingCaps: l2tp.FramingCapSync | l2tp.FramingCapAsync, }, } for k, v := range tcfg { var err error switch k { case "local": nt.Config.Local, err = toString(v) case "peer": nt.Config.Peer, err = toString(v) case "encap": nt.Config.Encap, err = toEncapType(v) case "version": nt.Config.Version, err = toVersion(v) case "tid": nt.Config.TunnelID, err = toCCID(v) case "ptid": nt.Config.PeerTunnelID, err = toCCID(v) case "window_size": nt.Config.WindowSize, err = toUint16(v) case "hello_timeout": nt.Config.HelloTimeout, err = toDurationMs(v) case "retry_timeout": nt.Config.RetryTimeout, err = toDurationMs(v) case "max_retries": if u, err := toUint16(v); err == nil { nt.Config.MaxRetries = uint(u) } case "host_name": nt.Config.HostName, err = toString(v) case "framing_caps": nt.Config.FramingCaps, err = toFramingCaps(v) case "session": nt.Sessions, err = cfg.loadSessions(nt, v) default: err = cfg.customParser.ParseTunnelParameter(nt, k, v) } if err != nil { return nil, fmt.Errorf("failed to process %v: %v", k, err) } } return nt, nil } func (cfg *Config) loadTunnels(tunnels map[string]interface{}) ([]NamedTunnel, error) { var out []NamedTunnel for name, got := range tunnels { tmap, ok := got.(map[string]interface{}) if !ok { return nil, fmt.Errorf("tunnel instances must be named, e.g. '[tunnel.mytunnel]'") } tcfg, err := cfg.newTunnelConfig(name, tmap) if err != nil { return nil, fmt.Errorf("tunnel %v: %v", name, err) } out = append(out, *tcfg) } return out, nil } func newConfig(tree *toml.Tree, customParser ConfigParser) (*Config, error) { cfg := &Config{ Map: tree.ToMap(), customParser: customParser, } // Walk the parameters, directly parse tunnel tables, defer everything else the custom parser for k, v := range cfg.Map { if k == "tunnel" { tunnels, ok := v.(map[string]interface{}) if !ok || len(tunnels) == 0 { return nil, fmt.Errorf("tunnel instances must be named, e.g. '[tunnel.mytunnel]'") } parsedTunnels, err := cfg.loadTunnels(tunnels) if err != nil { return nil, fmt.Errorf("failed to parse tunnels: %v", err) } cfg.Tunnels = append(cfg.Tunnels, parsedTunnels...) } else { err := cfg.customParser.ParseParameter(k, v) if err != nil { return nil, err } } } return cfg, nil } func newConfigFromFile(path string, customParser ConfigParser) (*Config, error) { tree, err := toml.LoadFile(path) if err != nil { return nil, fmt.Errorf("failed to load config file: %v", err) } return newConfig(tree, customParser) } func newConfigFromString(content string, customParser ConfigParser) (*Config, error) { tree, err := toml.Load(content) if err != nil { return nil, fmt.Errorf("failed to load config string: %v", err) } return newConfig(tree, customParser) } // LoadFile loads configuration from the specified file. func LoadFile(path string) (*Config, error) { return newConfigFromFile(path, &nilCustomParser{}) } // LoadString loads configuration from the specified string. func LoadString(content string) (*Config, error) { return newConfigFromString(content, &nilCustomParser{}) } // LoadFileWithCustomParser loads configuration from the specified file, // calling the ConfigParser interface for unrecognised key/value pairs. func LoadFileWithCustomParser(path string, customParser ConfigParser) (*Config, error) { return newConfigFromFile(path, customParser) } // LoadStringWithCustomParser loads configuration from the specified file, // calling the ConfigParser interface for unrecognised key/value pairs. func LoadStringWithCustomParser(content string, customParser ConfigParser) (*Config, error) { return newConfigFromString(content, customParser) } golang-github-katalix-go-l2tp-0.1.7/config/config_test.go000066400000000000000000000157441456471064000233050ustar00rootroot00000000000000package config import ( "fmt" "reflect" "strings" "testing" "time" "github.com/katalix/go-l2tp/l2tp" ) func TestGetTunnels(t *testing.T) { cases := []struct { in string want []NamedTunnel }{ { in: `[tunnel.t1] encap = "ip" version = "l2tpv3" peer = "82.9.90.101:1701" tid = 412 ptid = 8192 framing_caps = ["sync"] host_name = "blackhole.local" [tunnel.t2] encap = "udp" version = "l2tpv2" peer = "[2001:0000:1234:0000:0000:C1C0:ABCD:0876]:6543" hello_timeout = 250 window_size = 10 retry_timeout = 250 max_retries = 2 framing_caps = ["sync","async"] `, want: []NamedTunnel{ { Name: "t1", Config: &l2tp.TunnelConfig{ Encap: l2tp.EncapTypeIP, Version: l2tp.ProtocolVersion3, Peer: "82.9.90.101:1701", TunnelID: 412, PeerTunnelID: 8192, FramingCaps: l2tp.FramingCapSync, HostName: "blackhole.local", }, }, { Name: "t2", Config: &l2tp.TunnelConfig{ Encap: l2tp.EncapTypeUDP, Version: l2tp.ProtocolVersion2, Peer: "[2001:0000:1234:0000:0000:C1C0:ABCD:0876]:6543", HelloTimeout: 250 * time.Millisecond, WindowSize: 10, RetryTimeout: 250 * time.Millisecond, MaxRetries: 2, FramingCaps: l2tp.FramingCapSync | l2tp.FramingCapAsync, }, }, }, }, { in: `[tunnel.t1] encap = "ip" version = "l2tpv3" peer = "127.0.0.1:5001" [tunnel.t1.session.s1] pseudowire = "eth" cookie = [ 0x34, 0x04, 0xa9, 0xbe ] peer_cookie = [ 0x80, 0x12, 0xff, 0x5b ] seqnum = true reorder_timeout = 1500 l2spec_type = "none" [tunnel.t1.session.s2] pseudowire = "ppp" sid = 90210 psid = 1237812 interface_name = "becky" l2spec_type = "default" [tunnel.t1.session.s3] pseudowire = "pppac" pppoe_session_id = 5612 pppoe_peer_mac = [ 0xca, 0x6b, 0x7e, 0x93, 0xc4, 0xc3 ] `, want: []NamedTunnel{ { Name: "t1", Config: &l2tp.TunnelConfig{ Encap: l2tp.EncapTypeIP, Version: l2tp.ProtocolVersion3, Peer: "127.0.0.1:5001", FramingCaps: l2tp.FramingCapSync | l2tp.FramingCapAsync, }, Sessions: []NamedSession{ { Name: "s1", Config: &l2tp.SessionConfig{ Pseudowire: l2tp.PseudowireTypeEth, Cookie: []byte{0x34, 0x04, 0xa9, 0xbe}, PeerCookie: []byte{0x80, 0x12, 0xff, 0x5b}, SeqNum: true, ReorderTimeout: time.Millisecond * 1500, L2SpecType: l2tp.L2SpecTypeNone, }, }, { Name: "s2", Config: &l2tp.SessionConfig{ Pseudowire: l2tp.PseudowireTypePPP, SessionID: 90210, PeerSessionID: 1237812, InterfaceName: "becky", L2SpecType: l2tp.L2SpecTypeDefault, }, }, { Name: "s3", Config: &l2tp.SessionConfig{ Pseudowire: l2tp.PseudowireTypePPPAC, PPPoESessionId: 5612, PPPoEPeerMac: [6]byte{0xca, 0x6b, 0x7e, 0x93, 0xc4, 0xc3}, }, }, }, }, }, }, } for _, c := range cases { cfg, err := LoadString(c.in) if err != nil { t.Fatalf("LoadString(%v): %v", c.in, err) } for _, want_t := range c.want { got_t, err := cfg.findTunnelByName(want_t.Name) if err != nil { t.Fatalf("missing tunnel: %v", err) } for _, want_s := range want_t.Sessions { got_s, err := got_t.findSessionByName(want_s.Name) if err != nil { t.Fatalf("missing session: %v", err) } if !reflect.DeepEqual(got_s, &want_s) { t.Fatalf("got %v, want %v", got_s, want_s) } } } } } func (c *Config) findTunnelByName(name string) (*NamedTunnel, error) { for _, t := range c.Tunnels { if t.Name == name { return &t, nil } } return nil, fmt.Errorf("no tunnel of name %s", name) } func (t *NamedTunnel) findSessionByName(name string) (*NamedSession, error) { for _, s := range t.Sessions { if s.Name == name { return &s, nil } } return nil, fmt.Errorf("no session of name %s", name) } func TestBadConfig(t *testing.T) { cases := []struct { name string in string estr string }{ { name: "Bad type (int not string)", in: `[tunnel.t1] encap = 42`, estr: "could not be parsed as a string", }, { name: "Bad type (float not string)", in: `[tunnel.t1] encap = 42.21`, estr: "could not be parsed as a string", }, { name: "Bad type (array not string)", in: `[tunnel.t1] version = [0x12, 0x34]`, estr: "could not be parsed as a string", }, { name: "Bad type (bool not string)", in: `[tunnel.t1] encap = false`, estr: "could not be parsed as a string", }, { name: "Bad value (unrecognised encap)", in: `[tunnel.t1] encap = "sausage"`, estr: "expect 'udp' or 'ip'", }, { name: "Bad value (unrecognised version)", in: `[tunnel.t1] version = "2001"`, estr: "expect 'l2tpv2' or 'l2tpv3'", }, { name: "Bad value (unrecognised pseudowire)", in: `[tunnel.t1] [tunnel.t1.session.s1] pseudowire = "monkey"`, estr: "expect 'ppp', 'eth', or 'pppac'", }, { name: "Bad value (unrecognised L2SpecType)", in: `[tunnel.t1] [tunnel.t1.session.s1] l2spec_type = "whizzoo"`, estr: "expect 'none' or 'default'", }, { name: "Bad value (unrecognised FramingCap)", in: `[tunnel.t1] framing_caps = [ "bizzle" ]`, estr: "expect 'sync' or 'async'", }, { name: "Bad value (range exceeded)", in: `[tunnel.t1] tid = 4294967297`, estr: "out of range", }, { name: "Bad value (range exceeded)", in: `[tunnel.t1] [tunnel.t1.session.s1] cookie = [ 0x1e, 0xf0, 0x1fe, 0x24 ]`, estr: "out of range", }, { name: "Malformed (no tunnel name)", in: `[tunnel]`, estr: "tunnel instances must be named", }, { name: "Malformed (no tunnel name 2)", in: `tunnel = "t1"`, estr: "tunnel instances must be named", }, { name: "Malformed (no session name)", in: `[tunnel.t1] version = "l2tpv3" [tunnel.t1.session] pseudowire = "udp"`, estr: "session instances must be named", }, { name: "Malformed (no session name)", in: `[tunnel.t1] version = "l2tpv3" session = 42`, estr: "session instances must be named", }, { name: "Malformed (bad tunnel parameter)", in: `[tunnel.t1] monkey = "banana"`, estr: "unrecognised parameter", }, { name: "Malformed (bad session parameter)", in: `[tunnel.t1] [tunnel.t1.session.s1] whizz = 42`, estr: "unrecognised parameter", }, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { _, err := LoadString(tt.in) if err == nil { t.Fatalf("LoadString(%v) succeeded when we expected an error", tt.in) } if !strings.Contains(err.Error(), tt.estr) { t.Fatalf("LoadString(%v): error %q doesn't contain expected substring %q", tt.in, err, tt.estr) } }) } } golang-github-katalix-go-l2tp-0.1.7/doc/000077500000000000000000000000001456471064000177375ustar00rootroot00000000000000golang-github-katalix-go-l2tp-0.1.7/doc/GNUmakefile000066400000000000000000000006421456471064000220130ustar00rootroot00000000000000VERSION := v0.1.7 DATE := $(shell date +"%B %Y") MANPAGES := kl2tpd.8 MANPAGES += kl2tpd.toml.5 MANPAGES += ql2tpd.8 MANPAGES += ql2tpd.toml.5 MANPAGES += kpppoed.8 .PHONY: default clean default: $(MANPAGES) clean: rm -f $(MANPAGES) %.tmp: %.md sed 's/_DATE_/$(DATE)/g;s/_VERSION_/$(VERSION)/g' $< > $@ %: %.tmp pandoc --standalone --metadata hyphenate=false --from markdown --to man $< | iconv -f utf-8 > $@ golang-github-katalix-go-l2tp-0.1.7/doc/kl2tpd.8000066400000000000000000000017471456471064000212410ustar00rootroot00000000000000.\" Automatically generated by Pandoc 3.1.8 .\" .TH "kl2tpd" "8" "February 2024" "go-l2tp v0.1.7" "go-l2tp" .SH NAME kl2tpd - a minimal L2TPv2 client daemon .SH SYNOPSIS \f[B]kl2tpd\f[R] [ arguments ] .SH DESCRIPTION \f[B]kl2tpd\f[R] is a client/LAC-mode daemon for creating L2TPv2 (RFC 2661) tunnels and sessions. .PP \f[B]kl2tpd\f[R] requires root permissions to run, and is driven by a configuration file which details the tunnel and session instances to create. .PP For further details of the configuration format, please refer to \f[B]kl2tpd.toml\f[R](5). .PP By default, \f[B]kl2tpd\f[R] spawns the standard Linux \f[B]pppd\f[R] for PPP protocol support. .SH OPTIONS .TP -config string specify configuration file path (default \[lq]/etc/kl2tpd/kl2tpd.toml\[rq]) .TP -null toggle null data plane (establish L2TP tunnel and session but do not spawn \f[B]pppd\f[R]) .TP -verbose toggle verbose log output .SH SEE ALSO \f[B]kl2tpd.toml\f[R](5), \f[B]pppd\f[R](8) .SH AUTHORS Katalix Systems, Ltd. golang-github-katalix-go-l2tp-0.1.7/doc/kl2tpd.8.md000066400000000000000000000015371456471064000216350ustar00rootroot00000000000000% kl2tpd(8) go-l2tp _VERSION_ | go-l2tp % Katalix Systems, Ltd % _DATE_ # NAME kl2tpd - a minimal L2TPv2 client daemon # SYNOPSIS **kl2tpd** [ arguments ] # DESCRIPTION **kl2tpd** is a client/LAC-mode daemon for creating L2TPv2 (RFC 2661) tunnels and sessions. **kl2tpd** requires root permissions to run, and is driven by a configuration file which details the tunnel and session instances to create. For further details of the configuration format, please refer to **kl2tpd.toml**(5). By default, **kl2tpd** spawns the standard Linux **pppd** for PPP protocol support. # OPTIONS -config string : specify configuration file path (default "/etc/kl2tpd/kl2tpd.toml") -null : toggle null data plane (establish L2TP tunnel and session but do not spawn **pppd**) -verbose : toggle verbose log output # SEE ALSO **kl2tpd.toml**(5), **pppd**(8) golang-github-katalix-go-l2tp-0.1.7/doc/kl2tpd.toml.5000066400000000000000000000151651456471064000222070ustar00rootroot00000000000000.\" Automatically generated by Pandoc 3.1.8 .\" .TH "kl2tpd.toml" "5" "February 2024" "go-l2tp v0.1.7" "go-l2tp" .SH NAME \f[B]kl2tpd.toml\f[R] - configuration file for \f[B]kl2tpd\f[R] .SH DESCRIPTION The \f[B]kl2tpd.toml\f[R] file configures \f[B]kl2tpd\f[R]. It calls out the L2TP tunnels and sessions to establish. .PP \f[B]kl2tpd.toml\f[R] is written in the TOML markup language (https://toml.io/en/). .PP Tunnel and session instances are called out in the configuration file using named TOML tables. .PP Each tunnel or session instance table contains configuration parameters for that instance as key:value pairs. .PP Each tunnel and session has a minimal set of configuration which \f[B]\f[BI]must\f[B]\f[R] be specified. .PP In addition, each tunnel or session entry may call out various optional key:value pairs which will control \f[B]kl2tpd\f[R]\[cq]s runtime behaviour. .PP These options are generally not required, and \f[B]kl2tpd\f[R] will use sensible defaults for them if they are not included in the configuration. .SS TUNNEL CONFIGURATION Tunnels are described using named entries in the `tunnel' table. .PP Each tunnel entry describes a single tunnel instance, and must call out at least: .IP \[bu] 2 the peer\[cq]s IP address, .IP \[bu] 2 the tunnel L2TP version (currently only L2TPv2 is supported), .IP \[bu] 2 the tunnels encapsulation protocol (currently only UDP is supported). .PP Here is the full list of tunnel configuration options: .IP .EX # This is a tunnel instance named \[dq]t1\[dq] [tunnel.t1] # peer specifies the address of the peer that the tunnel should # connect its socket to peer = \[dq]127.0.0.1:5001\[dq] # version specifies the version of the L2TP specification the # tunnel should use. # Currently supported values are \[dq]l2tpv2\[dq]. version = \[dq]l2tpv2\[dq] # encap specifies the encapsulation to be used for the tunnel. # L2TPv2 tunnels are UDP only. encap = \[dq]udp\[dq] # local specifies the local address that the tunnel should # bind its socket to local = \[dq]127.0.0.1:5000\[dq] # tid specifies the local tunnel ID of the tunnel. # Tunnel IDs must be unique for the host. # L2TPv2 tunnel IDs are 16 bit, and may be in the range 1 - 65535. # L2TPv3 tunnel IDs are 32 bit, and may be in the range 1 - 4294967295. tid = 62719 # ptid specifies the peer\[aq]s tunnel ID for the tunnel. # The peer\[aq]s tunnel ID must be unique for the peer, and are unrelated # to the local tunnel ID. # The rules for tunnel ID range apply to the peer tunnel ID too. ptid = 72819 # window_size specifies the initial window size to use for the L2TP # reliable transport algorithm which is used for control protocol # messages. The window size dictates how many control messages the # tunnel may have \[dq]in flight\[dq] (i.e. pending an ACK from the peer) at # any one time. Tuning the window size can allow high-volume L2TP servers # to improve performance. Generally it won\[aq]t be necessary to change # this from the default value of 4. window_size = 10 # control messages # hello_timeout if set enables L2TP keep-alive (HELLO) messages. # A hello message is sent N milliseconds after the last control # message was sent or received. It allows for early detection of # tunnel failure on quiet connections. # By default no keep-alive messages are sent. hello_timeout = 7500 # milliseconds # retry_timeout if set tweaks the starting retry timeout for the # reliable transport algorithm used for L2TP control messages. # The algorithm uses an exponential backoff when retrying messages. # By default a starting retry timeout of 1000ms is used. retry_timeout = 1500 # milliseconds # max_retries sets how many times a given control message may be # retried before the transport considers the message transmission to # have failed. # It may be useful to tune this value on unreliable network connections # to avoid suprious tunnel failure, or conversely to allow for quicker # tunnel failure detection on reliable links. # The default is 3 retries. max_retries 5 # host_name sets the host name the tunnel will advertise in the # Host Name AVP per RFC2661. # If unset the host\[aq]s name will be queried and the returned value used. host_name \[dq]basilbrush.local\[dq] # framing_caps sets the framing capabilities the tunnel will advertise # in the Framing Capabilities AVP per RFC2661. # The default is to advertise both sync and async framing. framing_caps = [\[dq]sync\[dq],\[dq]async\[dq]] .EE .SS SESSION CONFIGURATION Sessions are described using named entries in the `session' table inside the parent tunnel table. .PP Each session entry describes a single session instance within the parent tunnel, and must call out at least: .IP \[bu] 2 the pseudowire type to be used (for L2TPv2 this must be ppp or pppac). .PP Here is the full list of session configuration options: .IP .EX # This is a session instance called \[dq]s1\[dq] within parent tunnel \[dq]t1\[dq]. # Session instances are always created inside a parent tunnel. [tunnel.t1.session.s1] # pseudowire specifies the type of layer 2 frames carried by the session. # Currently supported values are \[dq]ppp\[dq], \[dq]eth\[dq], and \[dq]pppac\[dq]. # L2TPv2 tunnels support PPP and PPPAC pseudowires only. pseudowire = \[dq]eth\[dq] # pppd_args specifes a file to be read for pppd arguments. These should # be either whitespace or newline delimited, and should call out pppd command # line arguments as described in the pppd manpage. pppd_args = \[dq]/etc/kl2tpd/t1s1_pppd_args.txt\[dq] # sid specifies the local session ID of the session. # Session IDs must be unique to the tunnel for L2TPv2, or unique to # the peer for L2TPv3. # L2TPv2 session IDs are 16 bit, and may be in the range 1 - 65535. # L2TPv3 session IDs are 32 bit, and may be in the range 1 - 4294967295. sid = 12389 # psid specifies the peer\[aq]s session ID for the session. # The peer\[aq]s session ID is unrelated to the local session ID. # The rules for the session ID range apply to the peer session ID too. psid = 1234 # seqnum, if set, enables the transmission of sequence numbers with # L2TP data messages. Use of sequence numbers enables the data plane # to reorder data packets to ensure they are delivered in sequence. # By default sequence numbers are not used. seqnum = false # pppoe_session_id specifies the assigned PPPoE session ID for the session. # Per RFC2516, the PPPoE session ID is in the range 1 - 65535 # This parameter only applies to pppac pseudowires. pppoe_session_id = 1234 # pppoe_peer_mac specifies the MAC address of the PPPoE peer for the session. # This parameter only applies to pppac pseudowires. pppoe_peer_mac = [ 0x02, 0x42, 0x94, 0xd1, 0x4e, 0x9a ] .EE .SH SEE ALSO \f[B]kl2tpd\f[R](1), \f[B]pppd\f[R](8) .SH AUTHORS Katalix Systems, Ltd. golang-github-katalix-go-l2tp-0.1.7/doc/kl2tpd.toml.5.md000066400000000000000000000145241456471064000226040ustar00rootroot00000000000000% kl2tpd.toml(5) go-l2tp _VERSION_ | go-l2tp % Katalix Systems, Ltd % _DATE_ # NAME **kl2tpd.toml** - configuration file for **kl2tpd** # DESCRIPTION The **kl2tpd.toml** file configures **kl2tpd**. It calls out the L2TP tunnels and sessions to establish. **kl2tpd.toml** is written in the TOML markup language (https://toml.io/en/). Tunnel and session instances are called out in the configuration file using named TOML tables. Each tunnel or session instance table contains configuration parameters for that instance as key:value pairs. Each tunnel and session has a minimal set of configuration which ***must*** be specified. In addition, each tunnel or session entry may call out various optional key:value pairs which will control **kl2tpd**'s runtime behaviour. These options are generally not required, and **kl2tpd** will use sensible defaults for them if they are not included in the configuration. ## TUNNEL CONFIGURATION Tunnels are described using named entries in the 'tunnel' table. Each tunnel entry describes a single tunnel instance, and must call out at least: * the peer's IP address, * the tunnel L2TP version (currently only L2TPv2 is supported), * the tunnels encapsulation protocol (currently only UDP is supported). Here is the full list of tunnel configuration options: # This is a tunnel instance named "t1" [tunnel.t1] # peer specifies the address of the peer that the tunnel should # connect its socket to peer = "127.0.0.1:5001" # version specifies the version of the L2TP specification the # tunnel should use. # Currently supported values are "l2tpv2". version = "l2tpv2" # encap specifies the encapsulation to be used for the tunnel. # L2TPv2 tunnels are UDP only. encap = "udp" # local specifies the local address that the tunnel should # bind its socket to local = "127.0.0.1:5000" # tid specifies the local tunnel ID of the tunnel. # Tunnel IDs must be unique for the host. # L2TPv2 tunnel IDs are 16 bit, and may be in the range 1 - 65535. # L2TPv3 tunnel IDs are 32 bit, and may be in the range 1 - 4294967295. tid = 62719 # ptid specifies the peer's tunnel ID for the tunnel. # The peer's tunnel ID must be unique for the peer, and are unrelated # to the local tunnel ID. # The rules for tunnel ID range apply to the peer tunnel ID too. ptid = 72819 # window_size specifies the initial window size to use for the L2TP # reliable transport algorithm which is used for control protocol # messages. The window size dictates how many control messages the # tunnel may have "in flight" (i.e. pending an ACK from the peer) at # any one time. Tuning the window size can allow high-volume L2TP servers # to improve performance. Generally it won't be necessary to change # this from the default value of 4. window_size = 10 # control messages # hello_timeout if set enables L2TP keep-alive (HELLO) messages. # A hello message is sent N milliseconds after the last control # message was sent or received. It allows for early detection of # tunnel failure on quiet connections. # By default no keep-alive messages are sent. hello_timeout = 7500 # milliseconds # retry_timeout if set tweaks the starting retry timeout for the # reliable transport algorithm used for L2TP control messages. # The algorithm uses an exponential backoff when retrying messages. # By default a starting retry timeout of 1000ms is used. retry_timeout = 1500 # milliseconds # max_retries sets how many times a given control message may be # retried before the transport considers the message transmission to # have failed. # It may be useful to tune this value on unreliable network connections # to avoid suprious tunnel failure, or conversely to allow for quicker # tunnel failure detection on reliable links. # The default is 3 retries. max_retries 5 # host_name sets the host name the tunnel will advertise in the # Host Name AVP per RFC2661. # If unset the host's name will be queried and the returned value used. host_name "basilbrush.local" # framing_caps sets the framing capabilities the tunnel will advertise # in the Framing Capabilities AVP per RFC2661. # The default is to advertise both sync and async framing. framing_caps = ["sync","async"] ## SESSION CONFIGURATION Sessions are described using named entries in the 'session' table inside the parent tunnel table. Each session entry describes a single session instance within the parent tunnel, and must call out at least: * the pseudowire type to be used (for L2TPv2 this must be ppp or pppac). Here is the full list of session configuration options: # This is a session instance called "s1" within parent tunnel "t1". # Session instances are always created inside a parent tunnel. [tunnel.t1.session.s1] # pseudowire specifies the type of layer 2 frames carried by the session. # Currently supported values are "ppp", "eth", and "pppac". # L2TPv2 tunnels support PPP and PPPAC pseudowires only. pseudowire = "eth" # pppd_args specifes a file to be read for pppd arguments. These should # be either whitespace or newline delimited, and should call out pppd command # line arguments as described in the pppd manpage. pppd_args = "/etc/kl2tpd/t1s1_pppd_args.txt" # sid specifies the local session ID of the session. # Session IDs must be unique to the tunnel for L2TPv2, or unique to # the peer for L2TPv3. # L2TPv2 session IDs are 16 bit, and may be in the range 1 - 65535. # L2TPv3 session IDs are 32 bit, and may be in the range 1 - 4294967295. sid = 12389 # psid specifies the peer's session ID for the session. # The peer's session ID is unrelated to the local session ID. # The rules for the session ID range apply to the peer session ID too. psid = 1234 # seqnum, if set, enables the transmission of sequence numbers with # L2TP data messages. Use of sequence numbers enables the data plane # to reorder data packets to ensure they are delivered in sequence. # By default sequence numbers are not used. seqnum = false # pppoe_session_id specifies the assigned PPPoE session ID for the session. # Per RFC2516, the PPPoE session ID is in the range 1 - 65535 # This parameter only applies to pppac pseudowires. pppoe_session_id = 1234 # pppoe_peer_mac specifies the MAC address of the PPPoE peer for the session. # This parameter only applies to pppac pseudowires. pppoe_peer_mac = [ 0x02, 0x42, 0x94, 0xd1, 0x4e, 0x9a ] # SEE ALSO **kl2tpd**(1), **pppd**(8) golang-github-katalix-go-l2tp-0.1.7/doc/kpppoed.8000066400000000000000000000033031456471064000214710ustar00rootroot00000000000000.\" Automatically generated by Pandoc 3.1.8 .\" .TH "kpppoed" "8" "February 2024" "go-l2tp v0.1.7" "go-l2tp" .SH NAME kpppoed - a PPPoE daemon for creating L2TPv2 Access Concentrator sessions in response to PPPoE requests .SH SYNOPSIS \f[B]kpppoed\f[R] [ arguments ] .SH DESCRIPTION \f[B]kpppoed\f[R] is a PPPoE (RFC 2516) server daemon for creating L2TPv2 Access Concentrator sessions. It spawns \f[B]kl2tpd\f[R] for L2TP protocol support. .PP \f[B]kpppoed\f[R] and is driven by a configuration file which describes the PPPoE service to offer. .SH OPTIONS .TP -config string specify configuration file path (default \[lq]/etc/kpppoed/kpppoed.toml\[rq]) .TP -verbose toggle verbose log output .SH CONFIGURATION The \f[B]kpppoed\f[R] file, \f[B]kpppoed.toml\f[R] is written in the TOML markup language (https://toml.io/en/). .PP It uses a small set of key:value pairs to configure the PPPoE server: .IP .EX # ac_name is the name that kpppoed will use in the PPPoE AC Name tag sent # in PADO packets. If not specified it will default to \[dq]kpppoed\[dq]. ac_name = \[dq]MyAccessConcentrator.2000\[dq] # interface_name is the name of the network interface that kpppoed will listen # on for PPPoE discovery packets. It must be specified. interface_name = \[dq]eth0\[dq] # services is a list of service names that kpppoed will advertise in PADO packets # At least one service must be specified. services = [ \[dq]serviceA\[dq], \[dq]serviceB\[dq], \[dq]serviceC\[dq] ] # lns_ipaddr is the IP address and port of the L2TP server to tunnel # pppoe sessions to. The LNS address must be specified. lns_ipaddr = \[dq]3.22.1.9:1701\[dq] .EE .SH SEE ALSO \f[B]kpppoed.toml\f[R](5), \f[B]kl2tpd\f[R](8) .SH AUTHORS Katalix Systems, Ltd. golang-github-katalix-go-l2tp-0.1.7/doc/kpppoed.8.md000066400000000000000000000030151456471064000220700ustar00rootroot00000000000000% kpppoed(8) go-l2tp _VERSION_ | go-l2tp % Katalix Systems, Ltd % _DATE_ # NAME kpppoed - a PPPoE daemon for creating L2TPv2 Access Concentrator sessions in response to PPPoE requests # SYNOPSIS **kpppoed** [ arguments ] # DESCRIPTION **kpppoed** is a PPPoE (RFC 2516) server daemon for creating L2TPv2 Access Concentrator sessions. It spawns **kl2tpd** for L2TP protocol support. **kpppoed** and is driven by a configuration file which describes the PPPoE service to offer. # OPTIONS -config string : specify configuration file path (default "/etc/kpppoed/kpppoed.toml") -verbose : toggle verbose log output # CONFIGURATION The **kpppoed** file, **kpppoed.toml** is written in the TOML markup language (https://toml.io/en/). It uses a small set of key:value pairs to configure the PPPoE server: # ac_name is the name that kpppoed will use in the PPPoE AC Name tag sent # in PADO packets. If not specified it will default to "kpppoed". ac_name = "MyAccessConcentrator.2000" # interface_name is the name of the network interface that kpppoed will listen # on for PPPoE discovery packets. It must be specified. interface_name = "eth0" # services is a list of service names that kpppoed will advertise in PADO packets # At least one service must be specified. services = [ "serviceA", "serviceB", "serviceC" ] # lns_ipaddr is the IP address and port of the L2TP server to tunnel # pppoe sessions to. The LNS address must be specified. lns_ipaddr = "3.22.1.9:1701" # SEE ALSO **kpppoed.toml**(5), **kl2tpd**(8) golang-github-katalix-go-l2tp-0.1.7/doc/ql2tpd.8000066400000000000000000000031771456471064000212460ustar00rootroot00000000000000.\" Automatically generated by Pandoc 3.1.8 .\" .TH "ql2tpd" "8" "February 2024" "go-l2tp v0.1.7" "go-l2tp" .SH NAME ql2tpd - a daemon for creating static (or quiescent) L2TPv3 tunnels and sessions .SH SYNOPSIS \f[B]ql2tpd\f[R] [ arguments ] .SH DESCRIPTION \f[B]ql2tpd\f[R] is a daemon for creating static L2TPv3 (RFC 3931) tunnels and sessions. .PP Static (or quiescent) tunnels and sessions implement \f[B]\f[BI]only\f[B]\f[R] the data plane transport: the L2TP control protocol is not used. This can be useful for setting up static L2TPv3 links where host configuration is known in advance. .PP When configured to bring up static tunnel and session instances, \f[B]ql2tpd\f[R] represents a more convenient way to bring up static sessions than \f[B]ip l2tp\f[R] commands. .PP Additionally, \f[B]ql2tpd\f[R] supports the use of a periodic keep-alive packet for tunnels it is managing (controlled by the configuration file \f[B]\f[BI]hello_timeout\f[B]\f[R] parameter). .PP This allows for the detection of tunnel failure, which will then tear down the sessions running in that tunnel. This mode of operation must only be enabled if the peer is also running \f[B]ql2tpd\f[R]. .PP \f[B]ql2tpd\f[R] requires root permissions to run, and is driven by a configuration file which details the tunnel and session instances to create. .PP For further details of the configuration format, please refer to \f[B]ql2tpd.toml\f[R](5). .SH OPTIONS .TP -config string specify configuration file path (default \[lq]/etc/ql2tpd/ql2tpd.toml\[rq]) .TP -verbose toggle verbose log output .SH SEE ALSO \f[B]ql2tpd.toml\f[R](5), \f[B]ip-l2tp\f[R](8) .SH AUTHORS Katalix Systems, Ltd. golang-github-katalix-go-l2tp-0.1.7/doc/ql2tpd.8.md000066400000000000000000000027121456471064000216370ustar00rootroot00000000000000% ql2tpd(8) go-l2tp _VERSION_ | go-l2tp % Katalix Systems, Ltd % _DATE_ # NAME ql2tpd - a daemon for creating static (or quiescent) L2TPv3 tunnels and sessions # SYNOPSIS **ql2tpd** [ arguments ] # DESCRIPTION **ql2tpd** is a daemon for creating static L2TPv3 (RFC 3931) tunnels and sessions. Static (or quiescent) tunnels and sessions implement ***only*** the data plane transport: the L2TP control protocol is not used. This can be useful for setting up static L2TPv3 links where host configuration is known in advance. When configured to bring up static tunnel and session instances, **ql2tpd** represents a more convenient way to bring up static sessions than **ip l2tp** commands. Additionally, **ql2tpd** supports the use of a periodic keep-alive packet for tunnels it is managing (controlled by the configuration file ***hello_timeout*** parameter). This allows for the detection of tunnel failure, which will then tear down the sessions running in that tunnel. This mode of operation must only be enabled if the peer is also running **ql2tpd**. **ql2tpd** requires root permissions to run, and is driven by a configuration file which details the tunnel and session instances to create. For further details of the configuration format, please refer to **ql2tpd.toml**(5). # OPTIONS -config string : specify configuration file path (default "/etc/ql2tpd/ql2tpd.toml") -verbose : toggle verbose log output # SEE ALSO **ql2tpd.toml**(5), **ip-l2tp**(8) golang-github-katalix-go-l2tp-0.1.7/doc/ql2tpd.toml.5000066400000000000000000000135211456471064000222070ustar00rootroot00000000000000.\" Automatically generated by Pandoc 3.1.8 .\" .TH "ql2tpd.toml" "5" "February 2024" "go-l2tp v0.1.7" "go-l2tp" .SH NAME \f[B]ql2tpd.toml\f[R] - configuration file for \f[B]ql2tpd\f[R] .SH DESCRIPTION The \f[B]ql2tpd.toml\f[R] file configures \f[B]ql2tpd\f[R]. It calls out the L2TP tunnels and sessions to establish. .PP \f[B]ql2tpd.toml\f[R] is written in the TOML markup langange (https://toml.io/en/). .PP Tunnel and session instances are called out in the configuration file using named TOML tables. .PP Each tunnel or session instance table contains configuration parameters for that instance as key:value pairs. .PP Each tunnel and session has a minimal set of configuration which \f[B]\f[BI]must\f[B]\f[R] be specified. .PP In addition, each tunnel or session entry may call out various optional key:value pairs which will control \f[B]ql2tpd\f[R]\[cq]s runtime behaviour. .PP These options are generally not required, and \f[B]ql2tpd\f[R] will use sensible defaults for them if they are not included in the configuration. .SS TUNNEL CONFIGURATION Tunnels are described using named entries in the `tunnel' table. .PP Each tunnel entry describes a single tunnel instance, and must call out at least: .IP \[bu] 2 the tunnel L2TP version (only L2TPv3 is supported), .IP \[bu] 2 the tunnels encapsulation protocol, .IP \[bu] 2 the local IP address, .IP \[bu] 2 the local tunnel ID, .IP \[bu] 2 the peer\[cq]s IP address, .IP \[bu] 2 the peer\[cq]s tunnel ID. .PP Here is the full list of tunnel configuration options: .IP .EX # This is a tunnel instance named \[dq]t1\[dq] [tunnel.t1] # version specifies the version of the L2TP specification the # tunnel should use. # Only \[dq]l2tpv3\[dq] is supported. version = \[dq]l2tpv3\[dq] # encap specifies the encapsulation to be used for the tunnel. # L2TPv3 tunnels may be UDP or IP. encap = \[dq]udp\[dq] # local specifies the local address that the tunnel should # bind its socket to local = \[dq]127.0.0.1:5000\[dq] # tid specifies the local tunnel ID of the tunnel. # Tunnel IDs must be unique for the host. # L2TPv2 tunnel IDs are 16 bit, and may be in the range 1 - 65535. # L2TPv3 tunnel IDs are 32 bit, and may be in the range 1 - 4294967295. tid = 62719 # peer specifies the address of the peer that the tunnel should # connect its socket to peer = \[dq]127.0.0.1:5001\[dq] # ptid specifies the peer\[aq]s tunnel ID for the tunnel. # The peer\[aq]s tunnel ID must be unique for the peer, and are unrelated # to the local tunnel ID. # The rules for tunnel ID range apply to the peer tunnel ID too. ptid = 72819 # hello_timeout if set enables L2TP keep-alive (HELLO) messages. # A hello message is sent N milliseconds after the last control # message was sent or received. It allows for early detection of # tunnel failure on quiet connections. # By default no keep-alive messages are sent. hello_timeout = 7500 # milliseconds .EE .SS SESSION CONFIGURATION Sessions are described using named entries in the `session' table inside the parent tunnel table. .PP Each session entry describes a single session instance within the parent tunnel, and must call out at least: .IP \[bu] 2 the pseudowire type to be used (this must be Ethernet), .IP \[bu] 2 the local session ID, .IP \[bu] 2 the peer\[cq]s session ID .PP Here is the full list of session configuration options: .IP .EX # This is a session instance called \[dq]s1\[dq] within parent tunnel \[dq]t1\[dq]. # Session instances are always created inside a parent tunnel. [tunnel.t1.session.s1] # pseudowire specifies the type of layer 2 frames carried by the session. # Static sessions support Ethernet pseudowires only. pseudowire = \[dq]eth\[dq] # sid specifies the local session ID of the session. # Session IDs must be unique to the tunnel for L2TPv2, or unique to # the peer for L2TPv3. # L2TPv2 session IDs are 16 bit, and may be in the range 1 - 65535. # L2TPv3 session IDs are 32 bit, and may be in the range 1 - 4294967295. sid = 12389 # psid specifies the peer\[aq]s session ID for the session. # The peer\[aq]s session ID is unrelated to the local session ID. # The rules for the session ID range apply to the peer session ID too. psid = 1234 # seqnum, if set, enables the transmission of sequence numbers with # L2TP data messages. Use of sequence numbers enables the data plane # to reorder data packets to ensure they are delivered in sequence. # By default sequence numbers are not used. seqnum = false # cookie, if set, specifies the local L2TPv3 cookie for the session. # Cookies are a data verification mechanism intended to allow misdirected # data packets to be detected and rejected. # Transmitted data packets will include the local cookie in their header. # Cookies may be either 4 or 8 bytes long, and contain aribrary data. # By default no local cookie is set. cookie = [ 0x12, 0xe9, 0x54, 0x0f, 0xe2, 0x68, 0x72, 0xbc ] # peer_cookie, if set, specifies the L2TPv3 cookie the peer will send in # the header of its data messages. # Messages received without the peer\[aq]s cookie (or with the wrong cookie) # will be rejected. # By default no peer cookie is set. peer_cookie = [ 0x74, 0x2e, 0x28, 0xa8 ] # interface_name, if set, specifies the network interface name to be # used for the session instance. # By default the Linux kernel autogenerates an interface name specific to # the pseudowire type, e.g. \[dq]l2tpeth0\[dq], \[dq]ppp0\[dq]. # Setting the interface name can be useful when you need to be certain # of the interface name a given session will use. # By default the kernel autogenerates an interface name. interface_name = \[dq]l2tpeth42\[dq] # l2spec_type specifies the L2TPv3 Layer 2 specific sublayer field to # be used in data packet headers as per RFC3931 section 3.2.2. # Currently supported values are \[dq]none\[dq] and \[dq]default\[dq]. # By default no Layer 2 specific sublayer is used. l2spec_type = \[dq]default\[dq] .EE .SH SEE ALSO \f[B]ql2tpd\f[R](1) .SH AUTHORS Katalix Systems, Ltd. golang-github-katalix-go-l2tp-0.1.7/doc/ql2tpd.toml.5.md000066400000000000000000000127701456471064000226130ustar00rootroot00000000000000% ql2tpd.toml(5) go-l2tp _VERSION_ | go-l2tp % Katalix Systems, Ltd % _DATE_ # NAME **ql2tpd.toml** - configuration file for **ql2tpd** # DESCRIPTION The **ql2tpd.toml** file configures **ql2tpd**. It calls out the L2TP tunnels and sessions to establish. **ql2tpd.toml** is written in the TOML markup langange (https://toml.io/en/). Tunnel and session instances are called out in the configuration file using named TOML tables. Each tunnel or session instance table contains configuration parameters for that instance as key:value pairs. Each tunnel and session has a minimal set of configuration which ***must*** be specified. In addition, each tunnel or session entry may call out various optional key:value pairs which will control **ql2tpd**'s runtime behaviour. These options are generally not required, and **ql2tpd** will use sensible defaults for them if they are not included in the configuration. ## TUNNEL CONFIGURATION Tunnels are described using named entries in the 'tunnel' table. Each tunnel entry describes a single tunnel instance, and must call out at least: * the tunnel L2TP version (only L2TPv3 is supported), * the tunnels encapsulation protocol, * the local IP address, * the local tunnel ID, * the peer's IP address, * the peer's tunnel ID. Here is the full list of tunnel configuration options: # This is a tunnel instance named "t1" [tunnel.t1] # version specifies the version of the L2TP specification the # tunnel should use. # Only "l2tpv3" is supported. version = "l2tpv3" # encap specifies the encapsulation to be used for the tunnel. # L2TPv3 tunnels may be UDP or IP. encap = "udp" # local specifies the local address that the tunnel should # bind its socket to local = "127.0.0.1:5000" # tid specifies the local tunnel ID of the tunnel. # Tunnel IDs must be unique for the host. # L2TPv2 tunnel IDs are 16 bit, and may be in the range 1 - 65535. # L2TPv3 tunnel IDs are 32 bit, and may be in the range 1 - 4294967295. tid = 62719 # peer specifies the address of the peer that the tunnel should # connect its socket to peer = "127.0.0.1:5001" # ptid specifies the peer's tunnel ID for the tunnel. # The peer's tunnel ID must be unique for the peer, and are unrelated # to the local tunnel ID. # The rules for tunnel ID range apply to the peer tunnel ID too. ptid = 72819 # hello_timeout if set enables L2TP keep-alive (HELLO) messages. # A hello message is sent N milliseconds after the last control # message was sent or received. It allows for early detection of # tunnel failure on quiet connections. # By default no keep-alive messages are sent. hello_timeout = 7500 # milliseconds ## SESSION CONFIGURATION Sessions are described using named entries in the 'session' table inside the parent tunnel table. Each session entry describes a single session instance within the parent tunnel, and must call out at least: * the pseudowire type to be used (this must be Ethernet), * the local session ID, * the peer's session ID Here is the full list of session configuration options: # This is a session instance called "s1" within parent tunnel "t1". # Session instances are always created inside a parent tunnel. [tunnel.t1.session.s1] # pseudowire specifies the type of layer 2 frames carried by the session. # Static sessions support Ethernet pseudowires only. pseudowire = "eth" # sid specifies the local session ID of the session. # Session IDs must be unique to the tunnel for L2TPv2, or unique to # the peer for L2TPv3. # L2TPv2 session IDs are 16 bit, and may be in the range 1 - 65535. # L2TPv3 session IDs are 32 bit, and may be in the range 1 - 4294967295. sid = 12389 # psid specifies the peer's session ID for the session. # The peer's session ID is unrelated to the local session ID. # The rules for the session ID range apply to the peer session ID too. psid = 1234 # seqnum, if set, enables the transmission of sequence numbers with # L2TP data messages. Use of sequence numbers enables the data plane # to reorder data packets to ensure they are delivered in sequence. # By default sequence numbers are not used. seqnum = false # cookie, if set, specifies the local L2TPv3 cookie for the session. # Cookies are a data verification mechanism intended to allow misdirected # data packets to be detected and rejected. # Transmitted data packets will include the local cookie in their header. # Cookies may be either 4 or 8 bytes long, and contain aribrary data. # By default no local cookie is set. cookie = [ 0x12, 0xe9, 0x54, 0x0f, 0xe2, 0x68, 0x72, 0xbc ] # peer_cookie, if set, specifies the L2TPv3 cookie the peer will send in # the header of its data messages. # Messages received without the peer's cookie (or with the wrong cookie) # will be rejected. # By default no peer cookie is set. peer_cookie = [ 0x74, 0x2e, 0x28, 0xa8 ] # interface_name, if set, specifies the network interface name to be # used for the session instance. # By default the Linux kernel autogenerates an interface name specific to # the pseudowire type, e.g. "l2tpeth0", "ppp0". # Setting the interface name can be useful when you need to be certain # of the interface name a given session will use. # By default the kernel autogenerates an interface name. interface_name = "l2tpeth42" # l2spec_type specifies the L2TPv3 Layer 2 specific sublayer field to # be used in data packet headers as per RFC3931 section 3.2.2. # Currently supported values are "none" and "default". # By default no Layer 2 specific sublayer is used. l2spec_type = "default" # SEE ALSO **ql2tpd**(1) golang-github-katalix-go-l2tp-0.1.7/go.mod000066400000000000000000000010371456471064000203010ustar00rootroot00000000000000module github.com/katalix/go-l2tp go 1.18 require ( github.com/go-kit/kit v0.13.0 github.com/mdlayher/genetlink v1.3.2 github.com/mdlayher/netlink v1.7.2 github.com/pelletier/go-toml v1.9.5 golang.org/x/sys v0.12.0 ) require ( github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/josharian/native v1.1.0 // indirect github.com/mdlayher/socket v0.4.1 // indirect golang.org/x/net v0.15.0 // indirect golang.org/x/sync v0.1.0 // indirect ) golang-github-katalix-go-l2tp-0.1.7/go.sum000066400000000000000000000037101456471064000203260ustar00rootroot00000000000000github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang-github-katalix-go-l2tp-0.1.7/internal/000077500000000000000000000000001456471064000210065ustar00rootroot00000000000000golang-github-katalix-go-l2tp-0.1.7/internal/nll2tp/000077500000000000000000000000001456471064000222215ustar00rootroot00000000000000golang-github-katalix-go-l2tp-0.1.7/internal/nll2tp/README000066400000000000000000000003441456471064000231020ustar00rootroot00000000000000Within this directory the file const.go is autogenerated from the l2tp.h C header file using the c-for-go tool[1]. To regenerate: cd ../ && ~/go/bin/c-for-go -nocgo nll2tp/nll2tp.yml [1] https://github.com/xlab/c-for-go golang-github-katalix-go-l2tp-0.1.7/internal/nll2tp/const.go000066400000000000000000000124761456471064000237100ustar00rootroot00000000000000// TODO // WARNING: This file has automatically been generated on Wed, 12 Feb 2020 10:38:25 UTC. // Code generated by https://git.io/c-for-go. DO NOT EDIT. package nll2tp const ( // CmdMax as defined in nll2tp/l2tp.h:90 CmdMax = -1 // AttrMax as defined in nll2tp/l2tp.h:135 AttrMax = -1 // AttrStatsMax as defined in nll2tp/l2tp.h:152 AttrStatsMax = -1 // GenlName as defined in nll2tp/l2tp.h:198 GenlName = "l2tp" // GenlVersion as defined in nll2tp/l2tp.h:199 GenlVersion = 0x1 // GenlMcgroup as defined in nll2tp/l2tp.h:200 GenlMcgroup = "l2tp" ) const ( // CmdNoop as declared in nll2tp/l2tp.h:78 CmdNoop = iota // CmdTunnelCreate as declared in nll2tp/l2tp.h:79 CmdTunnelCreate = 1 // CmdTunnelDelete as declared in nll2tp/l2tp.h:80 CmdTunnelDelete = 2 // CmdTunnelModify as declared in nll2tp/l2tp.h:81 CmdTunnelModify = 3 // CmdTunnelGet as declared in nll2tp/l2tp.h:82 CmdTunnelGet = 4 // CmdSessionCreate as declared in nll2tp/l2tp.h:83 CmdSessionCreate = 5 // CmdSessionDelete as declared in nll2tp/l2tp.h:84 CmdSessionDelete = 6 // CmdSessionModify as declared in nll2tp/l2tp.h:85 CmdSessionModify = 7 // CmdSessionGet as declared in nll2tp/l2tp.h:86 CmdSessionGet = 8 ) const ( // AttrNone as declared in nll2tp/l2tp.h:96 AttrNone = iota // AttrPwType as declared in nll2tp/l2tp.h:97 AttrPwType = 1 // AttrEncapType as declared in nll2tp/l2tp.h:98 AttrEncapType = 2 // AttrOffset as declared in nll2tp/l2tp.h:99 AttrOffset = 3 // AttrDataSeq as declared in nll2tp/l2tp.h:100 AttrDataSeq = 4 // AttrL2specType as declared in nll2tp/l2tp.h:101 AttrL2specType = 5 // AttrL2specLen as declared in nll2tp/l2tp.h:102 AttrL2specLen = 6 // AttrProtoVersion as declared in nll2tp/l2tp.h:103 AttrProtoVersion = 7 // AttrIfname as declared in nll2tp/l2tp.h:104 AttrIfname = 8 // AttrConnId as declared in nll2tp/l2tp.h:105 AttrConnId = 9 // AttrPeerConnId as declared in nll2tp/l2tp.h:106 AttrPeerConnId = 10 // AttrSessionId as declared in nll2tp/l2tp.h:107 AttrSessionId = 11 // AttrPeerSessionId as declared in nll2tp/l2tp.h:108 AttrPeerSessionId = 12 // AttrUdpCsum as declared in nll2tp/l2tp.h:109 AttrUdpCsum = 13 // AttrVlanId as declared in nll2tp/l2tp.h:110 AttrVlanId = 14 // AttrCookie as declared in nll2tp/l2tp.h:111 AttrCookie = 15 // AttrPeerCookie as declared in nll2tp/l2tp.h:112 AttrPeerCookie = 16 // AttrDebug as declared in nll2tp/l2tp.h:113 AttrDebug = 17 // AttrRecvSeq as declared in nll2tp/l2tp.h:114 AttrRecvSeq = 18 // AttrSendSeq as declared in nll2tp/l2tp.h:115 AttrSendSeq = 19 // AttrLnsMode as declared in nll2tp/l2tp.h:116 AttrLnsMode = 20 // AttrUsingIpsec as declared in nll2tp/l2tp.h:117 AttrUsingIpsec = 21 // AttrRecvTimeout as declared in nll2tp/l2tp.h:118 AttrRecvTimeout = 22 // AttrFd as declared in nll2tp/l2tp.h:119 AttrFd = 23 // AttrIpSaddr as declared in nll2tp/l2tp.h:120 AttrIpSaddr = 24 // AttrIpDaddr as declared in nll2tp/l2tp.h:121 AttrIpDaddr = 25 // AttrUdpSport as declared in nll2tp/l2tp.h:122 AttrUdpSport = 26 // AttrUdpDport as declared in nll2tp/l2tp.h:123 AttrUdpDport = 27 // AttrMtu as declared in nll2tp/l2tp.h:124 AttrMtu = 28 // AttrMru as declared in nll2tp/l2tp.h:125 AttrMru = 29 // AttrStats as declared in nll2tp/l2tp.h:126 AttrStats = 30 // AttrIp6Saddr as declared in nll2tp/l2tp.h:127 AttrIp6Saddr = 31 // AttrIp6Daddr as declared in nll2tp/l2tp.h:128 AttrIp6Daddr = 32 // AttrUdpZeroCsum6Tx as declared in nll2tp/l2tp.h:129 AttrUdpZeroCsum6Tx = 33 // AttrUdpZeroCsum6Rx as declared in nll2tp/l2tp.h:130 AttrUdpZeroCsum6Rx = 34 // AttrPad as declared in nll2tp/l2tp.h:131 AttrPad = 35 ) const ( // AttrStatsNone as declared in nll2tp/l2tp.h:139 AttrStatsNone = iota // AttrTxPackets as declared in nll2tp/l2tp.h:140 AttrTxPackets = 1 // AttrTxBytes as declared in nll2tp/l2tp.h:141 AttrTxBytes = 2 // AttrTxErrors as declared in nll2tp/l2tp.h:142 AttrTxErrors = 3 // AttrRxPackets as declared in nll2tp/l2tp.h:143 AttrRxPackets = 4 // AttrRxBytes as declared in nll2tp/l2tp.h:144 AttrRxBytes = 5 // AttrRxSeqDiscards as declared in nll2tp/l2tp.h:145 AttrRxSeqDiscards = 6 // AttrRxOosPackets as declared in nll2tp/l2tp.h:146 AttrRxOosPackets = 7 // AttrRxErrors as declared in nll2tp/l2tp.h:147 AttrRxErrors = 8 // AttrStatsPad as declared in nll2tp/l2tp.h:148 AttrStatsPad = 9 ) // L2tpPwtype as declared in nll2tp/l2tp.h:154 type L2tpPwtype int32 // L2tpPwtype enumeration from nll2tp/l2tp.h:154 const ( PwtypeNone = 0x0000 PwtypeEthVlan = 0x0004 PwtypeEth = 0x0005 PwtypePpp = 0x0007 PwtypePppAc = 0x0008 PwtypeIp = 0x000b ) // L2tpL2specType as declared in nll2tp/l2tp.h:164 type L2tpL2specType int32 // L2tpL2specType enumeration from nll2tp/l2tp.h:164 const ( L2spectypeNone = iota L2spectypeDefault = 1 ) // L2tpEncapType as declared in nll2tp/l2tp.h:169 type L2tpEncapType int32 // L2tpEncapType enumeration from nll2tp/l2tp.h:169 const ( EncaptypeUdp = iota EncaptypeIp = 1 ) // L2tpSeqmode as declared in nll2tp/l2tp.h:174 type L2tpSeqmode int32 // L2tpSeqmode enumeration from nll2tp/l2tp.h:174 const ( SeqNone = iota SeqIp = 1 SeqAll = 2 ) // L2tpDebugFlags as declared in nll2tp/l2tp.h:188 type L2tpDebugFlags uint32 // L2tpDebugFlags enumeration from nll2tp/l2tp.h:188 const ( MsgDebug = (1 << 0) MsgControl = (1 << 1) MsgSeq = (1 << 2) MsgData = (1 << 3) ) golang-github-katalix-go-l2tp-0.1.7/internal/nll2tp/doc.go000066400000000000000000000003701456471064000233150ustar00rootroot00000000000000// TODO // WARNING: This file has automatically been generated on Wed, 12 Feb 2020 10:38:25 UTC. // Code generated by https://git.io/c-for-go. DO NOT EDIT. /* Package nll2tp provides Go bindings for the Linux L2TP GeNetlink API */ package nll2tp golang-github-katalix-go-l2tp-0.1.7/internal/nll2tp/l2tp.h000066400000000000000000000130201456471064000232470ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ /* * L2TP-over-IP socket for L2TPv3. * * Author: James Chapman */ #ifndef _LINUX_L2TP_H_ #define _LINUX_L2TP_H_ #include #if 0 #include #include #include #define IPPROTO_L2TP 115 /** * struct sockaddr_l2tpip - the sockaddr structure for L2TP-over-IP sockets * @l2tp_family: address family number AF_L2TPIP. * @l2tp_addr: protocol specific address information * @l2tp_conn_id: connection id of tunnel */ #define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */ struct sockaddr_l2tpip { /* The first fields must match struct sockaddr_in */ __kernel_sa_family_t l2tp_family; /* AF_INET */ __be16 l2tp_unused; /* INET port number (unused) */ struct in_addr l2tp_addr; /* Internet address */ __u32 l2tp_conn_id; /* Connection ID of tunnel */ /* Pad to size of `struct sockaddr'. */ unsigned char __pad[__SOCK_SIZE__ - sizeof(__kernel_sa_family_t) - sizeof(__be16) - sizeof(struct in_addr) - sizeof(__u32)]; }; /** * struct sockaddr_l2tpip6 - the sockaddr structure for L2TP-over-IPv6 sockets * @l2tp_family: address family number AF_L2TPIP. * @l2tp_addr: protocol specific address information * @l2tp_conn_id: connection id of tunnel */ struct sockaddr_l2tpip6 { /* The first fields must match struct sockaddr_in6 */ __kernel_sa_family_t l2tp_family; /* AF_INET6 */ __be16 l2tp_unused; /* INET port number (unused) */ __be32 l2tp_flowinfo; /* IPv6 flow information */ struct in6_addr l2tp_addr; /* IPv6 address */ __u32 l2tp_scope_id; /* scope id (new in RFC2553) */ __u32 l2tp_conn_id; /* Connection ID of tunnel */ }; #endif /***************************************************************************** * NETLINK_GENERIC netlink family. *****************************************************************************/ /* * Commands. * Valid TLVs of each command are:- * TUNNEL_CREATE - CONN_ID, pw_type, netns, ifname, ipinfo, udpinfo, udpcsum, vlanid * TUNNEL_DELETE - CONN_ID * TUNNEL_MODIFY - CONN_ID, udpcsum * TUNNEL_GETSTATS - CONN_ID, (stats) * TUNNEL_GET - CONN_ID, (...) * SESSION_CREATE - SESSION_ID, PW_TYPE, offset, data_seq, cookie, peer_cookie, offset, l2spec * SESSION_DELETE - SESSION_ID * SESSION_MODIFY - SESSION_ID, data_seq * SESSION_GET - SESSION_ID, (...) * SESSION_GETSTATS - SESSION_ID, (stats) * */ enum { L2TP_CMD_NOOP, L2TP_CMD_TUNNEL_CREATE, L2TP_CMD_TUNNEL_DELETE, L2TP_CMD_TUNNEL_MODIFY, L2TP_CMD_TUNNEL_GET, L2TP_CMD_SESSION_CREATE, L2TP_CMD_SESSION_DELETE, L2TP_CMD_SESSION_MODIFY, L2TP_CMD_SESSION_GET, __L2TP_CMD_MAX, }; #define L2TP_CMD_MAX (__L2TP_CMD_MAX - 1) /* * ATTR types defined for L2TP */ enum { L2TP_ATTR_NONE, /* no data */ L2TP_ATTR_PW_TYPE, /* u16, enum l2tp_pwtype */ L2TP_ATTR_ENCAP_TYPE, /* u16, enum l2tp_encap_type */ L2TP_ATTR_OFFSET, /* u16 */ L2TP_ATTR_DATA_SEQ, /* u16 */ L2TP_ATTR_L2SPEC_TYPE, /* u8, enum l2tp_l2spec_type */ L2TP_ATTR_L2SPEC_LEN, /* u8, enum l2tp_l2spec_type */ L2TP_ATTR_PROTO_VERSION, /* u8 */ L2TP_ATTR_IFNAME, /* string */ L2TP_ATTR_CONN_ID, /* u32 */ L2TP_ATTR_PEER_CONN_ID, /* u32 */ L2TP_ATTR_SESSION_ID, /* u32 */ L2TP_ATTR_PEER_SESSION_ID, /* u32 */ L2TP_ATTR_UDP_CSUM, /* u8 */ L2TP_ATTR_VLAN_ID, /* u16 */ L2TP_ATTR_COOKIE, /* 0, 4 or 8 bytes */ L2TP_ATTR_PEER_COOKIE, /* 0, 4 or 8 bytes */ L2TP_ATTR_DEBUG, /* u32, enum l2tp_debug_flags */ L2TP_ATTR_RECV_SEQ, /* u8 */ L2TP_ATTR_SEND_SEQ, /* u8 */ L2TP_ATTR_LNS_MODE, /* u8 */ L2TP_ATTR_USING_IPSEC, /* u8 */ L2TP_ATTR_RECV_TIMEOUT, /* msec */ L2TP_ATTR_FD, /* int */ L2TP_ATTR_IP_SADDR, /* u32 */ L2TP_ATTR_IP_DADDR, /* u32 */ L2TP_ATTR_UDP_SPORT, /* u16 */ L2TP_ATTR_UDP_DPORT, /* u16 */ L2TP_ATTR_MTU, /* u16 */ L2TP_ATTR_MRU, /* u16 */ L2TP_ATTR_STATS, /* nested */ L2TP_ATTR_IP6_SADDR, /* struct in6_addr */ L2TP_ATTR_IP6_DADDR, /* struct in6_addr */ L2TP_ATTR_UDP_ZERO_CSUM6_TX, /* flag */ L2TP_ATTR_UDP_ZERO_CSUM6_RX, /* flag */ L2TP_ATTR_PAD, __L2TP_ATTR_MAX, }; #define L2TP_ATTR_MAX (__L2TP_ATTR_MAX - 1) /* Nested in L2TP_ATTR_STATS */ enum { L2TP_ATTR_STATS_NONE, /* no data */ L2TP_ATTR_TX_PACKETS, /* u64 */ L2TP_ATTR_TX_BYTES, /* u64 */ L2TP_ATTR_TX_ERRORS, /* u64 */ L2TP_ATTR_RX_PACKETS, /* u64 */ L2TP_ATTR_RX_BYTES, /* u64 */ L2TP_ATTR_RX_SEQ_DISCARDS, /* u64 */ L2TP_ATTR_RX_OOS_PACKETS, /* u64 */ L2TP_ATTR_RX_ERRORS, /* u64 */ L2TP_ATTR_STATS_PAD, __L2TP_ATTR_STATS_MAX, }; #define L2TP_ATTR_STATS_MAX (__L2TP_ATTR_STATS_MAX - 1) enum l2tp_pwtype { L2TP_PWTYPE_NONE = 0x0000, L2TP_PWTYPE_ETH_VLAN = 0x0004, L2TP_PWTYPE_ETH = 0x0005, L2TP_PWTYPE_PPP = 0x0007, L2TP_PWTYPE_PPP_AC = 0x0008, L2TP_PWTYPE_IP = 0x000b, __L2TP_PWTYPE_MAX }; enum l2tp_l2spec_type { L2TP_L2SPECTYPE_NONE, L2TP_L2SPECTYPE_DEFAULT, }; enum l2tp_encap_type { L2TP_ENCAPTYPE_UDP, L2TP_ENCAPTYPE_IP, }; enum l2tp_seqmode { L2TP_SEQ_NONE = 0, L2TP_SEQ_IP = 1, L2TP_SEQ_ALL = 2, }; /** * enum l2tp_debug_flags - debug message categories for L2TP tunnels/sessions * * @L2TP_MSG_DEBUG: verbose debug (if compiled in) * @L2TP_MSG_CONTROL: userspace - kernel interface * @L2TP_MSG_SEQ: sequence numbers * @L2TP_MSG_DATA: data packets */ enum l2tp_debug_flags { L2TP_MSG_DEBUG = (1 << 0), L2TP_MSG_CONTROL = (1 << 1), L2TP_MSG_SEQ = (1 << 2), L2TP_MSG_DATA = (1 << 3), }; /* * NETLINK_GENERIC related info */ #define L2TP_GENL_NAME "l2tp" #define L2TP_GENL_VERSION 0x1 #define L2TP_GENL_MCGROUP "l2tp" #endif /* _LINUX_L2TP_H_ */ golang-github-katalix-go-l2tp-0.1.7/internal/nll2tp/nll2tp.go000066400000000000000000000467771456471064000240100ustar00rootroot00000000000000package nll2tp import ( "errors" "fmt" "sync" "github.com/mdlayher/genetlink" "github.com/mdlayher/netlink" "github.com/mdlayher/netlink/nlenc" ) // L2tpProtocolVersion describes the RFC version of the tunnel: // L2TPv2 is described by RFC2661, while L2TPv3 is described by // RFC3931. type L2tpProtocolVersion uint32 // L2tpTunnelID represents the numeric identifier of an L2TP tunnel. // This ID is used in L2TP control and data packet headers and AVPs, // and is unique to the host. type L2tpTunnelID uint32 // L2tpSessionID represents the numeric identifier of an L2TP session. // This ID is used in L2TP control and data packet headers and AVPs, // and is unique to the tunnel for L2TPv2, or the host for L2TPv3. type L2tpSessionID uint32 const ( // ProtocolVersion2 specifies L2TPv2 RFC2661 ProtocolVersion2 = 2 // ProtocolVersion3 specifies L2TPv3 RFC3931 ProtocolVersion3 = 3 ) // TunnelConfig encapsulates genetlink parameters for L2TP tunnel commands. type TunnelConfig struct { // Tid is the host's L2TP ID for the tunnel. Tid L2tpTunnelID // Ptid is the peer's L2TP ID for the tunnel Ptid L2tpTunnelID // Version is the tunnel protocol version (L2TPv2 or L2TPv3) Version L2tpProtocolVersion // Encap specifies the tunnel encapsulation type. // For L2TPv3 this may be UDP or IP. // For L2TPv2 this may only be UDP. Encap L2tpEncapType // DebugFlags specifies the kernel debugging flags to use for the tunnel instance. DebugFlags L2tpDebugFlags } // SessionConfig encapsulates genetlink parameters for L2TP session commands. type SessionConfig struct { // Tid is the host's L2TP ID for the tunnel containing the session. Tid L2tpTunnelID // Ptid is the peer's L2TP ID for the tunnel containing the session. Ptid L2tpTunnelID // Sid is the host's L2TP ID for the session. Sid L2tpSessionID // Psid is the peer's L2TP ID for the session. Psid L2tpSessionID // PseudowireType specifies the type of traffic carried by the session. // For L2TPv3 this may be PPP or Ethernet. // For L2TPv2 this may be PPP only. PseudowireType L2tpPwtype // SendSeq controls whether to send data packet sequence numbers per RFC2661 section 5.4. SendSeq bool // RecvSeq if set will cause data packets without sequence numbers to be dropped. RecvSeq bool // IsLNS if unset allows the LNS to enable data packet sequence numbers per RFC2661 section 5.4 IsLNS bool // ReorderTimeout sets the maximum amount of time, in milliseconds, to hold a data packet // in the reorder queue when sequence numbers are enabled. ReorderTimeout uint64 // LocalCookie sets the RFC3931 cookie for the session. // Transmitted data packets will include the cookie. // The LocalCookie may be either 4 or 8 bytes in length if set. LocalCookie []byte // PeerCookie sets the RFC3931 peer cookie for the session as negotiated by the control protocol. // Received data packets with a cookie mismatch are discarded. // The PeerCookie may be either 4 or 8 bytes in length if set. PeerCookie []byte // IfName use depends on the pseudowire type. // For an RFC3931 Ethernet pseudowire, IfName specifies the interface name to use for // the L2TP Ethernet interface. By default the kernel generates a name "l2tpethX". // For an RFC2661 PPP/AC pseudowire, IfName specifies the name of the interface associated // with the PPPoE session. IfName string // L2SpecType specifies the Layer 2 specific sublayer field to be used in data packets // as per RFC3931 section 3.2.2 L2SpecType L2tpL2specType // DebugFlags specifies the kernel debugging flags to use for the session instance. DebugFlags L2tpDebugFlags } // SessionStatistics includes statistics on dataplane receive and transmit. type SessionStatistics struct { // TxPacketCount is the number of data packets the session has transmitted. TxPacketCount uint64 // TxBytes is the number of data bytes the session has transmitted. TxBytes uint64 // TxErrorCount is the number of transmission errors the session has recorded. TxErrorCount uint64 // RxPacketCount is the number of data packets the session has received. RxPacketCount uint64 // RxBytes is the number of data bytes the session has received. RxBytes uint64 // RxErrorCount is the number of receive errors the session has recorded. RxErrorCount uint64 // RxSeqDiscardCount is the number of packets the session has discarded due to sequence errors. // For example, if the session is in LNS mode, has requested sequence numbers, and the client // isn't sending them. RxSeqDiscardCount uint64 // RxOOSCount is the number of packets the session has received out of sequence if data packet // reordering is enabled. RxOOSCount uint64 } // SessionInfo encapsulates dataplane session information provided by the kernel. type SessionInfo struct { // Tid is the host's L2TP ID for the tunnel containing the session. Tid L2tpTunnelID // Ptid is the peer's L2TP ID for the tunnel containing the session. Ptid L2tpTunnelID // Sid is the host's L2TP ID for the session. Sid L2tpSessionID // Psid is the peer's L2TP ID for the session. Psid L2tpSessionID // IfName is the assigned interface name for this session. IfName string // LocalCookie is the RFC3931 cookie for the session. LocalCookie []byte // PeerCookie is the RFC3931 peer cookie for the session. PeerCookie []byte // SendSeq is true if session is sending data packet sequence numbers per RFC2661 section 5.4. SendSeq bool // RecvSeq is true if session is dropping data packets received without sequence numbers. RecvSeq bool // LnsMode is true if the session is running as server. If running as server // the session will not permit the peer to control data sequence number settings. LnsMode bool // UsingIPSec is true if the session is using IPSec. UsingIPSec bool // ReorderTimeout is the maximum amount of time to hold a data packet in the reorder // queue when sequence numbers are enabled. This number is defined in milliseconds. ReorderTimeout uint64 // Statistics is the current dataplane tx/rx stats. Statistics SessionStatistics } type msgRequest struct { msg genetlink.Message family uint16 flags netlink.HeaderFlags } type msgResponse struct { msg []genetlink.Message err error } // Conn represents the genetlink L2TP connection to the kernel. type Conn struct { genlFamily genetlink.Family c *genetlink.Conn reqChan chan *msgRequest rspChan chan *msgResponse wg sync.WaitGroup } // Dial creates a new genetlink L2TP connection to the kernel. func Dial() (*Conn, error) { c, err := genetlink.Dial(nil) if err != nil { return nil, err } id, err := c.GetFamily(GenlName) if err != nil { c.Close() return nil, err } conn := &Conn{ genlFamily: id, c: c, reqChan: make(chan *msgRequest), rspChan: make(chan *msgResponse), } conn.wg.Add(1) go runConn(conn, &conn.wg) return conn, nil } // Close connection, releasing associated resources func (c *Conn) Close() { close(c.reqChan) c.wg.Wait() c.c.Close() } // CreateManagedTunnel creates a new managed tunnel instance in the kernel. // A "managed" tunnel is one whose tunnel socket fd is created and managed // by a userspace process. A managed tunnel's lifetime is bound by the lifetime // of the tunnel socket fd, and may optionally be destroyed using explicit // netlink commands. func (c *Conn) CreateManagedTunnel(fd int, config *TunnelConfig) (err error) { if fd < 0 { return errors.New("managed tunnel needs a valid socket file descriptor") } attr, err := tunnelCreateAttr(config) if err != nil { return err } return c.createTunnel(append(attr, netlink.Attribute{ Type: AttrFd, Data: nlenc.Uint32Bytes(uint32(fd)), })) } // CreateStaticTunnel creates a new static tunnel instance in the kernel. // A "static" tunnel is one whose tunnel socket fd is implicitly created // by the kernel. A static tunnel must be explicitly deleted using netlink // commands. func (c *Conn) CreateStaticTunnel( localAddr []byte, localPort uint16, peerAddr []byte, peerPort uint16, config *TunnelConfig) (err error) { if config == nil { return errors.New("invalid nil tunnel config pointer") } if len(localAddr) == 0 { return errors.New("unmanaged tunnel needs a valid local address") } if len(peerAddr) == 0 { return errors.New("unmanaged tunnel needs a valid peer address") } if len(localAddr) != len(peerAddr) { return errors.New("local and peer IP addresses must be of the same address family") } if config.Encap == EncaptypeUdp { if localPort == 0 { return errors.New("unmanaged tunnel needs a valid local port") } if peerPort == 0 { return errors.New("unmanaged tunnel needs a valid peer port") } } attr, err := tunnelCreateAttr(config) if err != nil { return err } switch len(localAddr) { case 4: attr = append(attr, netlink.Attribute{ Type: AttrIpSaddr, Data: localAddr, }, netlink.Attribute{ Type: AttrIpDaddr, Data: peerAddr, }) case 16: attr = append(attr, netlink.Attribute{ Type: AttrIp6Saddr, Data: localAddr, }, netlink.Attribute{ Type: AttrIp6Daddr, Data: peerAddr, }) default: panic("unexpected address length") } return c.createTunnel(append(attr, netlink.Attribute{ Type: AttrUdpSport, Data: nlenc.Uint16Bytes(localPort), }, netlink.Attribute{ Type: AttrUdpDport, Data: nlenc.Uint16Bytes(peerPort), })) } // DeleteTunnel deletes a tunnel instance from the kernel. // Deleting a tunnel instance implicitly destroys any sessions // running in that tunnel. func (c *Conn) DeleteTunnel(config *TunnelConfig) error { if config == nil { return errors.New("invalid nil tunnel config") } b, err := netlink.MarshalAttributes([]netlink.Attribute{ { Type: AttrConnId, Data: nlenc.Uint32Bytes(uint32(config.Tid)), }, }) if err != nil { return err } req := genetlink.Message{ Header: genetlink.Header{ Command: CmdTunnelDelete, Version: c.genlFamily.Version, }, Data: b, } _, err = c.execute(req, c.genlFamily.ID, netlink.Request|netlink.Acknowledge) return err } // CreateSession creates a session instance in the kernel. // The parent tunnel instance referenced by the tunnel IDs in // the session configuration must already exist in the kernel. func (c *Conn) CreateSession(config *SessionConfig) error { attr, err := sessionCreateAttr(config) if err != nil { return err } b, err := netlink.MarshalAttributes(attr) if err != nil { return err } req := genetlink.Message{ Header: genetlink.Header{ Command: CmdSessionCreate, Version: c.genlFamily.Version, }, Data: b, } _, err = c.execute(req, c.genlFamily.ID, netlink.Request|netlink.Acknowledge) return err } // DeleteSession deletes a session instance from the kernel. func (c *Conn) DeleteSession(config *SessionConfig) error { if config == nil { return errors.New("invalid nil session config") } b, err := netlink.MarshalAttributes([]netlink.Attribute{ { Type: AttrConnId, Data: nlenc.Uint32Bytes(uint32(config.Tid)), }, { Type: AttrSessionId, Data: nlenc.Uint32Bytes(uint32(config.Sid)), }, }) if err != nil { return err } req := genetlink.Message{ Header: genetlink.Header{ Command: CmdSessionDelete, Version: c.genlFamily.Version, }, Data: b, } _, err = c.execute(req, c.genlFamily.ID, netlink.Request|netlink.Acknowledge) return err } func (stats *SessionStatistics) decode(ad *netlink.AttributeDecoder) error { for ad.Next() { switch ad.Type() { case AttrTxPackets: stats.TxPacketCount = ad.Uint64() case AttrTxBytes: stats.TxBytes = ad.Uint64() case AttrTxErrors: stats.TxErrorCount = ad.Uint64() case AttrRxPackets: stats.RxPacketCount = ad.Uint64() case AttrRxBytes: stats.RxBytes = ad.Uint64() case AttrRxErrors: stats.RxErrorCount = ad.Uint64() case AttrRxSeqDiscards: stats.RxSeqDiscardCount = ad.Uint64() case AttrRxOosPackets: stats.RxOOSCount = ad.Uint64() } } return nil } func sessionInfo_decode(data []byte) (*SessionInfo, error) { ad, err := netlink.NewAttributeDecoder(data) if err != nil { return nil, fmt.Errorf("failed to create attribute decoder: %v", err) } var info SessionInfo for ad.Next() { switch ad.Type() { case AttrConnId: info.Tid = L2tpTunnelID(ad.Uint32()) case AttrPeerConnId: info.Ptid = L2tpTunnelID(ad.Uint32()) case AttrSessionId: info.Sid = L2tpSessionID(ad.Uint32()) case AttrPeerSessionId: info.Psid = L2tpSessionID(ad.Uint32()) case AttrIfname: info.IfName = ad.String() case AttrCookie: info.LocalCookie = ad.Bytes() case AttrPeerCookie: info.PeerCookie = ad.Bytes() case AttrSendSeq: info.SendSeq = ad.Uint8() != 0 case AttrRecvSeq: info.RecvSeq = ad.Uint8() != 0 case AttrLnsMode: info.LnsMode = ad.Uint8() != 0 case AttrUsingIpsec: info.UsingIPSec = ad.Uint8() != 0 case AttrRecvTimeout: info.ReorderTimeout = ad.Uint64() case AttrStats: ad.Nested(info.Statistics.decode) } } if err = ad.Err(); err != nil { return nil, fmt.Errorf("failed to decode attributes: %v", err) } return &info, nil } // GetSessionInfo retrieves dataplane session information from the kernel. func (c *Conn) GetSessionInfo(config *SessionConfig) (*SessionInfo, error) { if config == nil { return nil, errors.New("invalid nil session config") } b, err := netlink.MarshalAttributes([]netlink.Attribute{ { Type: AttrConnId, Data: nlenc.Uint32Bytes(uint32(config.Tid)), }, { Type: AttrSessionId, Data: nlenc.Uint32Bytes(uint32(config.Sid)), }, }) if err != nil { return nil, err } req := genetlink.Message{ Header: genetlink.Header{ Command: CmdSessionGet, Version: c.genlFamily.Version, }, Data: b, } msgs, err := c.execute(req, c.genlFamily.ID, netlink.Request) if err != nil { return nil, err } info := SessionInfo{} for _, rsp := range msgs { if rsp.Header.Command != CmdSessionGet { continue } attributes, err := netlink.UnmarshalAttributes(rsp.Data) if err != nil { return nil, err } for _, a := range attributes { switch a.Type { } } } return &info, nil } func (c *Conn) createTunnel(attr []netlink.Attribute) error { b, err := netlink.MarshalAttributes(attr) if err != nil { return err } req := genetlink.Message{ Header: genetlink.Header{ Command: CmdTunnelCreate, Version: c.genlFamily.Version, }, Data: b, } _, err = c.execute(req, c.genlFamily.ID, netlink.Request|netlink.Acknowledge) return err } func (c *Conn) execute(msg genetlink.Message, family uint16, flags netlink.HeaderFlags) ([]genetlink.Message, error) { c.reqChan <- &msgRequest{ msg: msg, family: family, flags: flags, } rsp, ok := <-c.rspChan if !ok { return nil, errors.New("netlink connection closed") } return rsp.msg, rsp.err } func tunnelCreateAttr(config *TunnelConfig) ([]netlink.Attribute, error) { // Basic error checking if config == nil { return nil, errors.New("invalid nil tunnel config") } if config.Tid == 0 { return nil, errors.New("tunnel config must have a non-zero tunnel ID") } if config.Ptid == 0 { return nil, errors.New("tunnel config must have a non-zero peer tunnel ID") } if config.Version < ProtocolVersion2 || config.Version > ProtocolVersion3 { return nil, fmt.Errorf("invalid tunnel protocol version %d", config.Version) } if config.Encap != EncaptypeUdp && config.Encap != EncaptypeIp { return nil, errors.New("invalid tunnel encap (expect IP or UDP)") } // Version-specific checks if config.Version == ProtocolVersion2 { if config.Tid > 65535 { return nil, errors.New("L2TPv2 tunnel ID can't exceed 16-bit limit") } if config.Ptid > 65535 { return nil, errors.New("L2TPv2 peer tunnel ID can't exceed 16-bit limit") } if config.Encap != EncaptypeUdp { return nil, errors.New("L2TPv2 only supports UDP encapsuation") } } return []netlink.Attribute{ { Type: AttrConnId, Data: nlenc.Uint32Bytes(uint32(config.Tid)), }, { Type: AttrPeerConnId, Data: nlenc.Uint32Bytes(uint32(config.Ptid)), }, { Type: AttrProtoVersion, Data: nlenc.Uint8Bytes(uint8(config.Version)), }, { Type: AttrEncapType, Data: nlenc.Uint16Bytes(uint16(config.Encap)), }, { Type: AttrDebug, Data: nlenc.Uint32Bytes(uint32(config.DebugFlags)), }, }, nil } func sessionCreateAttr(config *SessionConfig) ([]netlink.Attribute, error) { // Sanity checks if config == nil { return nil, errors.New("invalid nil session config") } if config.Tid == 0 { return nil, errors.New("session config must have a non-zero parent tunnel ID") } if config.Ptid == 0 { return nil, errors.New("session config must have a non-zero parent peer tunnel ID") } if config.Sid == 0 { return nil, errors.New("session config must have a non-zero session ID") } if config.Psid == 0 { return nil, errors.New("session config must have a non-zero peer session ID") } if config.PseudowireType == PwtypeNone { return nil, errors.New("session config must have a valid pseudowire type") } if len(config.LocalCookie) > 0 { if len(config.LocalCookie) != 4 && len(config.LocalCookie) != 8 { return nil, fmt.Errorf("session config has peer cookie of %d bytes: valid lengths are 4 or 8 bytes", len(config.LocalCookie)) } } if len(config.PeerCookie) > 0 { if len(config.PeerCookie) != 4 && len(config.PeerCookie) != 8 { return nil, fmt.Errorf("session config has peer cookie of %d bytes: valid lengths are 4 or 8 bytes", len(config.PeerCookie)) } } attr := []netlink.Attribute{ { Type: AttrConnId, Data: nlenc.Uint32Bytes(uint32(config.Tid)), }, { Type: AttrPeerConnId, Data: nlenc.Uint32Bytes(uint32(config.Ptid)), }, { Type: AttrSessionId, Data: nlenc.Uint32Bytes(uint32(config.Sid)), }, { Type: AttrPeerSessionId, Data: nlenc.Uint32Bytes(uint32(config.Psid)), }, } // VLAN pseudowires use the kernel l2tp_eth driver if config.PseudowireType == PwtypeEthVlan { attr = append(attr, netlink.Attribute{ Type: AttrPwType, Data: nlenc.Uint16Bytes(uint16(PwtypeEth)), }) } else { attr = append(attr, netlink.Attribute{ Type: AttrPwType, Data: nlenc.Uint16Bytes(uint16(config.PseudowireType)), }) } if config.SendSeq { attr = append(attr, netlink.Attribute{ Type: AttrSendSeq, Data: nlenc.Uint8Bytes(1), }) } if config.RecvSeq { attr = append(attr, netlink.Attribute{ Type: AttrRecvSeq, Data: nlenc.Uint8Bytes(1), }) } if (config.SendSeq || config.RecvSeq) && config.IsLNS { attr = append(attr, netlink.Attribute{ Type: AttrLnsMode, Data: nlenc.Uint8Bytes(1), }) } if config.ReorderTimeout > 0 { attr = append(attr, netlink.Attribute{ Type: AttrRecvTimeout, Data: nlenc.Uint64Bytes(config.ReorderTimeout), }) } if len(config.LocalCookie) > 0 { attr = append(attr, netlink.Attribute{ Type: AttrCookie, Data: config.LocalCookie, }) } if len(config.PeerCookie) > 0 { attr = append(attr, netlink.Attribute{ Type: AttrCookie, Data: config.PeerCookie, }) } if config.IfName != "" { attr = append(attr, netlink.Attribute{ Type: AttrIfname, Data: nlenc.Bytes(config.IfName), }) } attr = append(attr, netlink.Attribute{ Type: AttrL2specType, Data: nlenc.Uint8Bytes(uint8(config.L2SpecType)), }) switch config.L2SpecType { case L2spectypeNone: attr = append(attr, netlink.Attribute{ Type: AttrL2specLen, Data: nlenc.Uint8Bytes(0), }) case L2spectypeDefault: attr = append(attr, netlink.Attribute{ Type: AttrL2specLen, Data: nlenc.Uint8Bytes(4), }) default: return nil, fmt.Errorf("unhandled L2 Spec Type %v", config.L2SpecType) } return attr, nil } func runConn(c *Conn, wg *sync.WaitGroup) { defer wg.Done() for req := range c.reqChan { m, err := c.c.Execute(req.msg, req.family, req.flags) c.rspChan <- &msgResponse{ msg: m, err: err, } } } golang-github-katalix-go-l2tp-0.1.7/internal/nll2tp/nll2tp.yml000066400000000000000000000012361456471064000241610ustar00rootroot00000000000000--- GENERATOR: PackageName: nll2tp PackageDescription: "Package nll2tp provides Go bindings for the Linux L2TP GeNetlink API" PackageLicense: "TODO" Options: SafeStrings: true PARSER: IncludePaths: [/usr/include, /usr/include/x86_64-linux-gnu] SourcesPaths: [l2tp.h] TRANSLATOR: ConstRules: defines: expand enum: expand Rules: const: - {transform: lower} - {action: accept, from: "^L2TP_"} - {action: replace, from: "^l2tp_", to: _} - {transform: export} type: - {transform: export} post-global: - {load: snakecase} golang-github-katalix-go-l2tp-0.1.7/l2tp/000077500000000000000000000000001456471064000200535ustar00rootroot00000000000000golang-github-katalix-go-l2tp-0.1.7/l2tp/avp.go000066400000000000000000001163471456471064000212040ustar00rootroot00000000000000package l2tp import ( "bytes" "encoding/binary" "errors" "fmt" "io" "strings" ) type avpFlagLen uint16 // avpVendorID is the Vendor ID from the AVP header as per RFC2661 section 4.1 type avpVendorID uint16 // avpType is the attribute type from the AVP header as per RFC2661 section 4.1 type avpType uint16 // avpMsgType stores the value of the Message Type AVP type avpMsgType uint16 // avpDataType indicates the type of the data value carried by the AVP type avpDataType int type avpInfo struct { avpType avpType VendorID avpVendorID isMandatory bool dataType avpDataType } // Don't be tempted to try to make the fields in this structure private: // doing so breaks the reflection properties which binary.Read depends upon // for extracting the header from the bytearray. type avpHeader struct { FlagLen avpFlagLen VendorID avpVendorID AvpType avpType } type avpPayload struct { dataType avpDataType data []byte } // avp represents a single AVP in an L2TP control message type avp struct { header avpHeader payload avpPayload } // avpResultCode represents an RFC2661/RFC3931 result code type avpResultCode uint16 // avpErrorCode represents an RFC2661/RFC3931 error code type avpErrorCode uint16 // resultCode represents an RFC2661/RFC3931 result code AVP type resultCode struct { result avpResultCode errCode avpErrorCode errMsg string } const ( avpHeaderLen = 6 // vendorIDIetf is the namespace used for standard AVPS described // by RFC2661 and RFC3931. vendorIDIetf = 0 ) const ( // avpDataTypeEmpty represents an AVP with no value avpDataTypeEmpty avpDataType = iota // avpDataTypeUint16 represents an AVP carrying a single uint16 value avpDataTypeUint16 avpDataType = iota // avpDataTypeUint32 represents an AVP carrying a single uint32 value avpDataTypeUint32 avpDataType = iota // avpDataTypeUint64 represents an AVP carrying a single uint64 value avpDataTypeUint64 avpDataType = iota // avpDataTypeString represents an AVP carrying an ASCII string avpDataTypeString avpDataType = iota // avpDataTypeBytes represents an AVP carrying a raw byte array avpDataTypeBytes avpDataType = iota // avpDataTypeResultCode represents an AVP carrying an RFC2661 result code avpDataTypeResultCode avpDataType = iota // avpDataTypeMsgID represents an AVP carrying the message type identifier avpDataTypeMsgID avpDataType = iota // avpDataTypeUnimplemented represents an AVP carrying a currently unimplemented data type avpDataTypeUnimplemented avpDataType = iota // avpDataTypeIllegal represents an AVP carrying an illegal data type. // AVPs falling into this category are typically those with currently // reserved IDs as per the RFCs. avpDataTypeIllegal avpDataType = iota // avpDataTypeMax is a sentinel value for test purposes avpDataTypeMax avpDataType = iota ) var avpInfoTable = [...]avpInfo{ {avpType: avpTypeMessage, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeMsgID}, {avpType: avpTypeResultCode, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeResultCode}, {avpType: avpTypeProtocolVersion, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeBytes}, {avpType: avpTypeFramingCap, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeUint32}, {avpType: avpTypeBearerCap, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeUint32}, {avpType: avpTypeTiebreaker, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeBytes}, {avpType: avpTypeFirmwareRevision, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeUint16}, {avpType: avpTypeHostName, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeString}, {avpType: avpTypeVendorName, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeString}, {avpType: avpTypeTunnelID, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeUint16}, {avpType: avpTypeRxWindowSize, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeUint16}, {avpType: avpTypeChallenge, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeBytes}, {avpType: avpTypeQ931CauseCode, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeUnimplemented}, // TODO {avpType: avpTypeChallengeResponse, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeBytes}, {avpType: avpTypeSessionID, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeUint16}, {avpType: avpTypeCallSerialNumber, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeUint32}, {avpType: avpTypeMinimumBps, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeUint32}, {avpType: avpTypeMaximumBps, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeUint32}, {avpType: avpTypeBearerType, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeUint32}, {avpType: avpTypeFramingType, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeUint32}, {avpType: avpTypePacketProcDelay, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeUnimplemented}, // TODO {avpType: avpTypeCalledNumber, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeString}, {avpType: avpTypeCallingNumber, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeString}, {avpType: avpTypeSubAddress, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeString}, {avpType: avpTypeConnectSpeed, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeUint32}, {avpType: avpTypePhysicalChannelID, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeUint32}, {avpType: avpTypeInitialRcvdLcpConfreq, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeBytes}, {avpType: avpTypeLastSentLcpConfreq, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeBytes}, {avpType: avpTypeLastRcvdLcpConfreq, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeBytes}, {avpType: avpTypeProxyAuthType, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeUint16}, {avpType: avpTypeProxyAuthName, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeString}, {avpType: avpTypeProxyAuthChallenge, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeBytes}, {avpType: avpTypeProxyAuthID, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeBytes}, {avpType: avpTypeProxyAuthResponse, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeBytes}, {avpType: avpTypeCallErrors, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeUnimplemented}, // TODO {avpType: avpTypeAccm, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeUnimplemented}, // TODO {avpType: avpTypeRandomVector, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeBytes}, {avpType: avpTypePrivGroupID, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeString}, {avpType: avpTypeRxConnectSpeed, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeUint32}, {avpType: avpTypeSequencingRequired, VendorID: vendorIDIetf, isMandatory: true, dataType: avpDataTypeEmpty}, {avpType: avpTypeUnused40, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypeUnused41, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypeUnused42, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypeUnused43, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypeUnused44, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypeUnused45, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypeUnused46, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypeUnused47, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypeUnused48, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypeUnused49, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypeUnused50, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypeUnused51, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypeUnused52, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypeUnused53, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypeUnused54, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypeUnused55, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypeUnused56, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypeUnused57, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypeExtended, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypeMessageDigest, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeBytes}, {avpType: avpTypeRouterID, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeUint32}, {avpType: avpTypeAssignedConnID, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeUint32}, {avpType: avpTypePseudowireCaps, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeUnimplemented}, {avpType: avpTypeLocalSessionID, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeUint32}, {avpType: avpTypeRemoteSessionID, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeUint32}, {avpType: avpTypeAssignedCookie, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeBytes}, {avpType: avpTypeRemoteEndID, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeBytes}, {avpType: avpTypeUnused67, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeIllegal}, {avpType: avpTypePseudowireType, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeUint16}, {avpType: avpTypeL2specificSublayer, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeUint16}, {avpType: avpTypeDataSequencing, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeUint16}, {avpType: avpTypeCircuitStatus, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeUint16}, {avpType: avpTypePreferredLanguage, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeBytes}, {avpType: avpTypeControlAuthNonce, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeBytes}, {avpType: avpTypeTxConnectSpeedBps, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeUint64}, {avpType: avpTypeRxConnectSpeedBps, VendorID: vendorIDIetf, isMandatory: false, dataType: avpDataTypeUint64}, } // AVP type identifiers as per RFC2661 and RFC3931, representing the // value held by a given AVP. const ( avpTypeMessage avpType = 0 avpTypeResultCode avpType = 1 avpTypeProtocolVersion avpType = 2 avpTypeFramingCap avpType = 3 avpTypeBearerCap avpType = 4 avpTypeTiebreaker avpType = 5 avpTypeFirmwareRevision avpType = 6 avpTypeHostName avpType = 7 avpTypeVendorName avpType = 8 avpTypeTunnelID avpType = 9 avpTypeRxWindowSize avpType = 10 avpTypeChallenge avpType = 11 avpTypeQ931CauseCode avpType = 12 avpTypeChallengeResponse avpType = 13 avpTypeSessionID avpType = 14 avpTypeCallSerialNumber avpType = 15 avpTypeMinimumBps avpType = 16 avpTypeMaximumBps avpType = 17 avpTypeBearerType avpType = 18 avpTypeFramingType avpType = 19 avpTypePacketProcDelay avpType = 20 /* Draft only (ignored) */ avpTypeCalledNumber avpType = 21 avpTypeCallingNumber avpType = 22 avpTypeSubAddress avpType = 23 avpTypeConnectSpeed avpType = 24 avpTypePhysicalChannelID avpType = 25 avpTypeInitialRcvdLcpConfreq avpType = 26 avpTypeLastSentLcpConfreq avpType = 27 avpTypeLastRcvdLcpConfreq avpType = 28 avpTypeProxyAuthType avpType = 29 avpTypeProxyAuthName avpType = 30 avpTypeProxyAuthChallenge avpType = 31 avpTypeProxyAuthID avpType = 32 avpTypeProxyAuthResponse avpType = 33 avpTypeCallErrors avpType = 34 avpTypeAccm avpType = 35 avpTypeRandomVector avpType = 36 avpTypePrivGroupID avpType = 37 avpTypeRxConnectSpeed avpType = 38 avpTypeSequencingRequired avpType = 39 avpTypeUnused40 avpType = 40 avpTypeUnused41 avpType = 41 avpTypeUnused42 avpType = 42 avpTypeUnused43 avpType = 43 avpTypeUnused44 avpType = 44 avpTypeUnused45 avpType = 45 avpTypeUnused46 avpType = 46 avpTypeUnused47 avpType = 47 avpTypeUnused48 avpType = 48 avpTypeUnused49 avpType = 49 avpTypeUnused50 avpType = 50 avpTypeUnused51 avpType = 51 avpTypeUnused52 avpType = 52 avpTypeUnused53 avpType = 53 avpTypeUnused54 avpType = 54 avpTypeUnused55 avpType = 55 avpTypeUnused56 avpType = 56 avpTypeUnused57 avpType = 57 avpTypeExtended avpType = 58 avpTypeMessageDigest avpType = 59 avpTypeRouterID avpType = 60 avpTypeAssignedConnID avpType = 61 avpTypePseudowireCaps avpType = 62 avpTypeLocalSessionID avpType = 63 avpTypeRemoteSessionID avpType = 64 avpTypeAssignedCookie avpType = 65 avpTypeRemoteEndID avpType = 66 avpTypeUnused67 avpType = 67 avpTypePseudowireType avpType = 68 avpTypeL2specificSublayer avpType = 69 avpTypeDataSequencing avpType = 70 avpTypeCircuitStatus avpType = 71 avpTypePreferredLanguage avpType = 72 avpTypeControlAuthNonce avpType = 73 avpTypeTxConnectSpeedBps avpType = 74 avpTypeRxConnectSpeedBps avpType = 75 avpTypeMax avpType = 76 ) // AVP message types as per RFC2661 and RFC3931, representing the various // control protocol messages used in the L2TPv2 and L2TPv3 protocols. const ( avpMsgTypeIllegal avpMsgType = 0 avpMsgTypeSccrq avpMsgType = 1 avpMsgTypeSccrp avpMsgType = 2 avpMsgTypeScccn avpMsgType = 3 avpMsgTypeStopccn avpMsgType = 4 avpMsgTypeReserved5 avpMsgType = 5 avpMsgTypeHello avpMsgType = 6 avpMsgTypeOcrq avpMsgType = 7 avpMsgTypeOcrp avpMsgType = 8 avpMsgTypeOccn avpMsgType = 9 avpMsgTypeIcrq avpMsgType = 10 avpMsgTypeIcrp avpMsgType = 11 avpMsgTypeIccn avpMsgType = 12 avpMsgTypeReserved13 avpMsgType = 13 avpMsgTypeCdn avpMsgType = 14 avpMsgTypeWen avpMsgType = 15 avpMsgTypeSli avpMsgType = 16 avpMsgTypeMdmst avpMsgType = 17 avpMsgTypeSrrq avpMsgType = 18 avpMsgTypeSrrp avpMsgType = 19 avpMsgTypeAck avpMsgType = 20 avpMsgTypeFsq avpMsgType = 21 avpMsgTypeFsr avpMsgType = 22 avpMsgTypeMsrq avpMsgType = 23 avpMsgTypeMsrp avpMsgType = 24 avpMsgTypeMse avpMsgType = 25 avpMsgTypeMsi avpMsgType = 26 avpMsgTypeMsen avpMsgType = 27 avpMsgTypeCsun avpMsgType = 28 avpMsgTypeCsurq avpMsgType = 29 avpMsgTypeMax avpMsgType = 30 ) // AVP result codes as per RFC2661 and RFC3931. // StopCCN messages and CDN messages have seperate result codes. const ( avpStopCCNResultCodeReserved avpResultCode = 0 avpStopCCNResultCodeClearConnection avpResultCode = 1 avpStopCCNResultCodeGeneralError avpResultCode = 2 avpStopCCNResultCodeChannelExists avpResultCode = 3 avpStopCCNResultCodeChannelNotAuthorized avpResultCode = 4 avpStopCCNResultCodeChannelProtocolVersionUnsupported avpResultCode = 5 avpStopCCNResultCodeChannelShuttingDown avpResultCode = 6 avpStopCCNResultCodeChannelFSMError avpResultCode = 7 avpCDNResultCodeReserved avpResultCode = 0 avpCDNResultCodeLostCarrier avpResultCode = 1 avpCDNResultCodeGeneralError avpResultCode = 2 avpCDNResultCodeAdminDisconnect avpResultCode = 3 avpCDNResultCodeNoResources avpResultCode = 4 avpCDNResultCodeNotAvailable avpResultCode = 5 avpCDNResultCodeInvalidDestination avpResultCode = 6 avpCDNResultCodeNoAnswer avpResultCode = 7 avpCDNResultCodeBusy avpResultCode = 8 avpCDNResultCodeNoDialTone avpResultCode = 9 avpCDNResultCodeTimeout avpResultCode = 10 avpCDNResultCodeBadTransport avpResultCode = 11 ) // AVP error codes as per RFC2661 and RFC3931 const ( avpErrorCodeNoError avpErrorCode = 0 avpErrorCodeNoControlConnection avpErrorCode = 1 avpErrorCodeBadLength avpErrorCode = 2 avpErrorCodeBadValue avpErrorCode = 3 avpErrorCodeNoResource avpErrorCode = 4 avpErrorCodeInvalidSessionID avpErrorCode = 5 avpErrorCodeVendorSpecificError avpErrorCode = 6 avpErrorCodeTryAnother avpErrorCode = 7 avpErrorCodeMBitShutdown avpErrorCode = 8 ) // String converts an avpType identifier into a human-readable string. // Implements the fmt.Stringer() interface. var _ fmt.Stringer = (*avpType)(nil) func (t avpType) String() string { switch t { case avpTypeMessage: return "avpTypeMessage" case avpTypeResultCode: return "avpTypeResultCode" case avpTypeProtocolVersion: return "avpTypeProtocolVersion" case avpTypeFramingCap: return "avpTypeFramingCap" case avpTypeBearerCap: return "avpTypeBearerCap" case avpTypeTiebreaker: return "avpTypeTiebreaker" case avpTypeFirmwareRevision: return "avpTypeFirmwareRevision" case avpTypeHostName: return "avpTypeHostName" case avpTypeVendorName: return "avpTypeVendorName" case avpTypeTunnelID: return "avpTypeTunnelID" case avpTypeRxWindowSize: return "avpTypeRxWindowSize" case avpTypeChallenge: return "avpTypeChallenge" case avpTypeQ931CauseCode: return "avpTypeQ931CauseCode" case avpTypeChallengeResponse: return "avpTypeChallengeResponse" case avpTypeSessionID: return "avpTypeSessionID" case avpTypeCallSerialNumber: return "avpTypeCallSerialNumber" case avpTypeMinimumBps: return "avpTypeMinimumBps" case avpTypeMaximumBps: return "avpTypeMaximumBps" case avpTypeBearerType: return "avpTypeBearerType" case avpTypeFramingType: return "avpTypeFramingType" case avpTypePacketProcDelay: return "avpTypePacketProcDelay" case avpTypeCalledNumber: return "avpTypeCalledNumber" case avpTypeCallingNumber: return "avpTypeCallingNumber" case avpTypeSubAddress: return "avpTypeSubAddress" case avpTypeConnectSpeed: return "avpTypeConnectSpeed" case avpTypePhysicalChannelID: return "avpTypePhysicalChannelID" case avpTypeInitialRcvdLcpConfreq: return "avpTypeInitialRcvdLcpConfreq" case avpTypeLastSentLcpConfreq: return "avpTypeLastSentLcpConfreq" case avpTypeLastRcvdLcpConfreq: return "avpTypeLastRcvdLcpConfreq" case avpTypeProxyAuthType: return "avpTypeProxyAuthType" case avpTypeProxyAuthName: return "avpTypeProxyAuthName" case avpTypeProxyAuthChallenge: return "avpTypeProxyAuthChallenge" case avpTypeProxyAuthID: return "avpTypeProxyAuthID" case avpTypeProxyAuthResponse: return "avpTypeProxyAuthResponse" case avpTypeCallErrors: return "avpTypeCallErrors" case avpTypeAccm: return "avpTypeAccm" case avpTypeRandomVector: return "avpTypeRandomVector" case avpTypePrivGroupID: return "avpTypePrivGroupID" case avpTypeRxConnectSpeed: return "avpTypeRxConnectSpeed" case avpTypeSequencingRequired: return "avpTypeSequencingRequired" case avpTypeUnused40: return "avpTypeUnused40" case avpTypeUnused41: return "avpTypeUnused41" case avpTypeUnused42: return "avpTypeUnused42" case avpTypeUnused43: return "avpTypeUnused43" case avpTypeUnused44: return "avpTypeUnused44" case avpTypeUnused45: return "avpTypeUnused45" case avpTypeUnused46: return "avpTypeUnused46" case avpTypeUnused47: return "avpTypeUnused47" case avpTypeUnused48: return "avpTypeUnused48" case avpTypeUnused49: return "avpTypeUnused49" case avpTypeUnused50: return "avpTypeUnused50" case avpTypeUnused51: return "avpTypeUnused51" case avpTypeUnused52: return "avpTypeUnused52" case avpTypeUnused53: return "avpTypeUnused53" case avpTypeUnused54: return "avpTypeUnused54" case avpTypeUnused55: return "avpTypeUnused55" case avpTypeUnused56: return "avpTypeUnused56" case avpTypeUnused57: return "avpTypeUnused57" case avpTypeExtended: return "avpTypeExtended" case avpTypeMessageDigest: return "avpTypeMessageDigest" case avpTypeRouterID: return "avpTypeRouterID" case avpTypeAssignedConnID: return "avpTypeAssignedConnID" case avpTypePseudowireCaps: return "avpTypePseudowireCaps" case avpTypeLocalSessionID: return "avpTypeLocalSessionID" case avpTypeRemoteSessionID: return "avpTypeRemoteSessionID" case avpTypeAssignedCookie: return "avpTypeAssignedCookie" case avpTypeRemoteEndID: return "avpTypeRemoteEndID" case avpTypeUnused67: return "avpTypeUnused67" case avpTypePseudowireType: return "avpTypePseudowireType" case avpTypeL2specificSublayer: return "avpTypeL2specificSublayer" case avpTypeDataSequencing: return "avpTypeDataSequencing" case avpTypeCircuitStatus: return "avpTypeCircuitStatus" case avpTypePreferredLanguage: return "avpTypePreferredLanguage" case avpTypeControlAuthNonce: return "avpTypeControlAuthNonce" case avpTypeTxConnectSpeedBps: return "avpTypeTxConnectSpeedBps" case avpTypeRxConnectSpeedBps: return "avpTypeRxConnectSpeedBps" } return "" } // String converts an avpMsgType identifier into a human-readable string. // Implements the fmt.Stringer() interface. var _ fmt.Stringer = (*avpMsgType)(nil) func (t avpMsgType) String() string { switch t { case avpMsgTypeIllegal: return "avpMsgTypeIllegal" case avpMsgTypeSccrq: return "avpMsgTypeSccrq" case avpMsgTypeSccrp: return "avpMsgTypeSccrp" case avpMsgTypeScccn: return "avpMsgTypeScccn" case avpMsgTypeStopccn: return "avpMsgTypeStopccn" case avpMsgTypeReserved5: return "avpMsgTypeReserved5" case avpMsgTypeHello: return "avpMsgTypeHello" case avpMsgTypeOcrq: return "avpMsgTypeOcrq" case avpMsgTypeOcrp: return "avpMsgTypeOcrp" case avpMsgTypeOccn: return "avpMsgTypeOccn" case avpMsgTypeIcrq: return "avpMsgTypeIcrq" case avpMsgTypeIcrp: return "avpMsgTypeIcrp" case avpMsgTypeIccn: return "avpMsgTypeIccn" case avpMsgTypeReserved13: return "avpMsgTypeReserved13" case avpMsgTypeCdn: return "avpMsgTypeCdn" case avpMsgTypeWen: return "avpMsgTypeWen" case avpMsgTypeSli: return "avpMsgTypeSli" case avpMsgTypeMdmst: return "avpMsgTypeMdmst" case avpMsgTypeSrrq: return "avpMsgTypeSrrq" case avpMsgTypeSrrp: return "avpMsgTypeSrrp" case avpMsgTypeAck: return "avpMsgTypeAck" case avpMsgTypeFsq: return "avpMsgTypeFsq" case avpMsgTypeFsr: return "avpMsgTypeFsr" case avpMsgTypeMsrq: return "avpMsgTypeMsrq" case avpMsgTypeMsrp: return "avpMsgTypeMsrp" case avpMsgTypeMse: return "avpMsgTypeMse" case avpMsgTypeMsi: return "avpMsgTypeMsi" case avpMsgTypeMsen: return "avpMsgTypeMsen" case avpMsgTypeCsun: return "avpMsgTypeCsun" case avpMsgTypeCsurq: return "avpMsgTypeCsurq" } return "" } // String represents the AVP as a human-readable string. // Implements the fmt.Stringer() interface. var _ fmt.Stringer = (*avp)(nil) func (avp avp) String() string { return fmt.Sprintf("%s %s", avp.header, avp.payload) } // String represents the vendor ID as a human-readable string. // Implements the fmt.Stringer() interface. var _ fmt.Stringer = (*avpVendorID)(nil) func (v avpVendorID) String() string { if v == vendorIDIetf { return "IETF" } return fmt.Sprintf("Vendor %d", v) } // String represents the AVP data type as a human-readable string. // Implements the fmt.Stringer() interface. var _ fmt.Stringer = (*avpDataType)(nil) func (t avpDataType) String() string { switch t { case avpDataTypeEmpty: return "no data" case avpDataTypeUint16: return "uint16" case avpDataTypeUint32: return "uint32" case avpDataTypeUint64: return "uint64" case avpDataTypeString: return "string" case avpDataTypeBytes: return "byte array" case avpDataTypeResultCode: return "result code" case avpDataTypeMsgID: return "message ID" case avpDataTypeUnimplemented: return "unimplemented AVP data type" case avpDataTypeIllegal: return "illegal AVP" } return "Unrecognised AVP data type" } var _ fmt.Stringer = (*avpHeader)(nil) func (hdr avpHeader) String() string { m := "-" h := "-" var t string if hdr.VendorID == vendorIDIetf { t = hdr.AvpType.String() } else { t = fmt.Sprintf("Vendor %d AVP %d", hdr.VendorID, uint16(hdr.AvpType)) } if hdr.isMandatory() { m = "M" } if hdr.isHidden() { h = "H" } return fmt.Sprintf("%s [%s%s]", t, m, h) } var _ fmt.Stringer = (*avpPayload)(nil) func (p avpPayload) String() string { var str strings.Builder str.WriteString(fmt.Sprintf("(%s) ", p.dataType)) switch p.dataType { case avpDataTypeUint16: v, _ := p.toUint16() str.WriteString(fmt.Sprintf("%d", v)) case avpDataTypeUint32: v, _ := p.toUint32() str.WriteString(fmt.Sprintf("%d", v)) case avpDataTypeUint64: v, _ := p.toUint64() str.WriteString(fmt.Sprintf("%d", v)) case avpDataTypeString: s, _ := p.toString() str.WriteString(s) case avpDataTypeBytes: str.WriteString(fmt.Sprintf("%s", p.data)) case avpDataTypeEmpty, avpDataTypeUnimplemented, avpDataTypeIllegal: str.WriteString("") } return str.String() } func (hdr *avpHeader) isMandatory() bool { return (0x8000 & hdr.FlagLen) == 0x8000 } func (hdr *avpHeader) isHidden() bool { return (0x4000 & hdr.FlagLen) == 0x4000 } func (hdr *avpHeader) totalLen() int { return int(0x3ff & hdr.FlagLen) } func (hdr *avpHeader) dataLen() int { return hdr.totalLen() - avpHeaderLen } func newAvpHeader(isMandatory, isHidden bool, payloadBytes uint, vid avpVendorID, typ avpType) *avpHeader { var flagLen avpFlagLen = 0x0 if isMandatory { flagLen = flagLen ^ 0x8000 } if isHidden { flagLen = flagLen ^ 0x4000 } flagLen = flagLen ^ avpFlagLen(0x3ff&(payloadBytes+avpHeaderLen)) return &avpHeader{ FlagLen: flagLen, VendorID: vid, AvpType: typ, } } // isMandatory returns true if a given AVP is flagged as being mandatory. // The RFCs state that if an unrecognised AVP with the mandatory flag set // is received by an implementation, the implementation MUST terminate the // associated tunnel or session instance. func (avp *avp) isMandatory() bool { return avp.header.isMandatory() } // isHidden returns true if a given AVP has been obscured using the hiding // algorithm described by RFC2661 Section 4.3. func (avp *avp) isHidden() bool { return avp.header.isHidden() } // type returns the type identifier for the AVP. func (avp *avp) getType() avpType { return avp.header.AvpType } // vendorID returns the vendor ID for the AVP. // Standard AVPs per RFC2661 and RFC3931 will use the IETF namespace. // Vendor-specific AVPs will use a per-vendor ID. func (avp *avp) vendorID() avpVendorID { return avp.header.VendorID } // totalLen returns the total number of bytes consumed by the AVP, inclusive // of the AVP header and data payload. func (avp *avp) totalLen() int { return avp.header.totalLen() } func getAVPInfo(avpType avpType, VendorID avpVendorID) (*avpInfo, error) { for _, info := range avpInfoTable { if info.avpType == avpType && info.VendorID == VendorID { return &info, nil } } return nil, errors.New("unrecognised AVP type") } // parseAVPBuffer takes a byte slice of encoded AVP data and parses it // into an array of AVP instances. func parseAVPBuffer(b []byte) (avps []avp, err error) { r := bytes.NewReader(b) for r.Len() >= avpHeaderLen { var h avpHeader var info *avpInfo var cursor int64 // Read the AVP header in if err := binary.Read(r, binary.BigEndian, &h); err != nil { return nil, err } // Look up the AVP info, err := getAVPInfo(h.AvpType, h.VendorID) if err != nil { if h.isMandatory() { return nil, fmt.Errorf("failed to parse mandatory AVP: %v", err) } // RFC2661 section 4.1 says unrecognised AVPs without the // mandatory bit set MUST be ignored continue } // Bounds check the AVP if h.dataLen() > r.Len() { return nil, errors.New("malformed AVP buffer: current AVP length exceeds buffer length") } if cursor, err = r.Seek(0, io.SeekCurrent); err != nil { return nil, errors.New("malformed AVP buffer: unable to determine offset of current AVP") } avps = append(avps, avp{ header: h, payload: avpPayload{ dataType: info.dataType, data: b[cursor : cursor+int64(h.dataLen())], }, }) // Step on to the next AVP in the buffer if _, err := r.Seek(int64(h.dataLen()), io.SeekCurrent); err != nil { return nil, errors.New("malformed AVP buffer: invalid length for current AVP") } } // We must have parsed at least one AVP if len(avps) == 0 { return nil, errors.New("no AVPs present in the input buffer") } return avps, nil } func encodeResultCode(rc *resultCode) ([]byte, error) { encBuf := new(bytes.Buffer) err := binary.Write(encBuf, binary.BigEndian, rc.result) if err != nil { return nil, err } err = binary.Write(encBuf, binary.BigEndian, rc.errCode) if err != nil { return nil, err } if rc.errMsg != "" { err = binary.Write(encBuf, binary.BigEndian, []byte(rc.errMsg)) if err != nil { return nil, err } } return encBuf.Bytes(), nil } func encodePayload(info *avpInfo, value interface{}) ([]byte, error) { var ok bool switch info.dataType { case avpDataTypeEmpty: case avpDataTypeUint16: _, ok = value.(uint16) case avpDataTypeUint32: _, ok = value.(uint32) case avpDataTypeUint64: _, ok = value.(uint64) case avpDataTypeString: var s string s, ok = value.(string) value = []byte(s) case avpDataTypeBytes: _, ok = value.([]byte) case avpDataTypeMsgID: _, ok = value.(avpMsgType) case avpDataTypeResultCode: var rc resultCode rc, ok = value.(resultCode) if ok { return encodeResultCode(&rc) } var rcp *resultCode rcp, ok = value.(*resultCode) if ok { return encodeResultCode(rcp) } case avpDataTypeUnimplemented, avpDataTypeIllegal: return nil, fmt.Errorf("AVP %v is not currently supported", info.avpType) } if !ok { return nil, fmt.Errorf("wrong data type %T passed for %v", value, info.avpType) } encBuf := new(bytes.Buffer) err := binary.Write(encBuf, binary.BigEndian, value) if err != nil { return nil, err } return encBuf.Bytes(), nil } // newAvp builds an AVP containing the specified data func newAvp(vendorID avpVendorID, avpType avpType, value interface{}) (a *avp, err error) { info, err := getAVPInfo(avpType, vendorID) if err != nil { return nil, err } buf, err := encodePayload(info, value) if err != nil { return nil, err } return &avp{ header: *newAvpHeader(info.isMandatory, false, uint(len(buf)), vendorID, avpType), payload: avpPayload{ dataType: info.dataType, data: buf, }, }, nil } // rawData returns the data type for the AVP, along with the raw byte // slice for the data carried by the AVP. func (avp *avp) rawData() (dataType avpDataType, buffer []byte) { return avp.payload.dataType, avp.payload.data } // isDataType returns true if the AVP holds the specified data type. func (avp *avp) isDataType(dt avpDataType) bool { return avp.payload.dataType == dt } func (p *avpPayload) toUint16() (out uint16, err error) { if len(p.data) > 2 { return 0, fmt.Errorf("AVP payload length %v exceeds expected length 2", len(p.data)) } r := bytes.NewReader(p.data) if err = binary.Read(r, binary.BigEndian, &out); err != nil { return 0, err } return out, err } func (p *avpPayload) toUint32() (out uint32, err error) { if len(p.data) > 4 { return 0, fmt.Errorf("AVP payload length %v exceeds expected length 4", len(p.data)) } r := bytes.NewReader(p.data) if err = binary.Read(r, binary.BigEndian, &out); err != nil { return 0, err } return out, err } func (p *avpPayload) toUint64() (out uint64, err error) { if len(p.data) > 8 { return 0, fmt.Errorf("AVP payload length %v exceeds expected length 8", len(p.data)) } r := bytes.NewReader(p.data) if err = binary.Read(r, binary.BigEndian, &out); err != nil { return 0, err } return out, err } func (p *avpPayload) toString() (out string, err error) { return string(p.data), nil } func (p *avpPayload) toResultCode() (out resultCode, err error) { var resCode, errCode uint16 var errMsg string r := bytes.NewReader(p.data) if err = binary.Read(r, binary.BigEndian, &resCode); err != nil { return resultCode{}, err } if r.Len() > 0 { if err = binary.Read(r, binary.BigEndian, &errCode); err != nil { return resultCode{}, err } if r.Len() > 0 { errMsg = string(p.data[4:]) } } return resultCode{ result: avpResultCode(resCode), errCode: avpErrorCode(errCode), errMsg: errMsg, }, nil } // decode decodes an AVP based on its data type. // An error is returned if the AVP cannot be decoded successfully. func (avp *avp) decode() (interface{}, error) { switch avp.payload.dataType { case avpDataTypeEmpty: return nil, nil case avpDataTypeUint16: return avp.payload.toUint16() case avpDataTypeUint32: return avp.payload.toUint32() case avpDataTypeUint64: return avp.payload.toUint64() case avpDataTypeString: return avp.payload.toString() case avpDataTypeBytes: return avp.payload.data, nil case avpDataTypeResultCode: return avp.payload.toResultCode() case avpDataTypeMsgID: v, err := avp.payload.toUint16() if err != nil { return nil, err } return avpMsgType(v), nil } return nil, fmt.Errorf("unhandled AVP data type") } // decodeUint16Data decodes an AVP holding a uint16 value. // It is an error to call this function on an AVP which doesn't // contain a uint16 payload. func (avp *avp) decodeUint16Data() (value uint16, err error) { if !avp.isDataType(avpDataTypeUint16) { return 0, errors.New("AVP data is not of type uint16, cannot decode") } return avp.payload.toUint16() } // decodeUint32Data decodes an AVP holding a uint32 value. // It is an error to call this function on an AVP which doesn't // contain a uint32 payload. func (avp *avp) decodeUint32Data() (value uint32, err error) { if !avp.isDataType(avpDataTypeUint32) { return 0, errors.New("AVP data is not of type uint32, cannot decode") } return avp.payload.toUint32() } // decodeUint64Data decodes an AVP holding a uint64 value. // It is an error to call this function on an AVP which doesn't // contain a uint64 payload. func (avp *avp) decodeUint64Data() (value uint64, err error) { if !avp.isDataType(avpDataTypeUint64) { return 0, errors.New("AVP data is not of type uint64, cannot decode") } return avp.payload.toUint64() } // decodeStringData decodes an AVP holding a string value. // It is an error to call this function on an AVP which doesn't // contain a string payload. func (avp *avp) decodeStringData() (value string, err error) { if !avp.isDataType(avpDataTypeString) { return "", errors.New("AVP data is not of type string, cannot decode") } return avp.payload.toString() } // decodeResultCode decodes an AVP holding a RFC2661/RFC3931 Result Code. // It is an error to call this function on an AVP which doesn't contain // a result code payload. func (avp *avp) decodeResultCode() (value resultCode, err error) { if !avp.isDataType(avpDataTypeResultCode) { return resultCode{}, errors.New("AVP is not of type result code, cannot decode") } return avp.payload.toResultCode() } // decodeMsgType decodes an AVP holding a message type ID. // It is an error to call this function on an AVP which doesn't contain // a message ID payload. func (avp *avp) decodeMsgType() (value avpMsgType, err error) { if !avp.isDataType(avpDataTypeMsgID) { return avpMsgTypeIllegal, errors.New("AVP is not of type message ID, cannot decode") } out, err := avp.payload.toUint16() return avpMsgType(out), err } // avpsLengthBytes returns the length of a slice of AVPs in bytes func avpsLengthBytes(avps []avp) int { var nb int for _, avp := range avps { nb += avp.totalLen() } return nb } // findAvp looks up a specific AVP in a slice of AVPs // An error will be returned if the requested AVP isn't present in the slice. func findAvp(avps []avp, vendorID avpVendorID, typ avpType) (*avp, error) { for _, a := range avps { if a.vendorID() == vendorID && a.getType() == typ { return &a, nil } } return nil, fmt.Errorf("AVP %v %v not found", vendorID, typ) } // findUint16Avp looks up a specific AVP in a slice of AVPs and decodes as uint16. // An error will be returned if the AVP isn't present or is of the wrong type. func findUint16Avp(avps []avp, vendorID avpVendorID, typ avpType) (uint16, error) { avp, err := findAvp(avps, vendorID, typ) if err != nil { return 0, err } val, err := avp.decodeUint16Data() if err != nil { return 0, fmt.Errorf("failed to decode %v: %v", typ, err) } return val, nil } // findUint32Avp looks up a specific AVP in a slice of AVPs and decodes as uint32. // An error will be returned if the AVP isn't present or is of the wrong type. func findUint32Avp(avps []avp, vendorID avpVendorID, typ avpType) (uint32, error) { avp, err := findAvp(avps, vendorID, typ) if err != nil { return 0, err } val, err := avp.decodeUint32Data() if err != nil { return 0, fmt.Errorf("failed to decode %v: %v", typ, err) } return val, nil } // findUint64Avp looks up a specific AVP in a slice of AVPs and decodes as uint64. // An error will be returned if the AVP isn't present or is of the wrong type. func findUint64Avp(avps []avp, vendorID avpVendorID, typ avpType) (uint64, error) { avp, err := findAvp(avps, vendorID, typ) if err != nil { return 0, err } val, err := avp.decodeUint64Data() if err != nil { return 0, fmt.Errorf("failed to decode %v: %v", typ, err) } return val, nil } // findBytesAvp looks up a specific AVP in a slice of AVPs and decodes as a byte slice. // An error will be returned if the AVP isn't present or is of the wrong type. func findBytesAvp(avps []avp, vendorID avpVendorID, typ avpType) ([]byte, error) { avp, err := findAvp(avps, vendorID, typ) if err != nil { return nil, err } return avp.payload.data, nil } // findStringAvp looks up a specific AVP in a slice of AVPs and decodes as a string. // An error will be returned if the AVP isn't present or is of the wrong type. func findStringAvp(avps []avp, vendorID avpVendorID, typ avpType) (string, error) { avp, err := findAvp(avps, vendorID, typ) if err != nil { return "", err } val, err := avp.decodeStringData() if err != nil { return "", fmt.Errorf("failed to decode %v: %v", typ, err) } return val, nil } // findResultCodeAvp looks up a specific AVP in a slice of AVPs and decodes as a result code. // An error will be returned if the AVP isn't present or is of the wrong type. func findResultCodeAvp(avps []avp, vendorID avpVendorID, typ avpType) (*resultCode, error) { avp, err := findAvp(avps, vendorID, typ) if err != nil { return nil, err } val, err := avp.decodeResultCode() if err != nil { return nil, fmt.Errorf("failed to decode %v: %v", typ, err) } return &val, nil } golang-github-katalix-go-l2tp-0.1.7/l2tp/avp_test.go000066400000000000000000000455641456471064000222450ustar00rootroot00000000000000package l2tp import ( "bytes" "fmt" "reflect" "testing" ) func TestParseAVPBufferGood(t *testing.T) { cases := []struct { in []byte want []avp }{ { in: []byte{0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06}, // message type want: []avp{ avp{ header: avpHeader{FlagLen: 0x8008, VendorID: 0, AvpType: avpTypeMessage}, payload: avpPayload{dataType: avpDataTypeMsgID, data: []byte{0x00, 0x06}}, }, }, }, { in: []byte{ 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // message type 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, // protocol version 0x80, 0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, // framing cap 0x00, 0x34, 0x00, 0x00, 0x00, 0x08, 0x70, 0x72, 0x6f, 0x6c, 0x32, 0x74, 0x70, 0x20, 0x31, 0x2e, 0x37, 0x2e, 0x33, 0x20, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x2d, 0x33, 0x2e, 0x31, 0x33, 0x2e, 0x30, 0x2d, 0x37, 0x31, 0x2d, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x20, 0x28, 0x78, 0x38, 0x36, 0x5f, 0x36, 0x34, 0x29, /* vendor-name AVP */ }, want: []avp{ avp{ header: avpHeader{FlagLen: 0x8008, VendorID: 0, AvpType: avpTypeMessage}, payload: avpPayload{dataType: avpDataTypeMsgID, data: []byte{0x00, 0x01}}, }, avp{ header: avpHeader{FlagLen: 0x0008, VendorID: 0, AvpType: avpTypeProtocolVersion}, payload: avpPayload{dataType: avpDataTypeBytes, data: []byte{0x01, 0x00}}, }, avp{ header: avpHeader{FlagLen: 0x800a, VendorID: 0, AvpType: avpTypeFramingCap}, payload: avpPayload{dataType: avpDataTypeUint32, data: []byte{0x00, 0x00, 0x00, 0x03}}, }, avp{ header: avpHeader{FlagLen: 0x0034, VendorID: 0, AvpType: avpTypeVendorName}, payload: avpPayload{dataType: avpDataTypeString, data: []byte{ 0x70, 0x72, 0x6f, 0x6c, 0x32, 0x74, 0x70, 0x20, 0x31, 0x2e, 0x37, 0x2e, 0x33, 0x20, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x2d, 0x33, 0x2e, 0x31, 0x33, 0x2e, 0x30, 0x2d, 0x37, 0x31, 0x2d, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x20, 0x28, 0x78, 0x38, 0x36, 0x5f, 0x36, 0x34, 0x29, }, }, }, }, }, { in: []byte{ 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, // message type 0x80, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, // result code 0x80, 0x08, 0x00, 0x00, 0x00, 0x09, 0x5f, 0x2b, // assigned tunnel id }, want: []avp{ avp{ header: avpHeader{FlagLen: 0x8008, VendorID: 0, AvpType: avpTypeMessage}, payload: avpPayload{dataType: avpDataTypeMsgID, data: []byte{0x00, 0x04}}, }, avp{ header: avpHeader{FlagLen: 0x8008, VendorID: 0, AvpType: avpTypeResultCode}, payload: avpPayload{dataType: avpDataTypeResultCode, data: []byte{0x00, 0x01}}, }, avp{ header: avpHeader{FlagLen: 0x8008, VendorID: 0, AvpType: avpTypeTunnelID}, payload: avpPayload{dataType: avpDataTypeUint16, data: []byte{0x5f, 0x2b}}, }, }, }, } for _, c := range cases { got, err := parseAVPBuffer(c.in) if err == nil { if !reflect.DeepEqual(got, c.want) { t.Errorf("parseAVPBuffer() == %q; want %q", got, c.want) } } else { t.Errorf("parseAVPBuffer(%q) failed: %q", c.in, err) } } } func TestParseAVPBufferBad(t *testing.T) { cases := []struct { in []byte }{ { in: []byte{}, // no avp data }, { in: []byte{0x1, 0x2, 0x3, 0x4}, // short avp data }, { in: []byte{0x80, 0x08, 0x01, 0xef, 0x00, 0x00, 0x00, 0x06}, // mandatory vendor AVP }, } for _, c := range cases { avps, err := parseAVPBuffer(c.in) if err == nil { t.Errorf("parseAVPBuffer(%q): expected error, but did not get one", c.in) } if len(avps) != 0 { t.Errorf("parseAVPBuffer(%q): expect zero-length AVP buffer output, but didn't get it", c.in) } } } type avpMetadata struct { mandatory, hidden bool typ avpType vid avpVendorID dtyp avpDataType nbytes int } func (md avpMetadata) String() string { return fmt.Sprintf("%s %s mandatory: %v, hidden: %v, dtyp: %v, len: %v", md.vid, md.typ, md.mandatory, md.hidden, md.dtyp, md.nbytes) } func TestAVPMetadata(t *testing.T) { cases := []struct { in []byte want []avpMetadata }{ { in: []byte{0x80, 0x0c, 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x65, 0x6e, 0x76, 0x33}, // hostname AVP want: []avpMetadata{ avpMetadata{mandatory: true, hidden: false, typ: avpTypeHostName, vid: vendorIDIetf, dtyp: avpDataTypeString, nbytes: 6}, }, }, { in: []byte{0x80, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0a}, // receive window size want: []avpMetadata{ avpMetadata{mandatory: true, hidden: false, typ: avpTypeRxWindowSize, vid: vendorIDIetf, dtyp: avpDataTypeUint16, nbytes: 2}, }, }, } for _, c := range cases { got, err := parseAVPBuffer(c.in) if err == nil { for i, gi := range got { dtyp, buf := gi.rawData() gotmd := avpMetadata{ mandatory: gi.isMandatory(), hidden: gi.isHidden(), typ: gi.getType(), vid: gi.vendorID(), dtyp: dtyp, nbytes: len(buf), } if !reflect.DeepEqual(gotmd, c.want[i]) { t.Errorf("metadata == %s; want %s", gotmd, c.want[i]) } } } else { t.Errorf("parseAVPBuffer(%q) failed: %q", c.in, err) } } } func TestAVPDecodeUint16(t *testing.T) { cases := []struct { in []byte wantVal uint16 wantType avpType }{ { in: []byte{0x80, 0x08, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00}, wantVal: 0, wantType: avpTypeSessionID, }, { in: []byte{0x80, 0x08, 0x00, 0x00, 0x00, 0x09, 0x5f, 0x2b}, wantVal: 24363, wantType: avpTypeTunnelID, }, } for _, c := range cases { got, err := parseAVPBuffer(c.in) if err == nil { if c.wantType != got[0].getType() { t.Errorf("Wanted type %q, got %q", c.wantType, got[0].getType()) } if val, err := got[0].decodeUint16Data(); err == nil { if val != c.wantVal { t.Errorf("Wanted value %q, got %q", c.wantVal, val) } } } else { t.Errorf("parseAVPBuffer(%q) failed: %q", c.in, err) } } } func TestAVPDecodeUint32(t *testing.T) { cases := []struct { in []byte wantVal uint32 wantType avpType }{ { in: []byte{0x00, 0x0a, 0x00, 0x00, 0x00, 0x3d, 0x28, 0x46, 0xf1, 0x81}, wantVal: 675737985, wantType: avpTypeAssignedConnID, }, { in: []byte{0x00, 0x0a, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00}, wantVal: 0, wantType: avpTypeRouterID, }, { in: []byte{0x80, 0x0a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03}, wantVal: 3, wantType: avpTypeBearerCap, }, } for _, c := range cases { got, err := parseAVPBuffer(c.in) if err == nil { if c.wantType != got[0].getType() { t.Errorf("Wanted type %q, got %q", c.wantType, got[0].getType()) } if val, err := got[0].decodeUint32Data(); err == nil { if val != c.wantVal { t.Errorf("Wanted value %q, got %q", c.wantVal, val) } } } else { t.Errorf("parseAVPBuffer(%q) failed: %q", c.in, err) } } } func TestAVPDecodeUint64(t *testing.T) { cases := []struct { in []byte wantVal uint64 wantType avpType }{ { in: []byte{0x00, 0x0e, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x9a, 0xca, 0x00}, wantVal: 1000000000, wantType: avpTypeRxConnectSpeedBps, }, } for _, c := range cases { got, err := parseAVPBuffer(c.in) if err == nil { if c.wantType != got[0].getType() { t.Errorf("Wanted type %q, got %q", c.wantType, got[0].getType()) } if val, err := got[0].decodeUint64Data(); err == nil { if val != c.wantVal { t.Errorf("Wanted value %q, got %q", c.wantVal, val) } } } else { t.Errorf("parseAVPBuffer(%q) failed: %q", c.in, err) } } } func TestAVPDecodeString(t *testing.T) { cases := []struct { in []byte wantVal string wantType avpType }{ { in: []byte{0x80, 0x0c, 0x00, 0x00, 0x00, 0x07, 0x77, 0x68, 0x6f, 0x6f, 0x73, 0x68}, wantVal: "whoosh", wantType: avpTypeHostName, }, { in: []byte{ 0x00, 0x34, 0x00, 0x00, 0x00, 0x08, 0x70, 0x72, 0x6f, 0x6c, 0x32, 0x74, 0x70, 0x20, 0x31, 0x2e, 0x38, 0x2e, 0x32, 0x20, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x2d, 0x33, 0x2e, 0x31, 0x33, 0x2e, 0x30, 0x2d, 0x38, 0x35, 0x2d, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x20, 0x28, 0x78, 0x38, 0x36, 0x5f, 0x36, 0x34, 0x29, }, wantVal: "prol2tp 1.8.2 Linux-3.13.0-85-generic (x86_64)", wantType: avpTypeVendorName, }, } for _, c := range cases { got, err := parseAVPBuffer(c.in) if err == nil { if c.wantType != got[0].getType() { t.Errorf("Wanted type %q, got %q", c.wantType, got[0].getType()) } if val, err := got[0].decodeStringData(); err == nil { if val != c.wantVal { t.Errorf("Wanted value %q, got %q", c.wantVal, val) } } } else { t.Errorf("parseAVPBuffer(%q) failed: %q", c.in, err) } } } func TestAVPDecodeResultCode(t *testing.T) { cases := []struct { in []byte wantVal resultCode wantType avpType }{ { in: []byte{0x80, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01}, wantVal: resultCode{ result: avpStopCCNResultCodeClearConnection, errCode: avpErrorCodeNoError, errMsg: "", }, wantType: avpTypeResultCode, }, { in: []byte{ 0x80, 0x1a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x41, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74}, wantVal: resultCode{ result: avpStopCCNResultCodeGeneralError, errCode: avpErrorCodeBadValue, errMsg: "Invalid Argument", }, wantType: avpTypeResultCode, }, } for _, c := range cases { got, err := parseAVPBuffer(c.in) if err == nil { if c.wantType != got[0].getType() { t.Errorf("Wanted type %q, got %q", c.wantType, got[0].getType()) } if val, err := got[0].decodeResultCode(); err == nil { if val != c.wantVal { t.Errorf("Wanted value %q, got %q", c.wantVal, val) } } } else { t.Errorf("parseAVPBuffer(%q) failed: %q", c.in, err) } } } func TestAVPDecodeMsgID(t *testing.T) { cases := []struct { in []byte wantVal avpMsgType wantType avpType }{ { in: []byte{0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, wantVal: avpMsgTypeSccrq, wantType: avpTypeMessage, }, { in: []byte{0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, wantVal: avpMsgTypeScccn, wantType: avpTypeMessage, }, { in: []byte{0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14}, wantVal: avpMsgTypeAck, wantType: avpTypeMessage, }, } for _, c := range cases { got, err := parseAVPBuffer(c.in) if err == nil { if c.wantType != got[0].getType() { t.Errorf("Wanted type %q, got %q", c.wantType, got[0].getType()) } if val, err := got[0].decodeMsgType(); err == nil { if val != c.wantVal { t.Errorf("Wanted value %q, got %q", c.wantVal, val) } } } else { t.Errorf("parseAVPBuffer(%q) failed: %q", c.in, err) } } } func TestEncodeUint16(t *testing.T) { cases := []struct { vendorID avpVendorID avpType avpType value interface{} }{ {vendorID: vendorIDIetf, avpType: avpTypeTunnelID, value: uint16(9010)}, {vendorID: vendorIDIetf, avpType: avpTypeSessionID, value: uint16(59182)}, {vendorID: vendorIDIetf, avpType: avpTypeRxWindowSize, value: uint16(5)}, } for _, c := range cases { if avp, err := newAvp(c.vendorID, c.avpType, c.value); err == nil { if !avp.isDataType(avpDataTypeUint16) { t.Errorf("Data type check failed") } if val, err := avp.decodeUint16Data(); err == nil { if val != c.value { t.Errorf("encode/decode failed: expected %q, got %q", c.value, val) } } else { t.Errorf("DecodeUint16Data() failed: %q", err) } } else { t.Errorf("newAvp(%v, %v, %v) failed: %q", c.vendorID, c.avpType, c.value, err) } } } func TestEncodeUint32(t *testing.T) { cases := []struct { vendorID avpVendorID avpType avpType value interface{} }{ {vendorID: vendorIDIetf, avpType: avpTypeFramingCap, value: uint32(3)}, {vendorID: vendorIDIetf, avpType: avpTypePhysicalChannelID, value: uint32(12398713)}, } for _, c := range cases { if avp, err := newAvp(c.vendorID, c.avpType, c.value); err == nil { if !avp.isDataType(avpDataTypeUint32) { t.Errorf("Data type check failed") } if val, err := avp.decodeUint32Data(); err == nil { if val != c.value { t.Errorf("encode/decode failed: expected %q, got %q", c.value, val) } } else { t.Errorf("DecodeUint32Data() failed: %q", err) } } else { t.Errorf("newAvp(%v, %v, %v) failed: %q", c.vendorID, c.avpType, c.value, err) } } } func TestEncodeUint64(t *testing.T) { cases := []struct { vendorID avpVendorID avpType avpType value interface{} }{ {vendorID: vendorIDIetf, avpType: avpTypeTxConnectSpeedBps, value: uint64(10 * 1024 * 1024 * 1024)}, {vendorID: vendorIDIetf, avpType: avpTypeRxConnectSpeedBps, value: uint64(1 * 1024 * 1024 * 0124)}, } for _, c := range cases { if avp, err := newAvp(c.vendorID, c.avpType, c.value); err == nil { if !avp.isDataType(avpDataTypeUint64) { t.Errorf("Data type check failed") } if val, err := avp.decodeUint64Data(); err == nil { if val != c.value { t.Errorf("encode/decode failed: expected %q, got %q", c.value, val) } } else { t.Errorf("DecodeUint64Data() failed: %q", err) } } else { t.Errorf("newAvp(%v, %v, %v) failed: %q", c.vendorID, c.avpType, c.value, err) } } } func TestEncodeString(t *testing.T) { cases := []struct { vendorID avpVendorID avpType avpType value interface{} }{ {vendorID: vendorIDIetf, avpType: avpTypeHostName, value: string("blackhole.local")}, {vendorID: vendorIDIetf, avpType: avpTypeVendorName, value: string("Katalix Systems Ltd.")}, } for _, c := range cases { if avp, err := newAvp(c.vendorID, c.avpType, c.value); err == nil { if !avp.isDataType(avpDataTypeString) { t.Errorf("Data type check failed") } if val, err := avp.decodeStringData(); err == nil { if val != c.value { t.Errorf("encode/decode failed: expected %q, got %q", c.value, val) } } else { t.Errorf("DecodeStringData() failed: %q", err) } } else { t.Errorf("newAvp(%v, %v, %v) failed: %q", c.vendorID, c.avpType, c.value, err) } } } func TestEncodeBytes(t *testing.T) { cases := []struct { vendorID avpVendorID avpType avpType value []byte }{ {vendorID: vendorIDIetf, avpType: avpTypeTiebreaker, value: []byte{0xef, 0x10, 0x34, 0x73, 0xb2, 0x8b, 0x91, 0xdd}}, } for _, c := range cases { if avp, err := newAvp(c.vendorID, c.avpType, c.value); err == nil { if !avp.isDataType(avpDataTypeBytes) { t.Errorf("Data type check failed") } val := avp.payload.data if !bytes.Equal(val, c.value) { t.Errorf("encode/decode failed: expected %q, got %q", c.value, val) } } else { t.Errorf("newAvp(%v, %v, %v) failed: %q", c.vendorID, c.avpType, c.value, err) } } } func TestEncodeResultCode(t *testing.T) { cases := []struct { vendorID avpVendorID avpType avpType value resultCode }{ { vendorID: vendorIDIetf, avpType: avpTypeResultCode, value: resultCode{ result: avpStopCCNResultCodeClearConnection, errCode: avpErrorCodeNoError, errMsg: "", }, }, { vendorID: vendorIDIetf, avpType: avpTypeResultCode, value: resultCode{ result: avpStopCCNResultCodeGeneralError, errCode: avpErrorCodeTryAnother, errMsg: "", }, }, { vendorID: vendorIDIetf, avpType: avpTypeResultCode, value: resultCode{ result: avpStopCCNResultCodeGeneralError, errCode: avpErrorCodeVendorSpecificError, errMsg: "Out of cheese error", }, }, } for _, c := range cases { if avp, err := newAvp(c.vendorID, c.avpType, c.value); err == nil { if !avp.isDataType(avpDataTypeResultCode) { t.Errorf("Data type check failed") } if val, err := avp.decodeResultCode(); err == nil { if !reflect.DeepEqual(val, c.value) { t.Errorf("encode/decode failed: expected %q, got %q", c.value, val) } } else { t.Errorf("DecodeResultCodeData() failed: %q", err) } } else { t.Errorf("newAvp(%v, %v, %v) failed: %q", c.vendorID, c.avpType, c.value, err) } } } func TestFind(t *testing.T) { cases := []struct { in []byte find []func([]avp) error notFind []func([]avp) error }{ { in: []byte{ 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // message type 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, // protocol version 0x80, 0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, // framing cap 0x00, 0x34, 0x00, 0x00, 0x00, 0x08, 0x70, 0x72, 0x6f, 0x6c, 0x32, 0x74, 0x70, 0x20, 0x31, 0x2e, 0x37, 0x2e, 0x33, 0x20, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x2d, 0x33, 0x2e, 0x31, 0x33, 0x2e, 0x30, 0x2d, 0x37, 0x31, 0x2d, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x20, 0x28, 0x78, 0x38, 0x36, 0x5f, 0x36, 0x34, 0x29, /* vendor-name AVP */ }, find: []func([]avp) error{ func(avps []avp) (err error) { // Don't have a specific finder/converter for Message Type // so just validate we can find the AVP without trying to decode _, err = findAvp(avps, vendorIDIetf, avpTypeMessage) return }, func(avps []avp) (err error) { _, err = findBytesAvp(avps, vendorIDIetf, avpTypeProtocolVersion) return }, func(avps []avp) (err error) { _, err = findUint32Avp(avps, vendorIDIetf, avpTypeFramingCap) return }, func(avps []avp) (err error) { _, err = findStringAvp(avps, vendorIDIetf, avpTypeVendorName) return }, }, notFind: []func([]avp) error{ func(avps []avp) (err error) { _, err = findUint16Avp(avps, vendorIDIetf, avpTypeFirmwareRevision) return }, func(avps []avp) (err error) { _, err = findUint32Avp(avps, vendorIDIetf, avpTypeMinimumBps) return }, func(avps []avp) (err error) { _, err = findBytesAvp(avps, vendorIDIetf, avpTypeTiebreaker) return }, func(avps []avp) (err error) { _, err = findStringAvp(avps, vendorIDIetf, avpTypeHostName) return }, }, }, } for _, c := range cases { avps, err := parseAVPBuffer(c.in) if err != nil { t.Fatalf("parseAVPBuffer(%q): %v", c.in, err) } for i, chk := range c.find { err = chk(avps) if err != nil { t.Errorf("find check %v failed: %v", i, err) } } for i, chk := range c.notFind { err = chk(avps) if err == nil { t.Errorf("notFind check %v succeeded", i) } } } } func TestAvpTypeStringer(t *testing.T) { for i := avpTypeMessage; i < avpTypeMax; i++ { s := i.String() if len(s) == 0 { t.Errorf("avpType stringer returned empty string for value %d", uint16(i)) } } } func TestAvpMsgTypeStringer(t *testing.T) { for i := avpMsgTypeIllegal; i < avpMsgTypeMax; i++ { s := i.String() if len(s) == 0 { t.Errorf("avpMsgType stringer returned empty string for value %d", uint16(i)) } } } func TestAvpDataTypeStringer(t *testing.T) { for i := avpDataTypeEmpty; i < avpDataTypeMax; i++ { s := i.String() if len(s) == 0 { t.Errorf("avpDataType stringer returned empty string for value %d", uint16(i)) } } } golang-github-katalix-go-l2tp-0.1.7/l2tp/const.go000066400000000000000000000226541456471064000215410ustar00rootroot00000000000000package l2tp import ( "github.com/katalix/go-l2tp/internal/nll2tp" "time" ) // ProtocolVersion is the version of the L2TP protocol to use type ProtocolVersion int const ( // ProtocolVersion3Fallback is used for RFC3931 fallback mode ProtocolVersion3Fallback = 1 // ProtocolVersion2 is used for RFC2661 ProtocolVersion2 = nll2tp.ProtocolVersion2 // ProtocolVersion3 is used for RFC3931 ProtocolVersion3 = nll2tp.ProtocolVersion3 ) // ControlConnID is a generic identifier used for RFC2661 tunnel // and session IDs as well as RFC3931 control connection IDs. type ControlConnID uint32 const ( v2TidSidMax = ControlConnID(^uint16(0)) ) // EncapType is the lower-level encapsulation to use for a tunnel type EncapType int const ( // EncapTypeUDP is used for RFC2661 and RFC3931 tunnels using UDP encapsulation EncapTypeUDP = nll2tp.EncaptypeUdp // EncapTypeIP is used for RFC3931 tunnels using IP encapsulation EncapTypeIP = nll2tp.EncaptypeIp ) func (e EncapType) String() string { switch e { case EncapTypeIP: return "IP" case EncapTypeUDP: return "UDP" } panic("unhandled encap type") } // FramingCapability describes the type of framing which a peer supports. // It should be specified as a bitwise OR of FramingCap* values. type FramingCapability uint32 const ( // FramingCapSync indicates synchronous framing is supported FramingCapSync = 0x1 // FramingCapAsync indicates asynchronous framing is supported FramingCapAsync = 0x2 ) // PseudowireType is the session type for a given session. // RFC2661 is PPP-only; whereas RFC3931 supports multiple types. type PseudowireType int const ( // PseudowireTypePPP specifies a PPP pseudowire PseudowireTypePPP = nll2tp.PwtypePpp // PseudowireTypeEth specifies an Ethernet pseudowire PseudowireTypeEth = nll2tp.PwtypeEth // PseudowireTypePPPAC specifies an Access Concentrator PPP pseudowire PseudowireTypePPPAC = nll2tp.PwtypePppAc ) // DebugFlags is used for kernel-space tunnel and session logging control. // Logging is emitted using the kernel's printk facility, and may be viewed // using dmesg, syslog, or the systemd journal depending on distro configuration. // Multiple flags may be combined to enable different log messages. type DebugFlags uint32 const ( // DebugFlagsControl enables logging of userspace/kernelspace API interactions DebugFlagsControl = nll2tp.MsgControl // DebugFlagsSeq enables logging of data sequence numbers if enabled for a given session DebugFlagsSeq = nll2tp.MsgSeq // DebugFlagsData enables logging of session data messages DebugFlagsData = nll2tp.MsgData ) // L2SpecType defines the Layer 2 specific sublayer for data packets as per RFC3931 section 3.2.2. type L2SpecType int32 const ( // L2SpecTypeNone defines no sublayer is to be used L2SpecTypeNone = nll2tp.L2spectypeNone // L2SpecTypeDefault defines use of the default sublayer L2SpecTypeDefault = nll2tp.L2spectypeDefault ) // TunnelType define the runtime behaviour of a tunnel instance. type TunnelType int const ( // TunnelTypeDynamic runs the L2TPv2 (RFC2661) or L2TPv3 (RFC3931) control // protocol to instantiate the tunnel instance. TunnelTypeDynamic = iota // TunnelTypeAcquiescent runs a minimal tunnel control protocol transport // which will ACK control messages and optionally send periodic HELLO messages. TunnelTypeAcquiescent // TunnelTypeStatic runs no control protocol, and instantiates the data plane // only. TunnelTypeStatic ) // TunnelConfig encapsulates tunnel configuration for a single // connection between two L2TP hosts. Each tunnel may contain // multiple sessions. // // Not every configuration option applies to all tunnel types. // Refer to the documentation for specific tunnel creation functions // for more information. type TunnelConfig struct { // The local address that the tunnel should bind its socket to. // This must be specified for static and quiescent tunnels. // For dynamic tunnels this can be left blank and the kernel // will autobind the socket when connecting to the peer. Local string // The address of the L2TP peer to connect to. Peer string // The encapsulation type to be used by the tunnel instance. // L2TPv2 tunnels support UDP encapsulation only. Encap EncapType // The version of the L2TP protocol to use for the tunnel. Version ProtocolVersion // The local tunnel ID for the tunnel instance. Tunnel // IDs must be unique to the host, and must be non-zero. // The tunnel ID must be specified for static and quiescent tunnels. // For dynamic tunnels the tunnel ID can set to zero and // the L2TP context will autogenerate an ID. TunnelID ControlConnID // The peer's tunnel ID for the tunnel instance. // The peer's ID must be specified for static and quiescent tunnels. PeerTunnelID ControlConnID // The initial window size to use for the L2TP reliable transport // algorithm. The window size dictates how many control messages the // tunnel may have "in flight" (i.e. pending an ACK from the peer) // at any one time. // Tuning the window size can allow high-volume L2TP servers // to improve performance. Generally it won't be necessary to change // this from the default value of 4. WindowSize uint16 // The amount of time to wait on receipt of a StopCCN message to allow // and retransmissions to be acknowledged. // The default is 31s per RFC2661 section 5.7. StopCCNTimeout time.Duration // The hello timeout, if set, enables transmission of L2TP keep-alive // (HELLO) messages. // A hello message is sent N milliseconds after the last control // message was sent or received. It allows for early detection of // tunnel failure on quiet connections. // By default no keep-alive messages are sent. HelloTimeout time.Duration // The retry timeout specifies the starting retry timeout for the // reliable transport algorithm used for L2TP control messages. // The algorithm uses an exponential backoff when retrying messages. // By default a starting retry timeout of 1000ms is used. RetryTimeout time.Duration // MaxRetries sets how many times a given control message may be // retried before the transport considers the message transmission to // have failed. // It may be useful to tune this value on unreliable network connections // to avoid suprious tunnel failure, or conversely to allow for quicker // tunnel failure detection on reliable links. // The default is 3 retries. MaxRetries uint // HostName sets the host name the tunnel will advertise in the // Host Name AVP per RFC2661. // If unset the host's name will be queried and the returned value used. HostName string // FramingCaps sets the framing capabilities the tunnel will advertise // in the Framing Capabilities AVP per RFC2661. // The default is to advertise both sync and async framing. FramingCaps FramingCapability } // SessionConfig encapsulates session configuration for a pseudowire // connection within a tunnel between two L2TP hosts. type SessionConfig struct { // SessionID specifies the local session ID of the session. // Session IDs must be unique to the tunnel for L2TPv2, or unique to // the peer for L2TPv3. // The session ID must be specified for sessions in static or // quiescent tunnels. SessionID ControlConnID // PeerSessionID specifies the peer's session ID for the session. // The peer session ID must be specified for sessions in static or // quiescent tunnels. PeerSessionID ControlConnID // Pseudowire specifies the type of layer 2 frames carried by the session. // L2TPv2 tunnels support PPP pseudowires only. Pseudowire PseudowireType // SeqNum, if set, enables the transmission of sequence numbers with // L2TP data messages. Use of sequence numbers enables the data plane // to reorder data packets to ensure they are delivered in sequence. // By default sequence numbers are not used. SeqNum bool // ReorderTimeout, if set, specifies the length of time to queue out // of sequence data packets before discarding them. // This parameter is not currently implemented and should not be used. ReorderTimeout time.Duration // Cookie, if set, specifies the local L2TPv3 cookie for the session. // Cookies are a data verification mechanism intended to allow misdirected // data packets to be detected and rejected. // Transmitted data packets will include the local cookie in their header. // Cookies may be either 4 or 8 bytes long, and contain aribrary data. // By default no local cookie is set. Cookie []byte // PeerCookie, if set, specifies the L2TPv3 cookie the peer will send in // the header of its data messages. // Messages received without the peer's cookie (or with the wrong cookie) // will be rejected. // By default no peer cookie is set. PeerCookie []byte // InterfaceName, if set, specifies the network interface name to be // used for the session instance. // Setting the interface name can be useful when you need to be certain // of the interface name a given session will use. // By default the Linux kernel autogenerates an interface name specific to // the pseudowire type, e.g. "l2tpeth0", "ppp0". InterfaceName string // L2SpecType specifies the L2TPv3 Layer 2 specific sublayer field to // be used in data packet headers as per RFC3931 section 3.2.2. // By default no Layer 2 specific sublayer is used. L2SpecType L2SpecType // PPPoESessionId specifies the assigned PPPoE ID of the session. // This parameter applies to PseudowireTypePPPAC only. PPPoESessionId uint16 // PPPoEPeerMac specifies the MAC address of the PPPoE peer. // This parameter applies to PseudowireTypePPPAC only. PPPoEPeerMac [6]byte } golang-github-katalix-go-l2tp-0.1.7/l2tp/controlplane.go000066400000000000000000000060521456471064000231050ustar00rootroot00000000000000package l2tp import ( "fmt" "os" "syscall" "golang.org/x/sys/unix" ) type controlPlane struct { local, remote unix.Sockaddr fd int file *os.File rc syscall.RawConn connected bool } func (cp *controlPlane) recvFrom(p []byte) (n int, addr unix.Sockaddr, err error) { cerr := cp.rc.Read(func(fd uintptr) bool { n, addr, err = unix.Recvfrom(int(fd), p, unix.MSG_NOSIGNAL) return err != unix.EAGAIN && err != unix.EWOULDBLOCK }) if err != nil { return n, addr, err } return n, addr, cerr } func (cp *controlPlane) write(b []byte) (n int, err error) { if cp.connected { return cp.file.Write(b) } return cp.writeTo(b, cp.remote) } func (cp *controlPlane) writeTo(p []byte, addr unix.Sockaddr) (n int, err error) { return len(p), cp.sendto(p, addr) } func (cp *controlPlane) sendto(p []byte, to unix.Sockaddr) (err error) { cerr := cp.rc.Write(func(fd uintptr) bool { err = unix.Sendto(int(fd), p, unix.MSG_NOSIGNAL, to) return err != unix.EAGAIN && err != unix.EWOULDBLOCK }) if err != nil { return err } return cerr } func (cp *controlPlane) close() (err error) { if cp.file != nil { err = cp.file.Close() cp.file = nil } return } func (cp *controlPlane) connect() error { err := unix.Connect(cp.fd, cp.remote) if err == nil { cp.connected = true } return err } func (cp *controlPlane) connectTo(sa unix.Sockaddr) error { cp.remote = sa return cp.connect() } func (cp *controlPlane) bind() error { return unix.Bind(cp.fd, cp.local) } func tunnelSocket(family, protocol int) (fd int, err error) { fd, err = unix.Socket(family, unix.SOCK_DGRAM, protocol) if err != nil { return -1, fmt.Errorf("socket: %v", err) } if err = unix.SetNonblock(fd, true); err != nil { unix.Close(fd) return -1, fmt.Errorf("failed to set socket nonblocking: %v", err) } flags, err := unix.FcntlInt(uintptr(fd), unix.F_GETFD, 0) if err != nil { unix.Close(fd) return -1, fmt.Errorf("fcntl(F_GETFD): %v", err) } _, err = unix.FcntlInt(uintptr(fd), unix.F_SETFD, flags|unix.FD_CLOEXEC) if err != nil { unix.Close(fd) return -1, fmt.Errorf("fcntl(F_SETFD, FD_CLOEXEC): %v", err) } return fd, nil } func newL2tpControlPlane(localAddr, remoteAddr unix.Sockaddr) (*controlPlane, error) { var family, protocol int switch localAddr.(type) { case *unix.SockaddrInet4: family = unix.AF_INET protocol = unix.IPPROTO_UDP case *unix.SockaddrInet6: family = unix.AF_INET6 protocol = unix.IPPROTO_UDP case *unix.SockaddrL2TPIP: family = unix.AF_INET protocol = unix.IPPROTO_L2TP case *unix.SockaddrL2TPIP6: family = unix.AF_INET6 protocol = unix.IPPROTO_L2TP default: return nil, fmt.Errorf("unexpected address type %T", localAddr) } fd, err := tunnelSocket(family, protocol) if err != nil { return nil, err } file := os.NewFile(uintptr(fd), "l2tp") sc, err := file.SyscallConn() if err != nil { unix.Close(fd) return nil, err } return &controlPlane{ local: localAddr, remote: remoteAddr, fd: fd, file: file, rc: sc, connected: false, }, nil } golang-github-katalix-go-l2tp-0.1.7/l2tp/doc.go000066400000000000000000000074111456471064000211520ustar00rootroot00000000000000/* Package l2tp is a library for Layer 2 Tunneling Protocol applications running on Linux systems. L2TP is specified by RFC2661 (L2TPv2) and RFC3931 (L2TPv3). L2TPv2 applies only to PPP tunneling, and is widely used in home broadband installations to convey consumer PPPoE frames to the ISP network. It is also used in conjunction with IPSec in VPN implementations. L2TPv3 extends the protocol in a backward-compatible manner, and allows for the tunneling of various additional Layer 2 frames including Ethernet and VLAN. On Linux systems, the kernel natively supports the L2TPv2 and L2TPv3 data plane. Tunneled frames are handled entirely by the kernel for maximum efficiency. The more complex control plane for instantiating and managing tunnel and session instances is implemented in user space. Currently package l2tp implements: * support for controlling the Linux L2TP data plane for L2TPv2 and L2TPv3 tunnels and sessions, * the L2TPv2 control plane for client/LAC mode. In the future we plan to add support for the L2TPv3 control plane, and server/LNS mode. Usage import ( "github.com/katalix/go-l2tp/l2tp" "github.com/katalix/go-l2tp/config" ) # Note we're ignoring errors for brevity. # Read configuration using the config package. # This is optional: you can build your own configuration # structures if you prefer. config, _ := config.LoadFile("./my-l2tp-config.toml") # Creation of L2TP instances requires an L2TP context. # We're disabling logging and using the default Linux data plane. l2tpctx, _ := l2tp.NewContext(l2tp.LinuxNetlinkDataPlane, nil) # Create tunnel and session instances based on the config for _, tcfg := range config.Tunnels { tunl, _ := l2tpctx.NewStaticTunnel(tcfg.Name, tcfg.Config) for _, scfg := range tcfg.Sessions { _, _, := tunl.NewSession(scfg.Name, scfg.Config) } } Tunnel types Package l2tp has a concept of "tunnel types" which are used to describe how much of the L2TP control protocol the tunnel instance runs. The most basic type is the static tunnel (sometimes described as an "unmanaged" tunnel). The static tunnel runs no control protocol at all, and just instantiates the L2TP data plane in the Linux kernel. Consequently all configuration parameters relating to the tunnel and the sessions within that tunnel must be agreed ahead of time by the peers terminating the tunnel. A slight variation on the theme of the static tunnel is the quiescent tunnel. The quiescent tunnel extends the static tunnel slightly by running just enough of the L2TP control protocol to allow keep-alive (HELLO) messages to be sent and acknowledged using the L2TP reliable transport algorithm. This slight extension allows for detection of tunnel failure in an otherwise static setup. The final tunnel type is the dynamic tunnel. This runs the full L2TP control protocol. Configuration Each tunnel and session instance can be configured using the TunnelConfig and SessionConfig types respectively. These types can be generated as required for your use-case. This partner package config in this repository implements a TOML parser for expressing L2TP configuration using a configuration file. Logging Package l2tp uses structured logging. The logger of choice is the go-kit logger: https://godoc.org/github.com/go-kit/kit/log, and uses go-kit levels in order to separate verbose debugging logs from normal informational output: https://godoc.org/github.com/go-kit/kit/log/level. Logging emitted at level.Info should be enabled for normal useful runtime information about the lifetime of tunnels and sessions. Logging emitted at level.Debug should be enabled for more verbose output allowing development debugging of the code or troubleshooting misbehaving L2TP instances. To disable all logging from package l2tp, pass in a nil logger. */ package l2tp golang-github-katalix-go-l2tp-0.1.7/l2tp/fsm.go000066400000000000000000000011071456471064000211660ustar00rootroot00000000000000package l2tp import ( "fmt" ) type fsmCallback func(args []interface{}) type eventDesc struct { from, to string events []string cb fsmCallback } type fsm struct { current string table []eventDesc } func (f *fsm) handleEvent(e string, args ...interface{}) error { for _, t := range f.table { if f.current == t.from { for _, event := range t.events { if e == event { f.current = t.to if t.cb != nil { t.cb(args) } return nil } } } } return fmt.Errorf("no transition defined for event %v in state %v", e, f.current) } golang-github-katalix-go-l2tp-0.1.7/l2tp/l2tp.go000066400000000000000000000610111456471064000212620ustar00rootroot00000000000000package l2tp import ( "fmt" "math/rand" "net" "os" "sync" "time" "github.com/go-kit/kit/log" "golang.org/x/sys/unix" ) // Context is a container for a collection of L2TP tunnels and // their sessions. type Context struct { logger log.Logger tunnelsByName map[string]tunnel tunnelsByID map[ControlConnID]tunnel tlock sync.RWMutex dp DataPlane callSerial uint32 serialLock sync.Mutex eventHandlers []EventHandler evtLock sync.RWMutex } // Tunnel is an interface representing an L2TP tunnel. type Tunnel interface { // NewSession adds a session to a tunnel instance. // // The name provided must be unique in the parent tunnel. NewSession(name string, cfg *SessionConfig) (Session, error) // Close closes the tunnel, releasing allocated resources. // // Any sessions instantiated inside the tunnel are removed. Close() } type tunnel interface { Tunnel getName() string getCfg() *TunnelConfig getDP() DataPlane getLogger() log.Logger unlinkSession(s session) handleUserEvent(event interface{}) } // Session is an interface representing an L2TP session. type Session interface { // Close closes the session, releasing allocated resources. Close() } type session interface { Session getName() string getCfg() *SessionConfig kill() } // DataPlane is an interface for creating tunnel and session // data plane instances. type DataPlane interface { // NewTunnel creates a new tunnel data plane instance. // // The localAddress and peerAddress arguments are unix Sockaddr // representations of the tunnel local and peer address. // // fd is the tunnel socket fd, which may be invalid (<0) for tunnel // types which don't manage the tunnel socket in userspace. // // On successful return the dataplane should be fully ready for use. NewTunnel( tcfg *TunnelConfig, localAddress, peerAddress unix.Sockaddr, fd int) (TunnelDataPlane, error) // NewSession creates a new session data plane instance. // // tunnelID and peerTunnelID are the L2TP IDs for the parent tunnel // of this session (local and peer respectively). // // On successful return the dataplane should be fully ready for use. NewSession(tunnelID, peerTunnelID ControlConnID, scfg *SessionConfig) (SessionDataPlane, error) // Close is called to release any resources held by the dataplane instance. // It is called when the l2tp Context using the dataplane shuts down. Close() } // TunnelDataPlane is an interface representing a tunnel data plane. type TunnelDataPlane interface { // Down performs the necessary actions to tear down the data plane. // On successful return the dataplane should be fully destroyed. Down() error } // SessionDataPlaneStatistics holds dataplane statistics for receipt and transmission. type SessionDataPlaneStatistics struct { TxPackets, TxBytes, TxErrors, RxPackets, RxBytes, RxErrors uint64 } // SessionDataPlane is an interface representing a session data plane. type SessionDataPlane interface { // GetStatistics obtains session statistics. GetStatistics() (*SessionDataPlaneStatistics, error) // GetInterfaceName obtains the interface name for the session, // which may have been generated by the dataplane. GetInterfaceName() (string, error) // Down performs the necessary actions to tear down the data plane. // On successful return the dataplane should be fully destroyed. Down() error } // EventHandler is an interface for receiving L2TP-specific events. type EventHandler interface { // HandleEvent is called when an event occurs. // // HandleEvent will be called from the goroutine of the tunnel or // session generating the event. // // The event passed is a pointer to a type specific to the event // which has occurred. Use type assertions to determine which event // is being passed. HandleEvent(event interface{}) } // TunnelUpEvent is passed to registered EventHandler instances when a // tunnel comes up. In the case of static or quiescent tunnels, this occurs // immediately on instantiation of the tunnel. For dynamic tunnels, this // occurs on completion of the L2TP control protocol message exchange with // the peer. type TunnelUpEvent struct { TunnelName string Tunnel Tunnel Config *TunnelConfig LocalAddress, PeerAddress unix.Sockaddr } // TunnelDownEvent is passed to registered EventHandler instances when a // tunnel goes down. In the case of static or quiescent tunnels, this occurs // immediately on closure of the tunnel. For dynamic tunnels, this // occurs on completion of the L2TP control protocol message exchange with // the peer. type TunnelDownEvent struct { TunnelName string Tunnel Tunnel Config *TunnelConfig LocalAddress, PeerAddress unix.Sockaddr } // SessionUpEvent is passed to registered EventHandler instances when a session // comes up. In the case of static or quiescent sessions, this occurs immediately // on instantiation of the session. For dynamic sessions, this occurs on the // completion of the L2TP control protocol message exchange with the peer. type SessionUpEvent struct { TunnelName string Tunnel Tunnel TunnelConfig *TunnelConfig SessionName string Session Session SessionConfig *SessionConfig InterfaceName string } // SessionDownEvent is passed to registered EventHandler instances when a session // comes up. In the case of static or quiescent sessions, this occurs immediately // on instantiation of the session. For dynamic sessions, this occurs on the // completion of the L2TP control protocol message exchange with the peer. type SessionDownEvent struct { TunnelName string Tunnel Tunnel TunnelConfig *TunnelConfig SessionName string Session Session SessionConfig *SessionConfig InterfaceName string Result string } // LinuxNetlinkDataPlane is a special sentinel value used to indicate // that the L2TP context should use the internal Linux kernel data plane // implementation. var LinuxNetlinkDataPlane DataPlane = &nullDataPlane{} // NewContext creates a new L2TP context, which can then be used // to instantiate tunnel and session instances. // // The dataplane interface may be specified as LinuxNetlinkDataPlane, // in which case an internal implementation of the Linux Kernel // L2TP data plane is used. In this case, context creation will // fail if it is not possible to connect to the kernel L2TP subsystem: // the kernel must be running the L2TP modules, and the process must // have appropriate permissions to access them. // // If the dataplane is specified as nil, a special "null" data plane // implementation is used. This is useful for experimenting with the // control protocol without requiring root permissions. // // Logging is generated using go-kit levels: informational logging // uses the Info level, while verbose debugging logging uses the // Debug level. Error conditions may be logged using the Error level // depending on the tunnel type. // // If a nil logger is passed, all logging is disabled. func NewContext(dataPlane DataPlane, logger log.Logger) (*Context, error) { if logger == nil { logger = log.NewNopLogger() } rand.Seed(time.Now().UnixNano()) dp, err := initDataPlane(dataPlane) if err != nil { return nil, fmt.Errorf("failed to initialise data plane: %v", err) } return &Context{ logger: logger, tunnelsByName: make(map[string]tunnel), tunnelsByID: make(map[ControlConnID]tunnel), dp: dp, callSerial: rand.Uint32(), }, nil } // NewDynamicTunnel creates a new dynamic L2TP. // // A dynamic L2TP tunnel runs a full RFC2661 (L2TPv2) or // RFC3931 (L2TPv3) tunnel instance using the control protocol // for tunnel instantiation and management. // // The name provided must be unique in the Context. // func (ctx *Context) NewDynamicTunnel(name string, cfg *TunnelConfig) (tunl Tunnel, err error) { var sal, sap unix.Sockaddr // Must have configuration if cfg == nil { return nil, fmt.Errorf("invalid nil config") } // Duplicate the configuration so we don't modify the user's copy myCfg := *cfg // Must not have name clashes if _, ok := ctx.findTunnelByName(name); ok { return nil, fmt.Errorf("already have tunnel %q", name) } // Generate host name if unset if myCfg.HostName == "" { name, err := os.Hostname() if err != nil { return nil, fmt.Errorf("failed to look up host name: %v", err) } myCfg.HostName = name } // Default StopCCN retransmit timeout if unset. // RFC2661 section 5.7 recommends a default of 31s. if myCfg.StopCCNTimeout == 0 { myCfg.StopCCNTimeout = 31 * time.Second } // Sanity check the configuration if myCfg.Version != ProtocolVersion3 && myCfg.Encap == EncapTypeIP { return nil, fmt.Errorf("IP encapsulation only supported for L2TPv3 tunnels") } if myCfg.Version == ProtocolVersion2 { if myCfg.TunnelID > 65535 { return nil, fmt.Errorf("L2TPv2 connection ID %v out of range", myCfg.TunnelID) } } if myCfg.PeerTunnelID != 0 { return nil, fmt.Errorf("L2TPv2 peer connection ID cannot be specified for dynamic tunnels") } if myCfg.Peer == "" { return nil, fmt.Errorf("must specify peer address for dynamic tunnel") } // If the tunnel ID in the config is unset we must generate one. // If the tunnel ID is set, we must check for collisions. // TODO: there is a potential race here if dynamic tunnels are concurrently // added -- an ID assigned here isn't actually reserved until the linkTunnel // call below. if myCfg.TunnelID != 0 { // Must not have TID clashes if _, ok := ctx.findTunnelByID(myCfg.TunnelID); ok { return nil, fmt.Errorf("already have tunnel with TID %q", myCfg.TunnelID) } } else { myCfg.TunnelID, err = ctx.allocTid(myCfg.Version) if err != nil { return nil, fmt.Errorf("failed to allocate a TID: %q", err) } } // Initialise tunnel address structures switch myCfg.Encap { case EncapTypeUDP: sal, sap, err = newUDPAddressPair(myCfg.Local, myCfg.Peer) case EncapTypeIP: sal, sap, err = newIPAddressPair(myCfg.Local, myCfg.TunnelID, myCfg.Peer, myCfg.PeerTunnelID) default: err = fmt.Errorf("unrecognised encapsulation type %v", myCfg.Encap) } if err != nil { return nil, fmt.Errorf("failed to initialise tunnel addresses: %v", err) } t, err := newDynamicTunnel(name, ctx, sal, sap, &myCfg) if err != nil { return nil, err } ctx.linkTunnel(t) tunl = t return } // NewQuiescentTunnel creates a new "quiescent" L2TP tunnel. // // A quiescent tunnel creates a user space socket for the // L2TP control plane, but does not run the control protocol // beyond acknowledging messages and optionally sending HELLO // messages. // // The data plane is established on creation of the tunnel instance. // // The name provided must be unique in the Context. // // The tunnel configuration must include local and peer addresses // and local and peer tunnel IDs. func (ctx *Context) NewQuiescentTunnel(name string, cfg *TunnelConfig) (tunl Tunnel, err error) { var sal, sap unix.Sockaddr // Must have configuration if cfg == nil { return nil, fmt.Errorf("invalid nil config") } // Duplicate the configuration so we don't modify the user's copy myCfg := *cfg // Must not have name clashes if _, ok := ctx.findTunnelByName(name); ok { return nil, fmt.Errorf("already have tunnel %q", name) } // Sanity check the configuration if myCfg.Version != ProtocolVersion3 && myCfg.Encap == EncapTypeIP { return nil, fmt.Errorf("IP encapsulation only supported for L2TPv3 tunnels") } if myCfg.Version == ProtocolVersion2 { if myCfg.TunnelID == 0 || myCfg.TunnelID > 65535 { return nil, fmt.Errorf("L2TPv2 connection ID %v out of range", myCfg.TunnelID) } else if myCfg.PeerTunnelID == 0 || myCfg.PeerTunnelID > 65535 { return nil, fmt.Errorf("L2TPv2 peer connection ID %v out of range", myCfg.PeerTunnelID) } } else { if myCfg.TunnelID == 0 || myCfg.PeerTunnelID == 0 { return nil, fmt.Errorf("L2TPv3 tunnel IDs %v and %v must both be > 0", myCfg.TunnelID, myCfg.PeerTunnelID) } } if myCfg.Local == "" { return nil, fmt.Errorf("must specify local address for quiescent tunnel") } if myCfg.Peer == "" { return nil, fmt.Errorf("must specify peer address for quiescent tunnel") } // Must not have TID clashes if _, ok := ctx.findTunnelByID(myCfg.TunnelID); ok { return nil, fmt.Errorf("already have tunnel with TID %q", myCfg.TunnelID) } // Initialise tunnel address structures switch myCfg.Encap { case EncapTypeUDP: sal, sap, err = newUDPAddressPair(myCfg.Local, myCfg.Peer) case EncapTypeIP: sal, sap, err = newIPAddressPair(myCfg.Local, myCfg.TunnelID, myCfg.Peer, myCfg.PeerTunnelID) default: err = fmt.Errorf("unrecognised encapsulation type %v", myCfg.Encap) } if err != nil { return nil, fmt.Errorf("failed to initialise tunnel addresses: %v", err) } t, err := newQuiescentTunnel(name, ctx, sal, sap, &myCfg) if err != nil { return nil, err } ctx.linkTunnel(t) tunl = t return } // NewStaticTunnel creates a new static (unmanaged) L2TP tunnel. // // A static tunnel does not run any control protocol // and instead merely instantiates the data plane in the // kernel. This is equivalent to the Linux 'ip l2tp' // command(s). // // Static L2TPv2 tunnels are not practically useful, // so NewStaticTunnel only supports creation of L2TPv3 // unmanaged tunnel instances. // // The name provided must be unique in the Context. // // The tunnel configuration must include local and peer addresses // and local and peer tunnel IDs. func (ctx *Context) NewStaticTunnel(name string, cfg *TunnelConfig) (tunl Tunnel, err error) { var sal, sap unix.Sockaddr // Must have configuration if cfg == nil { return nil, fmt.Errorf("invalid nil config") } // Duplicate the configuration so we don't modify the user's copy myCfg := *cfg // Must not have name clashes if _, ok := ctx.findTunnelByName(name); ok { return nil, fmt.Errorf("already have tunnel %q", name) } // Sanity check the configuration if myCfg.Version != ProtocolVersion3 { return nil, fmt.Errorf("static tunnels can be L2TPv3 only") } if myCfg.TunnelID == 0 || myCfg.PeerTunnelID == 0 { return nil, fmt.Errorf("L2TPv3 tunnel IDs %v and %v must both be > 0", myCfg.TunnelID, myCfg.PeerTunnelID) } if myCfg.Local == "" { return nil, fmt.Errorf("must specify local address for static tunnel") } if myCfg.Peer == "" { return nil, fmt.Errorf("must specify peer address for static tunnel") } // Must not have TID clashes if _, ok := ctx.findTunnelByID(myCfg.TunnelID); ok { return nil, fmt.Errorf("already have tunnel with TID %q", myCfg.TunnelID) } // Initialise tunnel address structures switch myCfg.Encap { case EncapTypeUDP: sal, sap, err = newUDPAddressPair(myCfg.Local, myCfg.Peer) case EncapTypeIP: sal, sap, err = newIPAddressPair(myCfg.Local, myCfg.TunnelID, myCfg.Peer, myCfg.PeerTunnelID) default: err = fmt.Errorf("unrecognised encapsulation type %v", myCfg.Encap) } if err != nil { return nil, fmt.Errorf("failed to initialise tunnel addresses: %v", err) } t, err := newStaticTunnel(name, ctx, sal, sap, &myCfg) if err != nil { return nil, err } ctx.linkTunnel(t) tunl = t return } // RegisterEventHandler adds an event handler to the L2TP context. // // On return, the event handler may be called at any time. // // The event handler may be called from multiple go routines managed // by the L2TP context. func (ctx *Context) RegisterEventHandler(handler EventHandler) { ctx.evtLock.Lock() defer ctx.evtLock.Unlock() ctx.eventHandlers = append(ctx.eventHandlers, handler) } // UnregisterEventHandler removes an event handler from the L2TP context. // // It must not be called from the context of an event handler callback. // // On return the event handler will not be called on further L2TP events. func (ctx *Context) UnregisterEventHandler(handler EventHandler) { ctx.evtLock.Lock() defer ctx.evtLock.Unlock() for i, hdlr := range ctx.eventHandlers { if hdlr == handler { ctx.eventHandlers = append(ctx.eventHandlers[:], ctx.eventHandlers[i+1:]...) break } } } func (ctx *Context) handleUserEvent(event interface{}) { ctx.evtLock.RLock() defer ctx.evtLock.RUnlock() for _, hdlr := range ctx.eventHandlers { hdlr.HandleEvent(event) } } // Close tears down the context, including all the L2TP tunnels and sessions // running inside it. func (ctx *Context) Close() { tunnels := []Tunnel{} ctx.tlock.Lock() for name, tunl := range ctx.tunnelsByName { tunnels = append(tunnels, tunl) delete(ctx.tunnelsByName, name) delete(ctx.tunnelsByID, tunl.getCfg().TunnelID) } ctx.tlock.Unlock() for _, tunl := range tunnels { tunl.Close() } ctx.dp.Close() } func (ctx *Context) allocTid(version ProtocolVersion) (ControlConnID, error) { for i := 0; i < 10; i++ { id, err := generateControlConnID(version) if err != nil { return 0, fmt.Errorf("failed to generate tunnel ID: %v", err) } if _, ok := ctx.findTunnelByID(id); !ok { return id, nil } } return 0, fmt.Errorf("ID space exhausted") } func (ctx *Context) linkTunnel(tunl tunnel) { ctx.tlock.Lock() defer ctx.tlock.Unlock() ctx.tunnelsByName[tunl.getName()] = tunl ctx.tunnelsByID[tunl.getCfg().TunnelID] = tunl } func (ctx *Context) unlinkTunnel(tunl tunnel) { ctx.tlock.Lock() defer ctx.tlock.Unlock() delete(ctx.tunnelsByName, tunl.getName()) delete(ctx.tunnelsByID, tunl.getCfg().TunnelID) } func (ctx *Context) findTunnelByName(name string) (tunl tunnel, ok bool) { ctx.tlock.RLock() defer ctx.tlock.RUnlock() tunl, ok = ctx.tunnelsByName[name] return } func (ctx *Context) findTunnelByID(tid ControlConnID) (tunl tunnel, ok bool) { ctx.tlock.RLock() defer ctx.tlock.RUnlock() tunl, ok = ctx.tunnelsByID[tid] return } func (ctx *Context) allocCallSerial() uint32 { ctx.serialLock.Lock() defer ctx.serialLock.Unlock() ctx.callSerial++ return ctx.callSerial } func newUDPTunnelAddress(address string) (unix.Sockaddr, error) { u, err := net.ResolveUDPAddr("udp", address) if err != nil { return nil, fmt.Errorf("resolve %v: %v", address, err) } if b := u.IP.To4(); b != nil { return &unix.SockaddrInet4{ Port: u.Port, Addr: [4]byte{b[0], b[1], b[2], b[3]}, }, nil } else if b := u.IP.To16(); b != nil { // TODO: SockaddrInet6 has a uint32 ZoneId, while UDPAddr // has a Zone string. How to convert between the two? return &unix.SockaddrInet6{ Port: u.Port, Addr: [16]byte{ b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], }, // ZoneId }, nil } return nil, fmt.Errorf("unhandled address family") } func newUDPAddressPair(local, remote string) (sal, sap unix.Sockaddr, err error) { // We expect the peer address to always be set sap, err = newUDPTunnelAddress(remote) if err != nil { return nil, nil, fmt.Errorf("remote address %q: %v", remote, err) } // The local address may not be set: in this case return // a zero-value sockaddr appropriate to the peer address type if local != "" { sal, err = newUDPTunnelAddress(local) if err != nil { return nil, nil, fmt.Errorf("local address %q: %v", local, err) } } else { switch sap.(type) { case *unix.SockaddrInet4: sal = &unix.SockaddrInet4{} case *unix.SockaddrInet6: sal = &unix.SockaddrInet6{} default: // should not occur, c.f. newUDPTunnelAddress return nil, nil, fmt.Errorf("unhanded address family") } } return } func newIPTunnelAddress(address string, ccid ControlConnID) (unix.Sockaddr, error) { u, err := net.ResolveUDPAddr("udp", address) if err != nil { return nil, fmt.Errorf("resolve %v: %v", address, err) } if b := u.IP.To4(); b != nil { return &unix.SockaddrL2TPIP{ Addr: [4]byte{b[0], b[1], b[2], b[3]}, ConnId: uint32(ccid), }, nil } else if b := u.IP.To16(); b != nil { // TODO: SockaddrInet6 has a uint32 ZoneId, while UDPAddr // has a Zone string. How to convert between the two? return &unix.SockaddrL2TPIP6{ Addr: [16]byte{ b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], }, // ZoneId ConnId: uint32(ccid), }, nil } return nil, fmt.Errorf("unhandled address family") } func newIPAddressPair(local string, ccid ControlConnID, remote string, pccid ControlConnID) (sal, sap unix.Sockaddr, err error) { // We expect the peer address to always be set sap, err = newIPTunnelAddress(remote, pccid) if err != nil { return nil, nil, fmt.Errorf("remote address %q: %v", remote, err) } // The local address may not be set: in this case return // a zero-value sockaddr appropriate to the peer address type if local != "" { sal, err = newIPTunnelAddress(local, ccid) if err != nil { return nil, nil, fmt.Errorf("local address %q: %v", local, err) } } else { switch sap.(type) { case *unix.SockaddrL2TPIP: sal = &unix.SockaddrL2TPIP{} case *unix.SockaddrL2TPIP6: sal = &unix.SockaddrL2TPIP6{} default: // should not occur, c.f. newIPTunnelAddress return nil, nil, fmt.Errorf("unhanded address family") } } return } func initDataPlane(dp DataPlane) (DataPlane, error) { if dp == nil { return &nullDataPlane{}, nil } else if dp == LinuxNetlinkDataPlane { return newNetlinkDataPlane() } return dp, nil } func generateControlConnID(version ProtocolVersion) (ControlConnID, error) { var id ControlConnID switch version { case ProtocolVersion2: id = ControlConnID(uint16(rand.Uint32())) case ProtocolVersion3: id = ControlConnID(rand.Uint32()) default: return 0, fmt.Errorf("unhandled version %v", version) } return id, nil } // baseTunnel implements base functionality which all tunnel types will need type baseTunnel struct { logger log.Logger name string parent *Context cfg *TunnelConfig sessionLock sync.RWMutex sessionsByName map[string]session sessionsByID map[ControlConnID]session } func newBaseTunnel(logger log.Logger, name string, parent *Context, config *TunnelConfig) *baseTunnel { return &baseTunnel{ logger: logger, name: name, parent: parent, cfg: config, sessionsByName: make(map[string]session), sessionsByID: make(map[ControlConnID]session), } } func (bt *baseTunnel) getName() string { return bt.name } func (bt *baseTunnel) getCfg() *TunnelConfig { return bt.cfg } func (bt *baseTunnel) getDP() DataPlane { return bt.parent.dp } func (bt *baseTunnel) getLogger() log.Logger { return bt.logger } func (bt *baseTunnel) linkSession(s session) { bt.sessionLock.Lock() defer bt.sessionLock.Unlock() bt.sessionsByName[s.getName()] = s bt.sessionsByID[s.getCfg().SessionID] = s } func (bt *baseTunnel) unlinkSession(s session) { bt.sessionLock.Lock() defer bt.sessionLock.Unlock() delete(bt.sessionsByName, s.getName()) delete(bt.sessionsByID, s.getCfg().SessionID) } func (bt *baseTunnel) handleUserEvent(event interface{}) { bt.parent.handleUserEvent(event) } func (bt *baseTunnel) findSessionByName(name string) (s session, ok bool) { bt.sessionLock.RLock() defer bt.sessionLock.RUnlock() s, ok = bt.sessionsByName[name] return } func (bt *baseTunnel) findSessionByID(id ControlConnID) (s session, ok bool) { bt.sessionLock.RLock() defer bt.sessionLock.RUnlock() s, ok = bt.sessionsByID[id] return } func (bt *baseTunnel) allSessions() (sessions []session) { bt.sessionLock.RLock() defer bt.sessionLock.RUnlock() for _, s := range bt.sessionsByName { sessions = append(sessions, s) } return } // Close all sessions in a tunnel without kicking their FSM instances. // When a tunnel goes down, StopCCN is sufficient to implicitly terminate // all session instances running in that tunnel. func (bt *baseTunnel) closeAllSessions() { sessions := []session{} bt.sessionLock.Lock() for name, s := range bt.sessionsByName { sessions = append(sessions, s) delete(bt.sessionsByName, name) delete(bt.sessionsByID, s.getCfg().SessionID) } bt.sessionLock.Unlock() for _, s := range sessions { s.kill() } } func (bt *baseTunnel) allocSid() (ControlConnID, error) { for i := 0; i < 10; i++ { id, err := generateControlConnID(bt.cfg.Version) if err != nil { return 0, fmt.Errorf("failed to generate session ID: %v", err) } if _, ok := bt.findSessionByID(id); !ok { return id, nil } } return 0, fmt.Errorf("ID space exhausted") } // baseSession implements base functionality which all session types will need type baseSession struct { logger log.Logger name string parent tunnel cfg *SessionConfig } func newBaseSession(logger log.Logger, name string, parent tunnel, config *SessionConfig) *baseSession { return &baseSession{ logger: logger, name: name, parent: parent, cfg: config, } } func (bs *baseSession) getName() string { return bs.name } func (bs *baseSession) getCfg() *SessionConfig { return bs.cfg } golang-github-katalix-go-l2tp-0.1.7/l2tp/l2tp_dynamic_session.go000066400000000000000000000271311456471064000245360ustar00rootroot00000000000000package l2tp import ( "fmt" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "sync" ) type dynamicSession struct { *baseSession isClosed bool established bool callSerial uint32 ifname string result string dt *dynamicTunnel dp SessionDataPlane wg sync.WaitGroup msgRxChan chan controlMessage eventChan chan string closeChan chan interface{} killChan chan interface{} fsm fsm } func (ds *dynamicSession) Close() { ds.parent.unlinkSession(ds) close(ds.closeChan) ds.wg.Wait() } func (ds *dynamicSession) kill() { ds.parent.unlinkSession(ds) close(ds.killChan) ds.wg.Wait() } func (ds *dynamicSession) onTunnelUp() { ds.eventChan <- "tunnelopen" } func (ds *dynamicSession) handleCtlMsg(msg controlMessage) { ds.msgRxChan <- msg } func (ds *dynamicSession) runSession() { defer ds.wg.Done() level.Info(ds.logger).Log( "message", "new dynamic session", "session_id", ds.cfg.SessionID, "peer_session_id", ds.cfg.PeerSessionID, "pseudowire", ds.cfg.Pseudowire) for !ds.isClosed { select { case msg, ok := <-ds.msgRxChan: if !ok { ds.fsmActClose(nil) return } ds.handleMsg(msg) case ev, ok := <-ds.eventChan: if !ok { ds.fsmActClose(nil) return } ds.handleEvent(ev) case <-ds.killChan: ds.fsmActClose(nil) return case <-ds.closeChan: ds.handleEvent("close", avpCDNResultCodeAdminDisconnect) return } } } func (ds *dynamicSession) handleEvent(ev string, args ...interface{}) { if ev != "" { level.Debug(ds.logger).Log( "message", "fsm event", "event", ev) err := ds.fsm.handleEvent(ev, args...) if err != nil { level.Error(ds.logger).Log( "message", "failed to handle fsm event", "error", err) ds.fsmActClose(nil) } } } // panics if expected arguments are not passed func fsmArgsToV2Msg(args []interface{}) (msg *v2ControlMessage) { if len(args) != 1 { panic(fmt.Sprintf("unexpected argument count (wanted 1, got %v)", len(args))) } msg, ok := args[0].(*v2ControlMessage) if !ok { panic(fmt.Sprintf("first argument %T not *v2ControlMessage", args[0])) } return } // cdn args are optional, we set defaults here func fsmArgsToCdnResult(args []interface{}) *resultCode { rc := resultCode{ result: avpCDNResultCodeAdminDisconnect, errCode: avpErrorCodeNoError, } for i := 0; i < len(args); i++ { switch v := args[i].(type) { case avpResultCode: rc.result = v case avpErrorCode: rc.errCode = v case string: rc.errMsg = v } } return &rc } func cdnResultCodeToString(rc *resultCode) string { var resStr, errStr, errMsg string switch rc.result { case avpCDNResultCodeReserved: resStr = "reserved" case avpCDNResultCodeLostCarrier: resStr = "lost carrier" case avpCDNResultCodeGeneralError: resStr = "general error" case avpCDNResultCodeAdminDisconnect: resStr = "admin disconnect" case avpCDNResultCodeNoResources: resStr = "temporary lack of resources" case avpCDNResultCodeNotAvailable: resStr = "permanent lack of resources" case avpCDNResultCodeInvalidDestination: resStr = "invalid destination" case avpCDNResultCodeNoAnswer: resStr = "not carrier detected" case avpCDNResultCodeBusy: resStr = "busy signal detected" case avpCDNResultCodeNoDialTone: resStr = "no dial tone" case avpCDNResultCodeTimeout: resStr = "establish timeout" case avpCDNResultCodeBadTransport: resStr = "no appropriate framing detected" } switch rc.errCode { case avpErrorCodeNoError: errStr = "no general error" case avpErrorCodeNoControlConnection: errStr = "no control connection exists yet" case avpErrorCodeBadLength: errStr = "length is wrong" case avpErrorCodeBadValue: errStr = "field out of range or reserved field was non-zero" case avpErrorCodeNoResource: errStr = "insufficient resources to handle this operation now" case avpErrorCodeInvalidSessionID: errStr = "session ID invalid in this context" case avpErrorCodeVendorSpecificError: errStr = "generic vendor-specific error" case avpErrorCodeTryAnother: errStr = "try another LNS" case avpErrorCodeMBitShutdown: errStr = "shut down due to unknown AVP with the M bit set" } if rc.errMsg != "" { errMsg = rc.errMsg } else { errMsg = "unset" } return fmt.Sprintf("result %d (%s), error %d (%s), message '%s'", rc.result, resStr, rc.errCode, errStr, errMsg) } func (ds *dynamicSession) handleMsg(msg controlMessage) { switch msg.protocolVersion() { case ProtocolVersion2: msg, ok := msg.(*v2ControlMessage) if !ok { // This shouldn't occur, since the header protocol version // dictates the message type during parsing. Bail out if // it does since it indicates some dire coding error. level.Error(ds.logger).Log( "message", "couldn't cast L2TPv2 message as v2ControlMessage") ds.fsmActClose(nil) return } ds.handleV2Msg(msg) return } level.Error(ds.logger).Log( "message", "unhandled protocol version", "version", msg.protocolVersion()) } func (ds *dynamicSession) handleV2Msg(msg *v2ControlMessage) { // It's possible to have a message mis-delivered on our control // socket. Ignore these messages: ideally we'd redirect them // but dropping them is a good compromise for now. if msg.Sid() != uint16(ds.cfg.SessionID) { level.Error(ds.logger).Log( "message", "received control message with the wrong SID", "expected", ds.cfg.SessionID, "got", msg.Sid()) return } // Validate the message. If validation fails drive shutdown via. // the FSM to allow the error to be communicated to the peer. err := msg.validate() if err != nil { level.Error(ds.logger).Log( "message", "bad control message", "message_type", msg.getType(), "error", err) ds.handleEvent("close", avpCDNResultCodeGeneralError, avpErrorCodeBadValue, fmt.Sprintf("bad %v message: %v", msg.getType(), err)) } // Map the message to the appropriate event type. If we haven't got // an event appropriate to the incoming message close the tunnel. eventMap := []struct { m avpMsgType e string }{ {avpMsgTypeIcrq, "icrq"}, {avpMsgTypeIcrp, "icrp"}, {avpMsgTypeIccn, "iccn"}, {avpMsgTypeCdn, "cdn"}, } for _, em := range eventMap { if msg.getType() == em.m { ds.handleEvent(em.e, msg) return } } level.Error(ds.logger).Log( "message", "unhandled v2 control message", "message_type", msg.getType()) ds.handleEvent("close", avpCDNResultCodeGeneralError, avpErrorCodeBadValue, fmt.Sprintf("unhandled v2 control message %v", msg.getType())) } func (ds *dynamicSession) sendMessage(msg controlMessage) { err := ds.dt.sendMessage(msg) if err != nil { level.Error(ds.logger).Log( "message", "failed to send control message", "message_type", msg.getType(), "error", err) ds.fsmActClose(nil) } } func (ds *dynamicSession) fsmActSendIcrq(args []interface{}) { err := ds.sendIcrq() if err != nil { level.Error(ds.logger).Log( "message", "failed to send ICRQ message", "error", err) ds.fsmActClose(nil) } } func (ds *dynamicSession) sendIcrq() (err error) { msg, err := newV2Icrq(ds.callSerial, ds.parent.getCfg().PeerTunnelID, ds.cfg) if err != nil { return err } ds.sendMessage(msg) return } func (ds *dynamicSession) fsmActOnIcrp(args []interface{}) { msg := fsmArgsToV2Msg(args) psid, err := findUint16Avp(msg.getAvps(), vendorIDIetf, avpTypeSessionID) if err != nil { // Shouldn't occur since session ID is mandatory level.Error(ds.logger).Log( "message", "failed to parse peer session ID from ICRP", "error", err) ds.handleEvent("close", avpCDNResultCodeGeneralError, avpErrorCodeBadValue, "no Assigned Session ID AVP in ICRP message") return } ds.cfg.PeerSessionID = ControlConnID(psid) err = ds.sendIccn() if err != nil { level.Error(ds.logger).Log( "message", "failed to send ICCN", "error", err) // TODO: CDN args ds.fsmActClose(nil) return } level.Info(ds.logger).Log("message", "control plane established") // establish the data plane ds.dp, err = ds.parent.getDP().NewSession( ds.parent.getCfg().TunnelID, ds.parent.getCfg().PeerTunnelID, ds.cfg) if err != nil { level.Error(ds.logger).Log( "message", "failed to establish data plane", "error", err) // TODO: CDN args ds.fsmActClose(nil) return } ds.ifname, err = ds.dp.GetInterfaceName() if err != nil { level.Error(ds.logger).Log( "message", "failed to retrieve session interface name", "error", err) // TODO: CDN args ds.fsmActClose(nil) } level.Info(ds.logger).Log("message", "data plane established") ds.established = true ds.parent.handleUserEvent(&SessionUpEvent{ TunnelName: ds.parent.getName(), Tunnel: ds.parent, TunnelConfig: ds.parent.getCfg(), SessionName: ds.getName(), Session: ds, SessionConfig: ds.cfg, InterfaceName: ds.ifname, }) } func (ds *dynamicSession) sendIccn() (err error) { msg, err := newV2Iccn(ds.parent.getCfg().PeerTunnelID, ds.cfg) if err != nil { return err } ds.sendMessage(msg) return } func (ds *dynamicSession) fsmActSendCdn(args []interface{}) { rc := fsmArgsToCdnResult(args) if ds.result == "" { ds.result = cdnResultCodeToString(rc) } _ = ds.sendCdn(rc) ds.fsmActClose(args) } func (ds *dynamicSession) sendCdn(rc *resultCode) (err error) { msg, err := newV2Cdn(ds.parent.getCfg().PeerTunnelID, rc, ds.cfg) if err != nil { return err } ds.sendMessage(msg) return } func (ds *dynamicSession) fsmActOnCdn(args []interface{}) { msg := fsmArgsToV2Msg(args) rc, err := findResultCodeAvp(msg.getAvps(), vendorIDIetf, avpTypeResultCode) if err == nil && ds.result == "" { ds.result = cdnResultCodeToString(rc) } ds.fsmActClose(args) } func (ds *dynamicSession) fsmActClose(args []interface{}) { if ds.dp != nil { err := ds.dp.Down() if err != nil { level.Error(ds.logger).Log("message", "dataplane down failed", "error", err) } } if ds.established { ds.established = false ds.parent.handleUserEvent(&SessionDownEvent{ TunnelName: ds.parent.getName(), Tunnel: ds.parent, TunnelConfig: ds.parent.getCfg(), SessionName: ds.getName(), Session: ds, SessionConfig: ds.cfg, InterfaceName: ds.ifname, Result: ds.result, }) } ds.parent.unlinkSession(ds) level.Info(ds.logger).Log("message", "close") ds.isClosed = true } // Create a new client/LAC mode session instance func newDynamicSession(serial uint32, name string, parent *dynamicTunnel, cfg *SessionConfig) (ds *dynamicSession, err error) { ds = &dynamicSession{ baseSession: newBaseSession( log.With(parent.getLogger(), "session_name", name), name, parent, cfg), callSerial: serial, dt: parent, msgRxChan: make(chan controlMessage), eventChan: make(chan string), closeChan: make(chan interface{}), killChan: make(chan interface{}), } // Ref: RFC2661 section 7.4.1 ds.fsm = fsm{ current: "waittunnel", table: []eventDesc{ {from: "waittunnel", events: []string{"tunnelopen"}, cb: ds.fsmActSendIcrq, to: "waitreply"}, {from: "waittunnel", events: []string{"close"}, cb: ds.fsmActClose, to: "dead"}, {from: "waitreply", events: []string{"icrp"}, cb: ds.fsmActOnIcrp, to: "established"}, {from: "waitreply", events: []string{"iccn"}, cb: ds.fsmActClose, to: "dead"}, {from: "waitreply", events: []string{"cdn"}, cb: ds.fsmActOnCdn, to: "dead"}, {from: "waitreply", events: []string{"icrq", "close"}, cb: ds.fsmActSendCdn, to: "dead"}, {from: "established", events: []string{"cdn"}, cb: ds.fsmActOnCdn, to: "dead"}, { from: "established", events: []string{ "icrq", "icrp", "iccn", "close", }, cb: ds.fsmActSendCdn, to: "dead", }, }, } ds.wg.Add(1) go ds.runSession() return } golang-github-katalix-go-l2tp-0.1.7/l2tp/l2tp_dynamic_test.go000066400000000000000000000221171456471064000240310ustar00rootroot00000000000000package l2tp // More tunnel tests which require root permissions are implemented // in l2tp_test.go in the TestRequiresRoot function. // // These tests are using the null dataplane and hence don't require root. import ( "fmt" "os" "sync" "testing" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "golang.org/x/sys/unix" ) type eventCounters struct { tunnelUp, tunnelDown, sessionUp, sessionDown int } type testEventCounter struct { eventCounters } func (tec *testEventCounter) HandleEvent(event interface{}) { switch event.(type) { case *TunnelUpEvent: tec.tunnelUp++ case *TunnelDownEvent: tec.tunnelDown++ case *SessionUpEvent: tec.sessionUp++ case *SessionDownEvent: tec.sessionDown++ } } func (tec *testEventCounter) getEventCounts() eventCounters { return tec.eventCounters } type eventCounterCloser interface { EventHandler getEventCounts() eventCounters wait() } type testTunnelEventCounterCloser struct { testEventCounter wg sync.WaitGroup } func (tecc *testTunnelEventCounterCloser) HandleEvent(event interface{}) { tecc.testEventCounter.HandleEvent(event) if ev, ok := event.(*TunnelUpEvent); ok { t := ev.Tunnel tecc.wg.Add(1) go func() { t.Close() tecc.wg.Done() }() } } func (tecc *testTunnelEventCounterCloser) wait() { tecc.wg.Wait() } type testSessionEventCounterCloser struct { testEventCounter wg sync.WaitGroup } func (secc *testSessionEventCounterCloser) HandleEvent(event interface{}) { secc.testEventCounter.HandleEvent(event) if ev, ok := event.(*SessionUpEvent); ok { // Closing the tunnel should close the session, and will also cause the // test LNS instance to shut down t := ev.Tunnel secc.wg.Add(1) go func() { t.Close() secc.wg.Done() }() } } func (secc *testSessionEventCounterCloser) wait() { secc.wg.Wait() } type testLNS struct { logger log.Logger tcfg *TunnelConfig scfg *SessionConfig xport *transport tunnelEstablished bool sessionEstablished bool isShutdown bool } func newTestLNS(logger log.Logger, tcfg *TunnelConfig, scfg *SessionConfig) (*testLNS, error) { myLogger := log.With(logger, "tunnel_name", "testLNS") sal, sap, err := newUDPAddressPair(tcfg.Local, tcfg.Peer) if err != nil { return nil, fmt.Errorf("newUDPAddressPair(%v, %v): %v", tcfg.Local, tcfg.Peer, err) } cp, err := newL2tpControlPlane(sal, sap) if err != nil { return nil, fmt.Errorf("newL2tpControlPlane(%v, %v): %v", sal, sap, err) } err = cp.bind() if err != nil { return nil, fmt.Errorf("cp.bind(): %v", err) } xcfg := defaulttransportConfig() xcfg.Version = tcfg.Version xport, err := newTransport(myLogger, cp, xcfg) if err != nil { return nil, fmt.Errorf("newTransport(): %v", err) } lns := &testLNS{ logger: myLogger, tcfg: tcfg, scfg: scfg, xport: xport, } return lns, nil } func (lns *testLNS) shutdown() { level.Debug(lns.logger).Log("message", "shutdown") lns.isShutdown = true } func (lns *testLNS) handleV2Msg(msg *v2ControlMessage, from unix.Sockaddr) error { level.Debug(lns.logger).Log( "message", "receive control message", "message_type", msg.getType()) switch msg.getType() { // Tunnel messages case avpMsgTypeSccrq: ptid, err := findUint16Avp(msg.getAvps(), vendorIDIetf, avpTypeTunnelID) if err != nil { return fmt.Errorf("no Tunnel ID AVP in SCCRQ") } lns.xport.config.PeerControlConnID = ControlConnID(ptid) lns.tcfg.PeerTunnelID = ControlConnID(ptid) lns.xport.cp.connectTo(from) rsp, err := newV2Sccrp(lns.tcfg) if err != nil { return fmt.Errorf("failed to build SCCRP: %v", err) } return lns.xport.send(rsp) case avpMsgTypeScccn: lns.tunnelEstablished = true return nil case avpMsgTypeStopccn: // HACK: allow the transport to ack the stopccn. // By closing the transport the transport recvChan will be // closed, which will cause the run() function to return. time.Sleep(250 * time.Millisecond) lns.isShutdown = true return nil case avpMsgTypeHello: return nil // Session messages case avpMsgTypeIcrq: psid, err := findUint16Avp(msg.getAvps(), vendorIDIetf, avpTypeSessionID) if err != nil { return fmt.Errorf("no Session ID AVP in ICRQ") } lns.scfg.PeerSessionID = ControlConnID(psid) rsp, err := newV2Icrp(lns.tcfg.PeerTunnelID, lns.scfg) if err != nil { return fmt.Errorf("failed to build ICRP: %v", err) } return lns.xport.send(rsp) case avpMsgTypeIccn: lns.sessionEstablished = true return nil case avpMsgTypeCdn: return nil } return fmt.Errorf("message %v not handled", msg.getType()) } func (lns *testLNS) run(timeout time.Duration) { deadline := time.NewTimer(timeout) for !lns.isShutdown { select { case <-deadline.C: lns.shutdown() return case m, ok := <-lns.xport.recvChan: if !ok { return } msg, ok := m.msg.(*v2ControlMessage) if !ok { panic("failed to cast received message as v2ControlMessage") } err := lns.handleV2Msg(msg, m.from) if err != nil { lns.shutdown() return } } } lns.xport.close() } func TestDynamicClient(t *testing.T) { cases := []struct { name string localTunnelCfg, peerTunnelCfg *TunnelConfig localSessionCfg, peerSessionCfg *SessionConfig }{ { name: "L2TPv2 UDP AF_INET", localTunnelCfg: &TunnelConfig{ Local: "127.0.0.1:6000", Peer: "localhost:5000", Version: ProtocolVersion2, TunnelID: 4567, Encap: EncapTypeUDP, StopCCNTimeout: 250 * time.Millisecond, }, peerTunnelCfg: &TunnelConfig{ Local: "localhost:5000", Peer: "127.0.0.1:6000", Version: ProtocolVersion2, TunnelID: 4567, Encap: EncapTypeUDP, StopCCNTimeout: 250 * time.Millisecond, }, }, { name: "L2TPv2 UDP AF_INET (alloc TID)", localTunnelCfg: &TunnelConfig{ Local: "127.0.0.1:6000", Peer: "localhost:5000", Version: ProtocolVersion2, Encap: EncapTypeUDP, StopCCNTimeout: 250 * time.Millisecond, }, peerTunnelCfg: &TunnelConfig{ Local: "localhost:5000", Peer: "127.0.0.1:6000", Version: ProtocolVersion2, TunnelID: 4567, Encap: EncapTypeUDP, StopCCNTimeout: 250 * time.Millisecond, }, }, { name: "L2TPv2 UDP AF_INET (alloc TID, with session)", localTunnelCfg: &TunnelConfig{ Local: "127.0.0.1:6000", Peer: "localhost:5000", Version: ProtocolVersion2, Encap: EncapTypeUDP, StopCCNTimeout: 250 * time.Millisecond, }, localSessionCfg: &SessionConfig{ Pseudowire: PseudowireTypePPP, }, peerTunnelCfg: &TunnelConfig{ Local: "localhost:5000", Peer: "127.0.0.1:6000", Version: ProtocolVersion2, TunnelID: 4567, Encap: EncapTypeUDP, StopCCNTimeout: 250 * time.Millisecond, }, peerSessionCfg: &SessionConfig{ Pseudowire: PseudowireTypePPP, SessionID: 5566, }, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { logger := level.NewFilter(log.NewLogfmtLogger(os.Stderr), level.AllowDebug()) // Create and run a test LNS instance lns, err := newTestLNS(logger, c.peerTunnelCfg, c.peerSessionCfg) if err != nil { t.Fatalf("newTestLNS: %v", err) } var lnsWg sync.WaitGroup lnsWg.Add(1) go func() { lns.run(3 * time.Second) lnsWg.Done() }() // Bring up the client tunnel. ctx, err := NewContext(nil, logger) if err != nil { t.Fatalf("NewContext(): %v", err) } // The event counter triggers shutdown when it sees a specific event. // For tests with just a tunnel, close on tunnel establishment. // For tests with a session, close on session establishment. var eventCounter eventCounterCloser if c.localSessionCfg != nil { eventCounter = &testSessionEventCounterCloser{} } else { eventCounter = &testTunnelEventCounterCloser{} } ctx.RegisterEventHandler(eventCounter) tunl, err := ctx.NewDynamicTunnel("t1", c.localTunnelCfg) if err != nil { t.Fatalf("NewDynamicTunnel(%q, %v): %v", "t1", c.localTunnelCfg, err) } // And optionally the client session if c.localSessionCfg != nil { _, err = tunl.NewSession("s1", c.peerSessionCfg) if err != nil { t.Fatalf("NewSession(%q, %v): %v", "s1", c.peerSessionCfg, err) } } lnsWg.Wait() ctx.Close() eventCounter.wait() // If we bought up tunnel and session we expect up/down events for both. // If we bought up just a tunnel, then we expect up/down events for the tunnel alone. var expectEvents eventCounters if c.localSessionCfg != nil { expectEvents = eventCounters{tunnelUp: 1, tunnelDown: 1, sessionUp: 1, sessionDown: 1} } else { expectEvents = eventCounters{tunnelUp: 1, tunnelDown: 1, sessionUp: 0, sessionDown: 0} } gotEvents := eventCounter.getEventCounts() if expectEvents != gotEvents { t.Errorf("event listener: expected %v event, got %v", expectEvents, gotEvents) } if lns.tunnelEstablished != true { t.Errorf("LNS didn't establish") } }) } } golang-github-katalix-go-l2tp-0.1.7/l2tp/l2tp_dynamic_tunnel.go000066400000000000000000000374251456471064000243670ustar00rootroot00000000000000package l2tp import ( "fmt" "sync" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "golang.org/x/sys/unix" ) type sendMsg struct { msg controlMessage completeChan chan error } type eventArgs struct { event string args []interface{} } type dynamicTunnel struct { *baseTunnel closingLock sync.Mutex isClosing bool established bool sal, sap unix.Sockaddr cp *controlPlane xport *transport dp TunnelDataPlane closeChan chan bool sendChan chan *sendMsg eventChan chan *eventArgs wg sync.WaitGroup sessionTxWg sync.WaitGroup fsm fsm } func (dt *dynamicTunnel) NewSession(name string, cfg *SessionConfig) (sess Session, err error) { // Must have configuration if cfg == nil { return nil, fmt.Errorf("invalid nil config") } // Name clashes are not allowed if _, ok := dt.findSessionByName(name); ok { return nil, fmt.Errorf("already have session %q", name) } dt.closingLock.Lock() if dt.isClosing { dt.closingLock.Unlock() return nil, fmt.Errorf("tunnel is closing") } dt.closingLock.Unlock() // Duplicate the configuration so we don't modify the user's copy myCfg := *cfg // If the session ID in the config is unset, we must generate one. // If the session ID is set, we must check for collisions. // TODO: there is a potential race here if sessions are concurrently // added -- an ID assigned here isn't actually reserved until the linkSession // call. if myCfg.SessionID != 0 { // Must not have session ID clashes if _, ok := dt.findSessionByID(myCfg.SessionID); ok { return nil, fmt.Errorf("already have session with SID %q", myCfg.SessionID) } } else { myCfg.SessionID, err = dt.allocSid() if err != nil { return nil, fmt.Errorf("failed to allocate a SID: %q", err) } } s, err := newDynamicSession(dt.parent.allocCallSerial(), name, dt, &myCfg) if err != nil { return nil, err } dt.injectEvent("newsession", s) sess = s return } func (dt *dynamicTunnel) Close() { if dt != nil { dt.parent.unlinkTunnel(dt) close(dt.closeChan) dt.wg.Wait() } } func (dt *dynamicTunnel) closeAllSessions() { // In order to prevent any concurrently executing sessions from // blocking in a channel send when trying to transmit control // messages, drain the send channel while closing the sessions. var wg sync.WaitGroup exit := make(chan interface{}) wg.Add(1) go func() { defer wg.Done() for { select { case <-exit: return case sm, ok := <-dt.sendChan: if !ok { return } sm.completeChan <- fmt.Errorf("tunnel is shutting down") } } }() dt.baseTunnel.closeAllSessions() dt.sessionTxWg.Wait() close(exit) wg.Wait() } func (dt *dynamicTunnel) sendMessage(msg controlMessage) error { sm := &sendMsg{ msg: msg, completeChan: make(chan error), } dt.sendChan <- sm return <-sm.completeChan } func (dt *dynamicTunnel) runTunnel() { defer dt.wg.Done() level.Info(dt.logger).Log( "message", "new dynamic tunnel", "version", dt.cfg.Version, "encap", dt.cfg.Encap, "local", dt.cfg.Local, "peer", dt.cfg.Peer, "tunnel_id", dt.cfg.TunnelID, "peer_tunnel_id", dt.cfg.PeerTunnelID) dt.handleEvent("open") for { select { case <-dt.closeChan: dt.handleEvent("close", avpStopCCNResultCodeClearConnection) return case m, ok := <-dt.xport.recvChan: if !ok { dt.fsmActClose(nil) return } dt.handleMsg(m) case ea, ok := <-dt.eventChan: if !ok { dt.fsmActClose(nil) return } dt.handleEvent(ea.event, ea.args...) case sm, ok := <-dt.sendChan: if !ok { dt.fsmActClose(nil) return } dt.sessionTxWg.Add(1) go func() { defer dt.sessionTxWg.Done() err := dt.xport.send(sm.msg) sm.completeChan <- err }() } } } func (dt *dynamicTunnel) handleEvent(ev string, args ...interface{}) { if ev != "" { level.Debug(dt.logger).Log( "message", "fsm event", "event", ev) err := dt.fsm.handleEvent(ev, args...) if err != nil { level.Error(dt.logger).Log( "message", "failed to handle fsm event", "error", err) // TODO: this may be extreme dt.fsmActClose(nil) } } } func (dt *dynamicTunnel) injectEvent(ev string, args ...interface{}) { ea := eventArgs{event: ev} for i := 0; i < len(args); i++ { ea.args = append(ea.args, args[i]) } dt.eventChan <- &ea } // panics if expected arguments are not passed func fsmArgsToV2MsgFrom(args []interface{}) (msg *v2ControlMessage, from unix.Sockaddr) { if len(args) != 2 { panic(fmt.Sprintf("unexpected argument count (wanted 2, got %v)", len(args))) } msg, ok := args[0].(*v2ControlMessage) if !ok { panic(fmt.Sprintf("first argument %T not *v2ControlMessage", args[0])) } from, ok = args[1].(unix.Sockaddr) if !ok { panic(fmt.Sprintf("second argument %T not unix.Sockaddr", args[0])) } return } // panics if expected arguments are not passed func fsmArgsToSession(args []interface{}) (ds *dynamicSession) { if len(args) != 1 { panic(fmt.Sprintf("unexpected argument count (wanted 1, got %v)", len(args))) } ds, ok := args[0].(*dynamicSession) if !ok { panic(fmt.Sprintf("first argument %T not *dynamicSession", args[0])) } return } // stopccn args are optional, we set defaults here func fsmArgsToStopccnResult(args []interface{}) *resultCode { rc := resultCode{ result: avpStopCCNResultCodeClearConnection, errCode: avpErrorCodeNoError, } for i := 0; i < len(args); i++ { switch v := args[i].(type) { case avpResultCode: rc.result = v case avpErrorCode: rc.errCode = v case string: rc.errMsg = v } } return &rc } func (dt *dynamicTunnel) handleMsg(m *recvMsg) { // Initial validation: ignore a message with the wrong protocol version if m.msg.protocolVersion() != dt.cfg.Version { level.Error(dt.logger).Log( "message", "received control message with wrong protocol version", "expected", dt.cfg.Version, "got", m.msg.protocolVersion()) return } switch m.msg.protocolVersion() { case ProtocolVersion2: msg, ok := m.msg.(*v2ControlMessage) if !ok { // This shouldn't occur, since the header protocol version // dictates the message type during parsing. Bail out if // it does since it indicates some dire coding error. level.Error(dt.logger).Log( "message", "couldn't cast L2TPv2 message as v2ControlMessage") dt.fsmActClose(nil) return } dt.handleV2Msg(msg, m.from) return } level.Error(dt.logger).Log( "message", "unhandled protocol version", "version", m.msg.protocolVersion()) dt.handleEvent("close", avpStopCCNResultCodeChannelProtocolVersionUnsupported, avpErrorCode(ProtocolVersion2), fmt.Sprintf("unhandled protocol version %v", m.msg.protocolVersion())) } func (dt *dynamicTunnel) handleV2Msg(msg *v2ControlMessage, from unix.Sockaddr) { // It's possible to have a message mis-delivered on our control // socket. Ignore these messages: ideally we'd redirect them // but dropping them is a good compromise for now. if msg.Tid() != uint16(dt.cfg.TunnelID) { level.Error(dt.logger).Log( "message", "received control message with the wrong TID", "expected", dt.cfg.TunnelID, "got", msg.Tid()) return } // Validate the message. If validation fails drive shutdown via. // the FSM to allow the error to be communicated to the peer. err := msg.validate() if err != nil { level.Error(dt.logger).Log( "message", "bad control message", "message_type", msg.getType(), "error", err) dt.handleEvent("close", avpStopCCNResultCodeGeneralError, avpErrorCodeBadValue, fmt.Sprintf("bad %v message: %v", msg.getType(), err)) } // Map the message to the appropriate event type. If we haven't got // an event appropriate to the incoming message close the tunnel. eventMap := []struct { m avpMsgType e string }{ {avpMsgTypeSccrq, "sccrq"}, {avpMsgTypeSccrp, "sccrp"}, {avpMsgTypeScccn, "scccn"}, {avpMsgTypeStopccn, "stopccn"}, {avpMsgTypeHello, ""}, // fsm ignores empty events {avpMsgTypeIcrq, "sessionmsg"}, {avpMsgTypeIcrp, "sessionmsg"}, {avpMsgTypeIccn, "sessionmsg"}, {avpMsgTypeCdn, "sessionmsg"}, } for _, em := range eventMap { if msg.getType() == em.m { dt.handleEvent(em.e, msg, from) return } } level.Error(dt.logger).Log( "message", "unhandled v2 control message", "message_type", msg.getType()) dt.handleEvent("close", avpStopCCNResultCodeGeneralError, avpErrorCodeBadValue, fmt.Sprintf("unhandled v2 control message %v", msg.getType())) } func (dt *dynamicTunnel) fsmActSendSccrq(args []interface{}) { err := dt.sendSccrq() if err != nil { level.Error(dt.logger).Log( "message", "failed to send SCCRQ message", "error", err) dt.fsmActClose(nil) } } func (dt *dynamicTunnel) sendSccrq() error { msg, err := newV2Sccrq(dt.cfg) if err != nil { return err } return dt.xport.send(msg) } func (dt *dynamicTunnel) fsmActOnSccrp(args []interface{}) { msg, from := fsmArgsToV2MsgFrom(args) ptid, err := findUint16Avp(msg.getAvps(), vendorIDIetf, avpTypeTunnelID) if err != nil { // Shouldn't occur since tunnel ID is mandatory level.Error(dt.logger).Log( "message", "failed to parse peer tunnel ID from SCCRP", "error", err) dt.handleEvent("close") return } // Reconfigure transport and socket now we know the peer TID // and the address being used for this tunnel dt.xport.config.PeerControlConnID = ControlConnID(ptid) dt.cfg.PeerTunnelID = ControlConnID(ptid) dt.cp.connectTo(from) err = dt.sendScccn() if err != nil { level.Error(dt.logger).Log( "message", "failed to send SCCCN", "error", err) dt.fsmActClose(nil) return } level.Info(dt.logger).Log("message", "control plane established") // establish the data plane dt.dp, err = dt.parent.dp.NewTunnel(dt.cfg, dt.sal, dt.sap, dt.cp.fd) if err != nil { level.Error(dt.logger).Log( "message", "failed to establish data plane", "error", err) dt.handleEvent("close", avpStopCCNResultCodeGeneralError, avpErrorCodeVendorSpecificError, fmt.Sprintf("failed to instantiate tunnel data plane: %v", err)) return } level.Info(dt.logger).Log("message", "data plane established") // inform sessions that we're up for _, s := range dt.allSessions() { if ds, ok := s.(*dynamicSession); ok { ds.onTunnelUp() } } dt.established = true dt.parent.handleUserEvent(&TunnelUpEvent{ TunnelName: dt.getName(), Tunnel: dt, Config: dt.cfg, LocalAddress: dt.sal, PeerAddress: dt.sap, }) } func (dt *dynamicTunnel) sendScccn() error { msg, err := newV2Scccn(dt.cfg) if err != nil { return err } return dt.xport.send(msg) } func (dt *dynamicTunnel) fsmActSendStopccn(args []interface{}) { rc := fsmArgsToStopccnResult(args) // Ignore tx error since we're going to close in any case _ = dt.sendStopccn(rc) dt.fsmActClose(args) } func (dt *dynamicTunnel) sendStopccn(rc *resultCode) error { msg, err := newV2Stopccn(rc, dt.cfg) if err != nil { return err } return dt.xport.send(msg) } // Implementes stopccn pend timeout as per RFC2661 section 5.7. // // While pending the timeout we ignore further messages, but // continue to drain the transport in order to allow messages to // be ACKed. func (dt *dynamicTunnel) fsmActOnStopccn(args []interface{}) { level.Debug(dt.logger).Log( "message", "pending for stopccn retransmit period", "timeout", dt.cfg.StopCCNTimeout) timeout := time.NewTimer(dt.cfg.StopCCNTimeout) for { select { case <-timeout.C: dt.fsmActClose(args) return case <-dt.xport.recvChan: } } } func (dt *dynamicTunnel) fsmActLinkSession(args []interface{}) { ds := fsmArgsToSession(args) dt.linkSession(ds) } func (dt *dynamicTunnel) fsmActStartSession(args []interface{}) { ds := fsmArgsToSession(args) dt.linkSession(ds) ds.onTunnelUp() } func (dt *dynamicTunnel) fsmActForwardSessionMsg(args []interface{}) { msg, _ := fsmArgsToV2MsgFrom(args) if s, ok := dt.findSessionByID(ControlConnID(msg.Sid())); ok { if ds, ok := s.(*dynamicSession); ok { ds.handleCtlMsg(msg) } } else { // TODO: on receipt of ICRQ we'll end up here; to handle this // we'd need to be able to create an LNS-mode session instance level.Error(dt.logger).Log( "message", "received session message for unknown session", "message_type", msg.getType(), "session ID", msg.Sid()) } } // Closes all tunnel resources and unlinks child sessions. // The tunnel goroutine will terminate after this call completes // because the transport recv channel will have been closed. func (dt *dynamicTunnel) fsmActClose(args []interface{}) { if dt != nil { dt.closingLock.Lock() defer dt.closingLock.Unlock() if dt.isClosing { return } dt.isClosing = true dt.closeAllSessions() if dt.dp != nil { err := dt.dp.Down() if err != nil { level.Error(dt.logger).Log("message", "dataplane down failed", "error", err) } } if dt.xport != nil { dt.xport.close() } if dt.cp != nil { dt.cp.close() } if dt.established { dt.established = false dt.parent.handleUserEvent(&TunnelDownEvent{ TunnelName: dt.getName(), Tunnel: dt, Config: dt.cfg, LocalAddress: dt.sal, PeerAddress: dt.sap, }) } dt.parent.unlinkTunnel(dt) level.Info(dt.logger).Log("message", "close") } } // Create a new client/LAC mode tunnel instance running the full control protocol func newDynamicTunnel(name string, parent *Context, sal, sap unix.Sockaddr, cfg *TunnelConfig) (dt *dynamicTunnel, err error) { // Currently only handle L2TPv2 if cfg.Version != ProtocolVersion2 { return nil, fmt.Errorf("L2TPv3 dynamic tunnels are not (yet) supported") } dt = &dynamicTunnel{ baseTunnel: newBaseTunnel( log.With(parent.logger, "tunnel_name", name), name, parent, cfg), sal: sal, sap: sap, closeChan: make(chan bool), sendChan: make(chan *sendMsg), eventChan: make(chan *eventArgs), } // Ref: RFC2661 section 7.2.1 dt.fsm = fsm{ current: "idle", table: []eventDesc{ // No other events possible in the idle state since we handle open to // kick off the FSM {from: "idle", events: []string{"open"}, cb: dt.fsmActSendSccrq, to: "waitctlreply"}, // waitctlreply is for when we've sent an sccrq to the peer and are waiting on the reply {from: "waitctlreply", events: []string{"sccrp"}, cb: dt.fsmActOnSccrp, to: "established"}, {from: "waitctlreply", events: []string{"stopccn"}, cb: dt.fsmActOnStopccn, to: "dead"}, {from: "waitctlreply", events: []string{"newsession"}, cb: dt.fsmActLinkSession, to: "waitctlreply"}, // TODO: don't really expect session messages: OK to ignore? {from: "waitctlreply", events: []string{"sessionmsg"}, cb: nil, to: "waitctlreply"}, { from: "waitctlreply", events: []string{ "sccrq", "scccn", "close", }, cb: dt.fsmActSendStopccn, to: "dead", }, // established is for once the tunnel three-way handshake is complete {from: "established", events: []string{"stopccn"}, cb: dt.fsmActOnStopccn, to: "dead"}, {from: "established", events: []string{"newsession"}, cb: dt.fsmActStartSession, to: "established"}, {from: "established", events: []string{"sessionmsg"}, cb: dt.fsmActForwardSessionMsg, to: "established"}, { from: "established", events: []string{ "sccrq", "sccrp", "scccn", "close", }, cb: dt.fsmActSendStopccn, to: "dead", }, }, } dt.cp, err = newL2tpControlPlane(sal, sap) if err != nil { dt.Close() return nil, err } err = dt.cp.bind() if err != nil { dt.Close() return nil, err } dt.xport, err = newTransport(dt.logger, dt.cp, transportConfig{ HelloTimeout: dt.cfg.HelloTimeout, TxWindowSize: dt.cfg.WindowSize, MaxRetries: dt.cfg.MaxRetries, RetryTimeout: dt.cfg.RetryTimeout, AckTimeout: time.Millisecond * 100, Version: dt.cfg.Version, PeerControlConnID: dt.cfg.PeerTunnelID, }) if err != nil { dt.Close() return nil, err } dt.wg.Add(1) go dt.runTunnel() return } golang-github-katalix-go-l2tp-0.1.7/l2tp/l2tp_quiescent.go000066400000000000000000000063631456471064000233530ustar00rootroot00000000000000package l2tp import ( "fmt" "sync" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "golang.org/x/sys/unix" ) type quiescentTunnel struct { *baseTunnel sal, sap unix.Sockaddr cp *controlPlane xport *transport dp TunnelDataPlane closeChan chan bool wg sync.WaitGroup } func (qt *quiescentTunnel) NewSession(name string, cfg *SessionConfig) (Session, error) { // Must have configuration if cfg == nil { return nil, fmt.Errorf("invalid nil config") } // Duplicate the configuration so we don't modify the user's copy myCfg := *cfg if _, ok := qt.findSessionByName(name); ok { return nil, fmt.Errorf("already have session %q", name) } if _, ok := qt.findSessionByID(cfg.SessionID); ok { return nil, fmt.Errorf("already have session %q", cfg.SessionID) } s, err := newStaticSession(name, qt, &myCfg) if err != nil { return nil, err } qt.linkSession(s) return s, nil } func (qt *quiescentTunnel) Close() { if qt != nil { close(qt.closeChan) qt.wg.Wait() qt.close() } } func (qt *quiescentTunnel) close() { if qt != nil { qt.baseTunnel.closeAllSessions() if qt.xport != nil { qt.xport.close() } if qt.cp != nil { qt.cp.close() } if qt.dp != nil { err := qt.dp.Down() level.Error(qt.logger).Log("message", "dataplane down failed", "error", err) } qt.parent.unlinkTunnel(qt) level.Info(qt.logger).Log("message", "close") } } func (qt *quiescentTunnel) xportReader() { // Although we're not running the control protocol we do need // to drain messages from the transport to avoid the receive // path blocking. defer qt.wg.Done() for { select { case <-qt.closeChan: return case _, ok := <-qt.xport.recvChan: if !ok { qt.close() return } } } } func newQuiescentTunnel(name string, parent *Context, sal, sap unix.Sockaddr, cfg *TunnelConfig) (qt *quiescentTunnel, err error) { qt = &quiescentTunnel{ baseTunnel: newBaseTunnel( log.With(parent.logger, "tunnel_name", name), name, parent, cfg), sal: sal, sap: sap, closeChan: make(chan bool), } // Initialise the control plane. // We bind/connect immediately since we're not runnning most of the control protocol. qt.cp, err = newL2tpControlPlane(sal, sap) if err != nil { qt.Close() return nil, err } err = qt.cp.bind() if err != nil { qt.Close() return nil, err } err = qt.cp.connect() if err != nil { qt.Close() return nil, err } qt.dp, err = parent.dp.NewTunnel(qt.cfg, qt.sal, qt.sap, qt.cp.fd) if err != nil { qt.Close() return nil, err } qt.xport, err = newTransport(qt.logger, qt.cp, transportConfig{ HelloTimeout: qt.cfg.HelloTimeout, TxWindowSize: qt.cfg.WindowSize, MaxRetries: qt.cfg.MaxRetries, RetryTimeout: qt.cfg.RetryTimeout, AckTimeout: time.Millisecond * 100, Version: qt.cfg.Version, PeerControlConnID: qt.cfg.PeerTunnelID, }) if err != nil { qt.Close() return nil, err } qt.wg.Add(1) go qt.xportReader() level.Info(qt.logger).Log( "message", "new quiescent tunnel", "version", qt.cfg.Version, "encap", qt.cfg.Encap, "local", qt.cfg.Local, "peer", qt.cfg.Peer, "tunnel_id", qt.cfg.TunnelID, "peer_tunnel_id", qt.cfg.PeerTunnelID) return } golang-github-katalix-go-l2tp-0.1.7/l2tp/l2tp_static.go000066400000000000000000000070451456471064000226400ustar00rootroot00000000000000package l2tp import ( "fmt" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "golang.org/x/sys/unix" ) type staticTunnel struct { *baseTunnel dp TunnelDataPlane } type staticSession struct { *baseSession dp SessionDataPlane ifname string } func (st *staticTunnel) NewSession(name string, cfg *SessionConfig) (Session, error) { // Must have configuration if cfg == nil { return nil, fmt.Errorf("invalid nil config") } // Must have a non-zero session ID and peer session ID if cfg.SessionID == 0 { return nil, fmt.Errorf("session ID must be non-zero") } if cfg.PeerSessionID == 0 { return nil, fmt.Errorf("peer session ID must be non-zero") } // Clashes of name or session ID are not allowed if _, ok := st.findSessionByName(name); ok { return nil, fmt.Errorf("already have session %q", name) } if _, ok := st.findSessionByID(cfg.SessionID); ok { return nil, fmt.Errorf("already have session %q", cfg.SessionID) } // Duplicate the configuration so we don't modify the user's copy myCfg := *cfg s, err := newStaticSession(name, st, &myCfg) if err != nil { return nil, err } st.linkSession(s) return s, nil } func (st *staticTunnel) Close() { if st != nil { st.baseTunnel.closeAllSessions() if st.dp != nil { err := st.dp.Down() if err != nil { level.Error(st.logger).Log("message", "dataplane down failed", "error", err) } } st.parent.unlinkTunnel(st) level.Info(st.logger).Log("message", "close") } } func newStaticTunnel(name string, parent *Context, sal, sap unix.Sockaddr, cfg *TunnelConfig) (st *staticTunnel, err error) { st = &staticTunnel{ baseTunnel: newBaseTunnel( log.With(parent.logger, "tunnel_name", name), name, parent, cfg), } st.dp, err = parent.dp.NewTunnel(st.cfg, sal, sap, -1) if err != nil { st.Close() return nil, err } level.Info(st.logger).Log( "message", "new static tunnel", "version", cfg.Version, "encap", cfg.Encap, "local", cfg.Local, "peer", cfg.Peer, "tunnel_id", cfg.TunnelID, "peer_tunnel_id", cfg.PeerTunnelID) return } func newStaticSession(name string, parent tunnel, cfg *SessionConfig) (ss *staticSession, err error) { tid := parent.getCfg().TunnelID ptid := parent.getCfg().PeerTunnelID ss = &staticSession{ baseSession: newBaseSession( log.With(parent.getLogger(), "session_name", name), name, parent, cfg), } ss.dp, err = parent.getDP().NewSession(tid, ptid, ss.cfg) if err != nil { return nil, err } ss.ifname, err = ss.dp.GetInterfaceName() if err != nil { ss.dp.Down() return nil, err } level.Info(ss.logger).Log( "message", "new static session", "session_id", ss.cfg.SessionID, "peer_session_id", ss.cfg.PeerSessionID, "pseudowire", ss.cfg.Pseudowire) ss.parent.handleUserEvent(&SessionUpEvent{ TunnelName: ss.parent.getName(), Tunnel: ss.parent, TunnelConfig: ss.parent.getCfg(), SessionName: ss.getName(), Session: ss, SessionConfig: ss.cfg, InterfaceName: ss.ifname, }) return } func (ss *staticSession) Close() { if ss.dp != nil { err := ss.dp.Down() if err != nil { level.Error(ss.logger).Log("message", "dataplane down failed", "error", err) } } ss.parent.handleUserEvent(&SessionDownEvent{ TunnelName: ss.parent.getName(), Tunnel: ss.parent, TunnelConfig: ss.parent.getCfg(), SessionName: ss.getName(), Session: ss, SessionConfig: ss.cfg, InterfaceName: ss.ifname, }) ss.parent.unlinkSession(ss) level.Info(ss.logger).Log("message", "close") } func (ss *staticSession) kill() { ss.Close() } golang-github-katalix-go-l2tp-0.1.7/l2tp/l2tp_test.go000066400000000000000000000263611456471064000223320ustar00rootroot00000000000000package l2tp import ( "bytes" "fmt" "os" "os/exec" "os/user" "strings" "testing" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" ) // Must be called with root permissions func testQuiescentTunnels(t *testing.T) { cases := []struct { name string cfg TunnelConfig expectFail bool }{ { name: "reject L2TPv2 IP encap", cfg: TunnelConfig{ Local: "127.0.0.1:6000", Peer: "localhost:5000", Version: ProtocolVersion2, TunnelID: 1, PeerTunnelID: 1001, Encap: EncapTypeIP, }, // L2TPv2 doesn't support IP encap expectFail: true, }, { name: "reject L2TPv2 config with no tunnel IDs", cfg: TunnelConfig{ Local: "127.0.0.1:6000", Peer: "localhost:5000", Version: ProtocolVersion2, Encap: EncapTypeUDP, }, // Must call out tunnel IDs expectFail: true, }, { name: "reject L2TPv3 config with no tunnel IDs", cfg: TunnelConfig{ Local: "127.0.0.1:6000", Peer: "localhost:5000", Version: ProtocolVersion3, Encap: EncapTypeUDP, }, // Must call out control connection IDs expectFail: true, }, { name: "L2TPv2 UDP AF_INET", cfg: TunnelConfig{ Local: "127.0.0.1:6000", Peer: "localhost:5000", Version: ProtocolVersion2, TunnelID: 1, PeerTunnelID: 1001, Encap: EncapTypeUDP, }, }, { name: "L2TPv2 UDP AF_INET6", cfg: TunnelConfig{ Local: "[::1]:6000", Peer: "[::1]:5000", Version: ProtocolVersion2, TunnelID: 2, PeerTunnelID: 1002, Encap: EncapTypeUDP, }, }, { name: "L2TPv3 UDP AF_INET", cfg: TunnelConfig{ Local: "127.0.0.1:6000", Peer: "localhost:5000", Version: ProtocolVersion3, TunnelID: 3, PeerTunnelID: 1003, Encap: EncapTypeUDP, }, }, { name: "L2TPv3 UDP AF_INET6", cfg: TunnelConfig{ Local: "[::1]:6000", Peer: "[::1]:5000", Version: ProtocolVersion3, TunnelID: 4, PeerTunnelID: 1004, Encap: EncapTypeUDP, }, }, { name: "L2TPv3 IP AF_INET", cfg: TunnelConfig{ Local: "127.0.0.1:6000", Peer: "localhost:5000", Version: ProtocolVersion3, TunnelID: 5, PeerTunnelID: 1005, Encap: EncapTypeIP, }, }, { name: "L2TPv3 IP AF_INET6", cfg: TunnelConfig{ Local: "[::1]:6000", Peer: "[::1]:5000", Version: ProtocolVersion3, TunnelID: 6, PeerTunnelID: 1006, Encap: EncapTypeIP, }, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { ctx, err := NewContext( LinuxNetlinkDataPlane, level.NewFilter(log.NewLogfmtLogger(os.Stderr), level.AllowDebug(), level.AllowInfo())) if err != nil { t.Fatalf("NewContext(): %v", err) } defer ctx.Close() _, err = ctx.NewQuiescentTunnel("t1", &c.cfg) if c.expectFail { if err == nil { t.Fatalf("Expected NewQuiescentTunnel(%v) to fail", c.cfg) } } else { if err != nil { t.Fatalf("NewQuiescentTunnel(%v): %v", c.cfg, err) } err = checkTunnel(&c.cfg) if err != nil { t.Errorf("NewQuiescentTunnel(%v): failed to validate: %v", c.cfg, err) } } }) } } // Must be called with root permissions func testQuiescentSessions(t *testing.T) { cases := []struct { name string tcfg TunnelConfig scfg SessionConfig }{ { name: "L2TPv3 Eth Session", tcfg: TunnelConfig{ Local: "127.0.0.1:6000", Peer: "localhost:5000", TunnelID: 5003, PeerTunnelID: 6003, Encap: EncapTypeIP, Version: ProtocolVersion3, }, scfg: SessionConfig{ SessionID: 500001, PeerSessionID: 500002, Pseudowire: PseudowireTypeEth, // FIXME: currently causes nl create to fail with EINVAL //InterfaceName: "l2tpeth42", }, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { ctx, err := NewContext( LinuxNetlinkDataPlane, level.NewFilter(log.NewLogfmtLogger(os.Stderr), level.AllowDebug(), level.AllowInfo())) if err != nil { t.Fatalf("NewContext(): %v", err) } defer ctx.Close() tunl, err := ctx.NewQuiescentTunnel("t1", &c.tcfg) if err != nil { t.Fatalf("NewQuiescentTunnel(%v): %v", c.tcfg, err) } _, err = tunl.NewSession("s1", &c.scfg) if err != nil { t.Fatalf("NewSession(%v): %v", c.scfg, err) } err = checkSession(&c.tcfg, &c.scfg) if err != nil { t.Fatalf("NewSession(%v): failed to validate: %v", c.scfg, err) } }) } } // Must be called with root permissions func testStaticTunnels(t *testing.T) { cases := []struct { name string cfg TunnelConfig expectFail bool }{ { name: "reject L2TPv3 config with no tunnel IDs", cfg: TunnelConfig{ Local: "127.0.0.1:6000", Peer: "localhost:5000", Encap: EncapTypeUDP, Version: ProtocolVersion3, }, // Must call out control connection IDs expectFail: true, }, { name: "L2TPv3 UDP AF_INET", cfg: TunnelConfig{ Local: "127.0.0.1:6000", Peer: "localhost:5000", TunnelID: 5001, PeerTunnelID: 6001, Encap: EncapTypeUDP, Version: ProtocolVersion3, }, }, { name: "L2TPv3 UDP AF_INET6", cfg: TunnelConfig{ Local: "[::1]:6000", Peer: "[::1]:5000", TunnelID: 5002, PeerTunnelID: 6002, Encap: EncapTypeUDP, Version: ProtocolVersion3, }, }, { name: "L2TPv3 IP AF_INET", cfg: TunnelConfig{ Local: "127.0.0.1:6000", Peer: "localhost:5000", TunnelID: 5003, PeerTunnelID: 6003, Encap: EncapTypeIP, Version: ProtocolVersion3, }, }, { name: "L2TPv3 IP AF_INET6", cfg: TunnelConfig{ Local: "[::1]:6000", Peer: "[::1]:5000", TunnelID: 5004, PeerTunnelID: 6004, Encap: EncapTypeIP, Version: ProtocolVersion3, }, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { ctx, err := NewContext( LinuxNetlinkDataPlane, level.NewFilter(log.NewLogfmtLogger(os.Stderr), level.AllowDebug(), level.AllowInfo())) if err != nil { t.Fatalf("NewContext(): %v", err) } defer ctx.Close() _, err = ctx.NewStaticTunnel("t1", &c.cfg) if c.expectFail { if err == nil { t.Fatalf("Expected NewStaticTunnel(%v) to fail", c.cfg) } } else { if err != nil { t.Fatalf("NewStaticTunnel(%v): %v", c.cfg, err) } err = checkTunnel(&c.cfg) if err != nil { t.Errorf("NewStaticTunnel(%v): failed to validate: %v", c.cfg, err) } } }) } } // Must be called with root permissions func testStaticSessions(t *testing.T) { cases := []struct { name string tcfg TunnelConfig scfg SessionConfig }{ { name: "L2TPv3 Eth Session", tcfg: TunnelConfig{ Local: "127.0.0.1:6000", Peer: "localhost:5000", TunnelID: 5003, PeerTunnelID: 6003, Encap: EncapTypeIP, Version: ProtocolVersion3, }, scfg: SessionConfig{ SessionID: 500001, PeerSessionID: 500002, Pseudowire: PseudowireTypeEth, // FIXME: currently causes nl create to fail with EINVAL //InterfaceName: "l2tpeth42", }, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { ctx, err := NewContext( LinuxNetlinkDataPlane, level.NewFilter(log.NewLogfmtLogger(os.Stderr), level.AllowDebug(), level.AllowInfo())) if err != nil { t.Fatalf("NewContext(): %v", err) } defer ctx.Close() tunl, err := ctx.NewStaticTunnel("t1", &c.tcfg) if err != nil { t.Fatalf("NewStaticTunnel(%v): %v", c.tcfg, err) } _, err = tunl.NewSession("s1", &c.scfg) if err != nil { t.Fatalf("NewSession(%v): %v", c.scfg, err) } err = checkSession(&c.tcfg, &c.scfg) if err != nil { t.Fatalf("NewSession(%v): failed to validate: %v", c.scfg, err) } }) } } func TestRequiresRoot(t *testing.T) { // These tests need root permissions, so verify we have those first of all user, err := user.Current() if err != nil { t.Errorf("Unable to obtain current user: %q", err) } if user.Uid != "0" { t.Skip("skipping test because we don't have root permissions") } tests := []struct { name string testFn func(t *testing.T) }{ { name: "QuiescentTunnels", testFn: testQuiescentTunnels, }, { name: "QuiescentSessions", testFn: testQuiescentSessions, }, { name: "StaticTunnels", testFn: testStaticTunnels, }, { name: "StaticSessions", testFn: testStaticSessions, }, } for _, sub := range tests { t.Run(sub.name, sub.testFn) } } func ipL2tpShowTunnel(tid uint32) (out string, err error) { var tidStr string var tidArgStr string var sout bytes.Buffer if tid > 0 { tidArgStr = "tunnel_id" tidStr = fmt.Sprintf("%d", tid) } cmd := exec.Command("sudo", "ip", "l2tp", "show", "tunnel", tidArgStr, tidStr) cmd.Stdout = &sout err = cmd.Run() if err != nil { return "", err } fmt.Println(sout.String()) return sout.String(), nil } func ipL2tpShowSession(tid, sid uint32) (out string, err error) { var tidStr string var tidArgStr string var sidStr string var sidArgStr string var sout bytes.Buffer if tid > 0 { tidArgStr = "tunnel_id" tidStr = fmt.Sprintf("%d", tid) } if sid > 0 { sidArgStr = "session_id" sidStr = fmt.Sprintf("%d", sid) } cmd := exec.Command("sudo", "ip", "l2tp", "show", "session", tidArgStr, tidStr, sidArgStr, sidStr) cmd.Stdout = &sout err = cmd.Run() if err != nil { return "", err } fmt.Println(sout.String()) return sout.String(), nil } func validateIPL2tpTunnelOut(out string, tid, ptid uint32, encap EncapType) error { expect := []string{ fmt.Sprintf("Tunnel %v,", tid), fmt.Sprintf("encap %v", encap), fmt.Sprintf("Peer tunnel %v", ptid), } for _, e := range expect { if !strings.Contains(out, e) { return fmt.Errorf("failed to locate expected substring %q in output %q", e, out) } } return nil } func validateIPL2tpSessionOut(out string, tid, sid, ptid, psid uint32, ifnam string) error { expect := []string{ fmt.Sprintf("Session %v in", sid), fmt.Sprintf("in tunnel %v", tid), fmt.Sprintf("Peer session %v,", psid), fmt.Sprintf(", tunnel %v", ptid), } if ifnam != "" { expect = append(expect, fmt.Sprintf("interface name: %v", ifnam)) } for _, e := range expect { if !strings.Contains(out, e) { return fmt.Errorf("failed to locate expected substring %q in output %q", e, out) } } return nil } func checkSession(tcfg *TunnelConfig, scfg *SessionConfig) error { tid := uint32(tcfg.TunnelID) sid := uint32(scfg.SessionID) ptid := uint32(tcfg.PeerTunnelID) psid := uint32(scfg.PeerSessionID) out, err := ipL2tpShowSession(tid, sid) if err != nil { return fmt.Errorf("ip l2tp couldn't show session %v/%v: %v", tid, sid, err) } return validateIPL2tpSessionOut(out, tid, sid, ptid, psid, scfg.InterfaceName) } func checkTunnel(cfg *TunnelConfig) error { tid := uint32(cfg.TunnelID) ptid := uint32(cfg.PeerTunnelID) out, err := ipL2tpShowTunnel(tid) if err != nil { return fmt.Errorf("ip l2tp couldn't show tunnel %v: %v", tid, err) } return validateIPL2tpTunnelOut(out, tid, ptid, cfg.Encap) } golang-github-katalix-go-l2tp-0.1.7/l2tp/msg.go000066400000000000000000000511211456471064000211700ustar00rootroot00000000000000package l2tp import ( "bytes" "encoding/binary" "errors" "fmt" "io" ) // L2TPv2 and L2TPv3 headers have these fields in common type l2tpCommonHeader struct { FlagsVer uint16 Len uint16 } // L2TPv2 control message header per RFC2661 type l2tpV2Header struct { Common l2tpCommonHeader Tid uint16 Sid uint16 Ns uint16 Nr uint16 } // L2TPv3 control message header per RFC3931 type l2tpV3Header struct { Common l2tpCommonHeader Ccid uint32 Ns uint16 Nr uint16 } const ( controlMessageMinLen = 12 controlMessageMaxLen = ^uint16(0) commonHeaderLen = 4 v2HeaderLen = 12 v3HeaderLen = 12 ) // Message AVP specification as per RFCx type avpSpec int type msgSpec struct { m map[avpType]avpSpec } const ( mustExist avpSpec = 1 mayExist avpSpec = 2 ) func (spec *msgSpec) hasAvp(t avpType) (avpSpec, bool) { as, ok := spec.m[t] return as, ok } func v2SccrqMsgSpec() *msgSpec { /* Ref: RFC2661 section 6.1 */ spec := msgSpec{make(map[avpType]avpSpec)} spec.m[avpTypeMessage] = mustExist spec.m[avpTypeProtocolVersion] = mustExist spec.m[avpTypeHostName] = mustExist spec.m[avpTypeFramingCap] = mustExist spec.m[avpTypeTunnelID] = mustExist spec.m[avpTypeBearerCap] = mayExist spec.m[avpTypeRxWindowSize] = mayExist spec.m[avpTypeChallenge] = mayExist spec.m[avpTypeTiebreaker] = mayExist spec.m[avpTypeFirmwareRevision] = mayExist spec.m[avpTypeVendorName] = mayExist return &spec } func v2SccrpMsgSpec() *msgSpec { /* Ref: RFC2661 section 6.2 */ spec := msgSpec{make(map[avpType]avpSpec)} spec.m[avpTypeMessage] = mustExist spec.m[avpTypeProtocolVersion] = mustExist spec.m[avpTypeFramingCap] = mustExist spec.m[avpTypeHostName] = mustExist spec.m[avpTypeTunnelID] = mustExist spec.m[avpTypeBearerCap] = mayExist spec.m[avpTypeRxWindowSize] = mayExist spec.m[avpTypeChallenge] = mayExist spec.m[avpTypeChallengeResponse] = mayExist spec.m[avpTypeTiebreaker] = mayExist spec.m[avpTypeFirmwareRevision] = mayExist spec.m[avpTypeVendorName] = mayExist return &spec } func v2ScccnMsgSpec() *msgSpec { /* Ref: RFC2661 section 6.3 */ spec := msgSpec{make(map[avpType]avpSpec)} spec.m[avpTypeMessage] = mustExist spec.m[avpTypeChallengeResponse] = mayExist return &spec } func v2StopccnMsgSpec() *msgSpec { /* Ref: RFC2661 section 6.4 */ spec := msgSpec{make(map[avpType]avpSpec)} spec.m[avpTypeMessage] = mustExist spec.m[avpTypeTunnelID] = mustExist spec.m[avpTypeResultCode] = mustExist return &spec } func v2HelloMsgSpec() *msgSpec { /* Ref: RFC2661 section 6.5 */ spec := msgSpec{make(map[avpType]avpSpec)} spec.m[avpTypeMessage] = mustExist return &spec } func v2IcrqMsgSpec() *msgSpec { /* Ref: RFC2661 section 6.6 */ spec := msgSpec{make(map[avpType]avpSpec)} spec.m[avpTypeMessage] = mustExist spec.m[avpTypeSessionID] = mustExist spec.m[avpTypeCallSerialNumber] = mustExist spec.m[avpTypeBearerType] = mayExist spec.m[avpTypePhysicalChannelID] = mayExist spec.m[avpTypeCallingNumber] = mayExist spec.m[avpTypeCalledNumber] = mayExist spec.m[avpTypeSubAddress] = mayExist return &spec } func v2IcrpMsgSpec() *msgSpec { /* Ref: RFC2661 section 6.7 */ spec := msgSpec{make(map[avpType]avpSpec)} spec.m[avpTypeMessage] = mustExist spec.m[avpTypeSessionID] = mustExist return &spec } func v2IccnMsgSpec() *msgSpec { /* Ref: RFC2661 section 6.8 */ spec := msgSpec{make(map[avpType]avpSpec)} spec.m[avpTypeMessage] = mustExist spec.m[avpTypeConnectSpeed] = mustExist spec.m[avpTypeFramingType] = mustExist spec.m[avpTypeInitialRcvdLcpConfreq] = mayExist spec.m[avpTypeLastSentLcpConfreq] = mayExist spec.m[avpTypeLastRcvdLcpConfreq] = mayExist spec.m[avpTypeProxyAuthType] = mayExist spec.m[avpTypeProxyAuthName] = mayExist spec.m[avpTypeProxyAuthChallenge] = mayExist spec.m[avpTypeProxyAuthID] = mayExist spec.m[avpTypeProxyAuthResponse] = mayExist spec.m[avpTypePrivGroupID] = mayExist spec.m[avpTypeRxConnectSpeed] = mayExist spec.m[avpTypeSequencingRequired] = mayExist return &spec } func v2CdnMsgSpec() *msgSpec { /* Ref: RFC2661 section 6.12 */ spec := msgSpec{make(map[avpType]avpSpec)} spec.m[avpTypeMessage] = mustExist spec.m[avpTypeResultCode] = mustExist spec.m[avpTypeSessionID] = mustExist spec.m[avpTypeQ931CauseCode] = mayExist return &spec } func getV2MsgSpec(t avpMsgType) (*msgSpec, error) { switch t { case avpMsgTypeSccrq: return v2SccrqMsgSpec(), nil case avpMsgTypeSccrp: return v2SccrpMsgSpec(), nil case avpMsgTypeScccn: return v2ScccnMsgSpec(), nil case avpMsgTypeStopccn: return v2StopccnMsgSpec(), nil case avpMsgTypeHello: return v2HelloMsgSpec(), nil case avpMsgTypeIcrq: return v2IcrqMsgSpec(), nil case avpMsgTypeIcrp: return v2IcrpMsgSpec(), nil case avpMsgTypeIccn: return v2IccnMsgSpec(), nil case avpMsgTypeCdn: return v2CdnMsgSpec(), nil } return nil, fmt.Errorf("no specification for v2 message %v", t) } func v3HelloMsgSpec() *msgSpec { /* Ref: RFC3931 section 6.5 */ spec := msgSpec{make(map[avpType]avpSpec)} spec.m[avpTypeMessage] = mustExist spec.m[avpTypeRandomVector] = mayExist spec.m[avpTypeMessageDigest] = mayExist return &spec } func getV3MsgSpec(t avpMsgType) (*msgSpec, error) { switch t { case avpMsgTypeHello: return v3HelloMsgSpec(), nil } return nil, fmt.Errorf("no specification for v3 message %v", t) } func (h *l2tpCommonHeader) protocolVersion() (version ProtocolVersion, err error) { switch h.FlagsVer & 0xf { case 2: return ProtocolVersion2, nil case 3: return ProtocolVersion3, nil } return 0, errors.New("illegal protocol version") } func validateAvps(avps []avp, spec *msgSpec) error { seen := make(map[avpType]bool) for at, as := range spec.m { if as == mustExist { seen[at] = false } } for _, avp := range avps { as, ok := spec.hasAvp(avp.getType()) if !ok { // RFC2661 section 4.1 says we MUST tear down the tunnel on receipt of // an unrecognised AVP with the M bit set. // And we MUST ignore an unrecognised AVP with the M bit unset. if avp.isMandatory() { return fmt.Errorf("unexpected AVP %v", avp.getType()) } continue } if as == mustExist { seen[avp.getType()] = true } _, err := avp.decode() if err != nil { return fmt.Errorf("failed to decode AVP %v: %v", avp.getType(), err) } } // ensure we saw all the AVPs we must have for at, ok := range seen { if !ok { return fmt.Errorf("missing mandatory AVP %v", at) } } return nil } func newL2tpV2MessageHeader(tid, sid, ns, nr uint16, payloadBytes int) *l2tpV2Header { return &l2tpV2Header{ Common: l2tpCommonHeader{ FlagsVer: 0xc802, Len: uint16(v2HeaderLen + payloadBytes), }, Tid: tid, Sid: sid, Ns: ns, Nr: nr, } } func newL2tpV3MessageHeader(ccid uint32, ns, nr uint16, payloadBytes int) *l2tpV3Header { return &l2tpV3Header{ Common: l2tpCommonHeader{ FlagsVer: 0xc803, Len: uint16(v3HeaderLen + payloadBytes), }, Ccid: ccid, Ns: ns, Nr: nr, } } func bytesToV2CtlMsg(b []byte) (msg *v2ControlMessage, err error) { var hdr l2tpV2Header var avps []avp r := bytes.NewReader(b) if err = binary.Read(r, binary.BigEndian, &hdr); err != nil { return nil, err } // Messages with no AVP payload are treated as ZLB (zero-length-body) ack messages, // so they're valid L2TPv2 messages. Don't try to parse the AVP payload in this case. if hdr.Common.Len > v2HeaderLen { if avps, err = parseAVPBuffer(b[v2HeaderLen:hdr.Common.Len]); err != nil { return nil, err } // RFC2661 says the first AVP in the message MUST be the Message Type AVP, // so let's validate that now. if avps[0].getType() != avpTypeMessage { return nil, errors.New("invalid L2TPv2 message: first AVP is not Message Type AVP") } } return &v2ControlMessage{ header: hdr, avps: avps, }, nil } func bytesToV3CtlMsg(b []byte) (msg *v3ControlMessage, err error) { var hdr l2tpV3Header var avps []avp r := bytes.NewReader(b) if err = binary.Read(r, binary.BigEndian, &hdr); err != nil { return nil, err } if avps, err = parseAVPBuffer(b[v3HeaderLen:hdr.Common.Len]); err != nil { return nil, err } // RFC3931 says the first AVP in the message MUST be the Message Type AVP, // so let's validate that now if avps[0].getType() != avpTypeMessage { return nil, errors.New("invalid L2TPv3 message: first AVP is not Message Type AVP") } return &v3ControlMessage{ header: hdr, avps: avps, }, nil } // controlMessage is an interface representing a generic L2TP // control message, providing access to the fields that are common // to both v2 and v3 versions of the protocol. type controlMessage interface { // protocolVersion returns the protocol version for the control message. protocolVersion() ProtocolVersion // getLen returns the total control message length, including the header, in octets. getLen() int // ns returns the L2TP transport Ns value for the message. ns() uint16 // nr returns the L2TP transport NR value for the message. nr() uint16 // getAvps returns the slice of Attribute Value Pair (AVP) values held by the control message. getAvps() []avp // getType returns the value of the Message Type AVP. getType() avpMsgType // appendAvp appends an AVP to the message. appendAvp(avp *avp) // setTransportSeqNum sets the header sequence numbers. setTransportSeqNum(ns, nr uint16) // toBytes encodes the message as bytes for transmission. toBytes() ([]byte, error) // validate the message AVPs, checking that the mandatory AVPs are // present and contain the expected data. validate() error } // v2ControlMessage represents an RFC2661 control message type v2ControlMessage struct { header l2tpV2Header avps []avp } // v3ControlMessage represents an RFC3931 control message type v3ControlMessage struct { header l2tpV3Header avps []avp } func (m *v2ControlMessage) protocolVersion() ProtocolVersion { return ProtocolVersion2 } func (m *v2ControlMessage) getLen() int { return int(m.header.Common.Len) } func (m *v2ControlMessage) ns() uint16 { return m.header.Ns } func (m *v2ControlMessage) nr() uint16 { return m.header.Nr } func (m *v2ControlMessage) getAvps() []avp { return m.avps } func (m v2ControlMessage) getType() avpMsgType { // Messages with no AVP payload are treated as ZLB (zero-length-body) // ack messages in RFC2661. Strictly speaking ZLBs have no message type, // so we (ab)use the L2TPv3 AvpMsgTypeAck for that scenario. if len(m.getAvps()) == 0 { return avpMsgTypeAck } avp := m.getAvps()[0] // c.f. newv2ControlMessage: we've validated this condition at message // creation time, so this is just a belt/braces assertation to catch // programming errors during development if avp.getType() != avpTypeMessage { panic("Invalid L2TPv2 message") } mt, err := avp.decodeMsgType() if err != nil { panic(fmt.Sprintf("Failed to decode AVP message type: %v", err)) } return mt } func (m *v2ControlMessage) Tid() uint16 { return m.header.Tid } func (m *v2ControlMessage) Sid() uint16 { return m.header.Sid } func (m *v2ControlMessage) appendAvp(avp *avp) { m.avps = append(m.avps, *avp) m.header.Common.Len += uint16(avp.totalLen()) } func (m *v2ControlMessage) setTransportSeqNum(ns, nr uint16) { m.header.Ns = ns m.header.Nr = nr } func (m *v2ControlMessage) toBytes() ([]byte, error) { buf := new(bytes.Buffer) if err := binary.Write(buf, binary.BigEndian, m.header); err != nil { return nil, err } for _, avp := range m.avps { if err := binary.Write(buf, binary.BigEndian, avp.header); err != nil { return nil, err } if err := binary.Write(buf, binary.BigEndian, avp.payload.data); err != nil { return nil, err } } return buf.Bytes(), nil } func (m *v2ControlMessage) validate() error { spec, err := getV2MsgSpec(m.getType()) if err != nil { return err } return validateAvps(m.avps, spec) } func (m *v3ControlMessage) protocolVersion() ProtocolVersion { return ProtocolVersion3 } func (m *v3ControlMessage) getLen() int { return int(m.header.Common.Len) } func (m *v3ControlMessage) ns() uint16 { return m.header.Ns } func (m *v3ControlMessage) nr() uint16 { return m.header.Nr } func (m *v3ControlMessage) getAvps() []avp { return m.avps } func (m v3ControlMessage) getType() avpMsgType { avp := m.getAvps()[0] // c.f. bytesToV2CtlMsg: we've validated this condition at message // creation time, so this is just a belt/braces assertation to catch // programming errors during development if avp.getType() != avpTypeMessage { panic("Invalid L2TPv3 message") } mt, err := avp.decodeMsgType() if err != nil { panic(fmt.Sprintf("Failed to decode AVP message type: %v", err)) } return mt } func (m *v3ControlMessage) ControlConnectionID() uint32 { return m.header.Ccid } func (m *v3ControlMessage) appendAvp(avp *avp) { m.avps = append(m.avps, *avp) m.header.Common.Len += uint16(avp.totalLen()) } func (m *v3ControlMessage) setTransportSeqNum(ns, nr uint16) { m.header.Ns = ns m.header.Nr = nr } func (m *v3ControlMessage) toBytes() ([]byte, error) { buf := new(bytes.Buffer) if err := binary.Write(buf, binary.BigEndian, m.header); err != nil { return nil, err } for _, avp := range m.avps { if err := binary.Write(buf, binary.BigEndian, avp.header); err != nil { return nil, err } if err := binary.Write(buf, binary.BigEndian, avp.payload.data); err != nil { return nil, err } } return buf.Bytes(), nil } func (m *v3ControlMessage) validate() error { spec, err := getV3MsgSpec(m.getType()) if err != nil { return err } return validateAvps(m.avps, spec) } // parseMessageBuffer takes a byte slice of L2TP control message data and // parses it into an array of controlMessage instances. func parseMessageBuffer(b []byte) (messages []controlMessage, err error) { r := bytes.NewReader(b) for r.Len() >= controlMessageMinLen { var ver ProtocolVersion var h l2tpCommonHeader var cursor int64 if cursor, err = r.Seek(0, io.SeekCurrent); err != nil { return nil, errors.New("malformed message buffer: unable to determine current offset") } // Read the common part of the header: this will tell us the // protocol version and the length of the complete frame if err := binary.Read(r, binary.BigEndian, &h); err != nil { return nil, err } // Throw out malformed packets if int(h.Len-commonHeaderLen) > r.Len() { return nil, fmt.Errorf("malformed header: length %d exceeds buffer bounds of %d", h.Len, r.Len()) } // Figure out the protocol version, and read the message if ver, err = h.protocolVersion(); err != nil { return nil, err } if ver == ProtocolVersion2 { var msg *v2ControlMessage if msg, err = bytesToV2CtlMsg(b[cursor : cursor+int64(h.Len)]); err != nil { return nil, err } messages = append(messages, msg) } else if ver == ProtocolVersion3 { var msg *v3ControlMessage if msg, err = bytesToV3CtlMsg(b[cursor : cursor+int64(+h.Len)]); err != nil { return nil, err } messages = append(messages, msg) } else { panic("Unhandled protocol version") } // Step on to the next message in the buffer, if any if _, err := r.Seek(int64(h.Len), io.SeekCurrent); err != nil { return nil, errors.New("malformed message buffer: invalid length for current message") } } return messages, nil } // newV2ControlMessage builds a new control message func newV2ControlMessage(tid ControlConnID, sid ControlConnID, avps []avp) (msg *v2ControlMessage, err error) { if tid > v2TidSidMax { return nil, fmt.Errorf("v2 tunnel ID %v out of range", tid) } if sid > v2TidSidMax { return nil, fmt.Errorf("v2 session ID %v out of range", sid) } return &v2ControlMessage{ header: *newL2tpV2MessageHeader(uint16(tid), uint16(sid), 0, 0, avpsLengthBytes(avps)), avps: avps, }, nil } type avpIn struct { typ avpType data interface{} } func buildV2Msg(ptid ControlConnID, psid ControlConnID, in []avpIn) (msg *v2ControlMessage, err error) { msg, err = newV2ControlMessage(ptid, psid, []avp{}) if err != nil { return } for _, i := range in { avp, err := newAvp(vendorIDIetf, i.typ, i.data) if err != nil { return nil, fmt.Errorf("failed to create AVP %v: %v", i.typ, err) } msg.appendAvp(avp) } return } // newV2Sccrq builds a new SCCRQ message func newV2Sccrq(cfg *TunnelConfig) (msg *v2ControlMessage, err error) { /* RFC2661 says we MUST include: - Message Type - Protocol Version - Host Name - Framing Capabilities - Assigned Tunnel ID and we MAY include: - Bearer Capabilities - Receive Window Size - Challenge - Tie Breaker - Firmware Revision - Vendor Name */ in := []avpIn{ {avpTypeMessage, avpMsgTypeSccrq}, {avpTypeProtocolVersion, []byte{1, 0}}, {avpTypeHostName, cfg.HostName}, {avpTypeFramingCap, uint32(cfg.FramingCaps)}, {avpTypeTunnelID, uint16(cfg.TunnelID)}, } return buildV2Msg(0, 0, in) } // newV2Sccrp builds a new SCCRP message func newV2Sccrp(cfg *TunnelConfig) (msg *v2ControlMessage, err error) { /* RFC2661 says we MUST include: - Message Type - Protocol Version - Framing Capabilities - Host Name - Assigned Tunnel ID and we MAY include: - Bearer Capabilities - Firmware Revision - Vendor Name - Receive Window Size - Challenge - Challenge Response */ in := []avpIn{ {avpTypeMessage, avpMsgTypeSccrp}, {avpTypeProtocolVersion, []byte{1, 0}}, {avpTypeFramingCap, uint32(cfg.FramingCaps)}, {avpTypeHostName, cfg.HostName}, {avpTypeTunnelID, uint16(cfg.TunnelID)}, } return buildV2Msg(cfg.PeerTunnelID, 0, in) } // newV2Scccn builds a new SCCCN message func newV2Scccn(cfg *TunnelConfig) (msg *v2ControlMessage, err error) { /* RFC2661 says we MUST include: - Message Type and we MAY include: - Challenge response */ in := []avpIn{ {avpTypeMessage, avpMsgTypeScccn}, } return buildV2Msg(cfg.PeerTunnelID, 0, in) } // newV2Stopccn builds a new StopCCN message func newV2Stopccn(rc *resultCode, cfg *TunnelConfig) (msg *v2ControlMessage, err error) { /* RFC2661 says we MUST include: - Message Type - Assigned Tunnel ID - Result Code */ in := []avpIn{ {avpTypeMessage, avpMsgTypeStopccn}, {avpTypeTunnelID, uint16(cfg.TunnelID)}, {avpTypeResultCode, rc}, } return buildV2Msg(cfg.PeerTunnelID, 0, in) } // newV2Hello builds a new HELLO message func newV2Hello(cfg *TunnelConfig) (msg *v2ControlMessage, err error) { /* RFC2661 says we MUST include: - Message Type */ in := []avpIn{ {avpTypeMessage, avpMsgTypeHello}, } return buildV2Msg(cfg.PeerTunnelID, 0, in) } // newV2Icrq builds a new ICRQ message func newV2Icrq(callSerial uint32, ptid ControlConnID, scfg *SessionConfig) (msg *v2ControlMessage, err error) { /* RFC2661 says we MUST include: - Message Type - Assigned Session ID - Call Serial Number and we MAY include: - Bearer Type - Physical Channel ID - Calling Number - Called Number - Sub-Address */ in := []avpIn{ {avpTypeMessage, avpMsgTypeIcrq}, {avpTypeSessionID, uint16(scfg.SessionID)}, {avpTypeCallSerialNumber, callSerial}, } return buildV2Msg(ptid, 0, in) } // newV2Icrp builds a new ICRP message func newV2Icrp(ptid ControlConnID, scfg *SessionConfig) (msg *v2ControlMessage, err error) { /* RFC2661 says we MUST include - Message Type - Assigned Session ID */ in := []avpIn{ {avpTypeMessage, avpMsgTypeIcrp}, {avpTypeSessionID, uint16(scfg.SessionID)}, } return buildV2Msg(ptid, scfg.PeerSessionID, in) } // newV2Iccn builds a new ICCN message func newV2Iccn(ptid ControlConnID, scfg *SessionConfig) (msg *v2ControlMessage, err error) { /* RFC2661 says we MUST include: - Message Type - (Tx) Connect Speed - Framing Type and we MAY include: - Initial Received LCP CONFREQ - Last Sent LCP CONFREQ - Last Received LCP CONFREQ - Proxy Authen Type - Proxy Authen Name - Proxy Authen Challenge - Proxy Authen ID - Proxy Authen Response - Private Group ID - Rx Connect Speed - Sequencing Required */ in := []avpIn{ {avpTypeMessage, avpMsgTypeIccn}, {avpTypeConnectSpeed, uint32(0)}, // TODO: config field? {avpTypeFramingType, uint32(FramingCapSync | FramingCapAsync)}, // TODO: config field? } return buildV2Msg(ptid, scfg.PeerSessionID, in) } // newV2Cdn builds a new CDN message func newV2Cdn(ptid ControlConnID, rc *resultCode, scfg *SessionConfig) (msg *v2ControlMessage, err error) { /* RFC2661 says we MUST include: - Message Type - Result Code - Assigned Session ID and we MAY include: - Q.931 Cause Code */ in := []avpIn{ {avpTypeMessage, avpMsgTypeCdn}, {avpTypeResultCode, rc}, {avpTypeSessionID, uint16(scfg.SessionID)}, } return buildV2Msg(ptid, scfg.PeerSessionID, in) } // newV3ControlMessage builds a new control message func newV3ControlMessage(ccid ControlConnID, avps []avp) (msg *v3ControlMessage, err error) { return &v3ControlMessage{ header: *newL2tpV3MessageHeader(uint32(ccid), 0, 0, avpsLengthBytes(avps)), avps: avps, }, nil } golang-github-katalix-go-l2tp-0.1.7/l2tp/msg_test.go000066400000000000000000000161631456471064000222360ustar00rootroot00000000000000package l2tp import ( "bytes" "testing" ) type msgInfo struct { version ProtocolVersion ns, nr, tid, sid uint16 ccid uint32 navps int msgType avpMsgType } func TestParseMessageBuffer(t *testing.T) { cases := []struct { in []byte want []msgInfo }{ { in: []byte{ 0xc8, 0x02, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, }, want: []msgInfo{ {version: ProtocolVersion2, ns: 1, nr: 1, tid: 1, sid: 0, navps: 1, msgType: avpMsgTypeHello}, }, }, { in: []byte{ 0xc8, 0x02, 0x00, 0x0c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, }, want: []msgInfo{ {version: ProtocolVersion2, tid: 1, sid: 0, ns: 1, nr: 1, navps: 0, msgType: avpMsgTypeAck}, }, }, { in: []byte{ 0xc8, 0x03, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x0c, 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x65, 0x6e, 0x76, 0x33, 0x00, 0x34, 0x00, 0x00, 0x00, 0x08, 0x70, 0x72, 0x6f, 0x6c, 0x32, 0x74, 0x70, 0x20, 0x31, 0x2e, 0x37, 0x2e, 0x33, 0x20, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x2d, 0x33, 0x2e, 0x31, 0x33, 0x2e, 0x30, 0x2d, 0x33, 0x30, 0x2d, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x20, 0x28, 0x78, 0x38, 0x36, 0x5f, 0x36, 0x34, 0x29, 0x80, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x3d, 0x28, 0x46, 0xf1, 0x81, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x07, 0x00, 0x05, 0x00, 0x04, }, want: []msgInfo{ {version: ProtocolVersion3, ns: 0, nr: 0, ccid: 0, navps: 7, msgType: avpMsgTypeSccrq}, }, }, } for _, c := range cases { got, err := parseMessageBuffer(c.in) if err == nil { for i, g := range got { // common checks if g.protocolVersion() != c.want[i].version { t.Errorf("ProtocolVersion() == %q, want %q", g.protocolVersion(), c.want[i].version) } if g.getLen() != len(c.in) { t.Errorf("Len() == %q, want %q", g.getLen(), len(c.in)) } if g.ns() != c.want[i].ns { t.Errorf("Ns() == %q, want %q", g.ns(), c.want[i].ns) } if g.nr() != c.want[i].nr { t.Errorf("Nr() == %q, want %q", g.nr(), c.want[i].nr) } if len(g.getAvps()) != c.want[i].navps { t.Errorf("AVP count failed: got %q, want %q", len(g.getAvps()), c.want[i].navps) } if g.getType() != c.want[i].msgType { t.Errorf("Type() == %q, want %q", g.getType(), c.want[i].msgType) } // version specifics switch c.want[i].version { case ProtocolVersion2: v2msg, ok := g.(*v2ControlMessage) if ok { if v2msg.Tid() != c.want[i].tid { t.Errorf("Tid() == %q, want %q", v2msg.Tid(), c.want[i].tid) } if v2msg.Sid() != c.want[i].sid { t.Errorf("Sid() == %q, want %q", v2msg.Sid(), c.want[i].sid) } } else { t.Errorf("Expected V2ControlMessage, but didn't receive one") } case ProtocolVersion3: v3msg, ok := g.(*v3ControlMessage) if ok { if v3msg.ControlConnectionID() != c.want[i].ccid { t.Errorf("ControlConnectionID() == %q, want %q", v3msg.ControlConnectionID(), c.want[i].ccid) } } else { t.Errorf("Expected V3ControlMessage, but didn't receive one") } } } } else { t.Errorf("parseMessageBuffer(%q) failed: %q", c.in, err) } } } type msgTestAvpMetadata struct { isMandatory, isHidden bool avpType avpType vendorID avpVendorID dataType avpDataType data interface{} } func TestV2MessageBuild(t *testing.T) { cases := []struct { tid ControlConnID sid ControlConnID avps []msgTestAvpMetadata }{ { tid: 42, sid: 42, avps: []msgTestAvpMetadata{ {true, false, avpTypeMessage, vendorIDIetf, avpDataTypeMsgID, avpMsgTypeHello}, }, }, } for _, c := range cases { msg, err := newV2ControlMessage(c.tid, c.sid, []avp{}) if err != nil { t.Fatalf("newV2ControlMessage(%v, %v, []) said: %v", c.tid, c.sid, err) } for _, in := range c.avps { avp, err := newAvp(in.vendorID, in.avpType, in.data) if err != nil { t.Fatalf("newAvp(%v, %v, %v) said: %v", in.vendorID, in.avpType, in.data, err) } msg.appendAvp(avp) } if msg.getType() != c.avps[0].data { t.Fatalf("%v != %v", msg.getType(), c.avps[0].data) } } } func TestV3MessageBuild(t *testing.T) { cases := []struct { ccid ControlConnID avps []msgTestAvpMetadata }{ { ccid: 90210, avps: []msgTestAvpMetadata{ {true, false, avpTypeMessage, vendorIDIetf, avpDataTypeMsgID, avpMsgTypeHello}, }, }, } for _, c := range cases { msg, err := newV3ControlMessage(c.ccid, []avp{}) if err != nil { t.Fatalf("newV3ControlMessage(%v, []) said: %v", c.ccid, err) } for _, in := range c.avps { avp, err := newAvp(in.vendorID, in.avpType, in.data) if err != nil { t.Fatalf("newAvp(%v, %v, %v) said: %v", in.vendorID, in.avpType, in.data, err) } msg.appendAvp(avp) } if msg.getType() != c.avps[0].data { t.Fatalf("%v != %v", msg.getType(), c.avps[0].data) } } } func TestParseEncode(t *testing.T) { cases := []struct { in []byte }{ {in: []byte{ 0xc8, 0x02, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, }, }, } for _, c := range cases { got, err := parseMessageBuffer(c.in) if err != nil { t.Fatalf("parseMessageBuffer(%v) failed: %v", c.in, err) } if len(got) != 1 { t.Fatalf("parseMessageBuffer(%v): wanted 1 message, got %d", c.in, len(got)) } mb, err := got[0].toBytes() if err != nil { t.Fatalf("toBytes() failed: %v", err) } if !bytes.Equal(mb, c.in) { t.Fatalf("toBytes(): wanted %v, got %v", c.in, mb) } } } func TestV2TunnelBuildValidate(t *testing.T) { cases := []struct { tcfg TunnelConfig rc resultCode buildersGood []func(*TunnelConfig, *resultCode) (*v2ControlMessage, error) }{ { // Currently the builders don't check data ranges, so // all we're really doing is exercising the builder/validation // code. tcfg: TunnelConfig{}, rc: resultCode{}, buildersGood: []func(*TunnelConfig, *resultCode) (*v2ControlMessage, error){ func(tcfg *TunnelConfig, rc *resultCode) (*v2ControlMessage, error) { return newV2Sccrq(tcfg) }, func(tcfg *TunnelConfig, rc *resultCode) (*v2ControlMessage, error) { return newV2Sccrp(tcfg) }, func(tcfg *TunnelConfig, rc *resultCode) (*v2ControlMessage, error) { return newV2Scccn(tcfg) }, func(tcfg *TunnelConfig, rc *resultCode) (*v2ControlMessage, error) { return newV2Stopccn(rc, tcfg) }, func(tcfg *TunnelConfig, rc *resultCode) (*v2ControlMessage, error) { return newV2Hello(tcfg) }, }, }, } for _, c := range cases { for i, builder := range c.buildersGood { msg, err := builder(&c.tcfg, &c.rc) if err != nil { t.Fatalf("good builder %v: %v %v: %v", i, c.tcfg, c.rc, err) } err = msg.validate() if err != nil { t.Fatalf("good builder validation %v: %v %v: %v", i, c.tcfg, c.rc, err) } } } } golang-github-katalix-go-l2tp-0.1.7/l2tp/nl_dataplane.go000066400000000000000000000120441456471064000230250ustar00rootroot00000000000000package l2tp import ( "fmt" "github.com/katalix/go-l2tp/internal/nll2tp" "golang.org/x/sys/unix" ) var _ DataPlane = (*nlDataPlane)(nil) var _ TunnelDataPlane = (*nlTunnelDataPlane)(nil) var _ SessionDataPlane = (*nlSessionDataPlane)(nil) type nlDataPlane struct { nlconn *nll2tp.Conn } type nlTunnelDataPlane struct { f *nlDataPlane cfg *nll2tp.TunnelConfig } type nlSessionDataPlane struct { f *nlDataPlane cfg *nll2tp.SessionConfig interfaceName string } func sockaddrAddrPort(sa unix.Sockaddr) (addr []byte, port uint16, err error) { switch sa := sa.(type) { case *unix.SockaddrInet4: return sa.Addr[:], uint16(sa.Port), nil case *unix.SockaddrInet6: return sa.Addr[:], uint16(sa.Port), nil case *unix.SockaddrL2TPIP: return sa.Addr[:], 0, nil case *unix.SockaddrL2TPIP6: return sa.Addr[:], 0, nil } return []byte{}, 0, fmt.Errorf("unexpected address type %T", addr) } func tunnelCfgToNl(cfg *TunnelConfig) (*nll2tp.TunnelConfig, error) { // TODO: facilitate kernel level debug return &nll2tp.TunnelConfig{ Tid: nll2tp.L2tpTunnelID(cfg.TunnelID), Ptid: nll2tp.L2tpTunnelID(cfg.PeerTunnelID), Version: nll2tp.L2tpProtocolVersion(cfg.Version), Encap: nll2tp.L2tpEncapType(cfg.Encap), DebugFlags: nll2tp.L2tpDebugFlags(0)}, nil } func sessionCfgToNl(tid, ptid ControlConnID, cfg *SessionConfig) (*nll2tp.SessionConfig, error) { // In kernel-land, the PPP/AC pseudowire is implemented using // an l2tp_ppp session, and a pppol2tp channel bridged to a // pppoe channel. Hence we should ask for a PPP pseudowire here. pwtype := nll2tp.L2tpPwtype(cfg.Pseudowire) if pwtype == nll2tp.PwtypePppAc { pwtype = nll2tp.PwtypePpp } // TODO: facilitate kernel level debug // TODO: IsLNS defaulting to false allows the peer to decide, // not sure whether this is a good idea or not really. return &nll2tp.SessionConfig{ Tid: nll2tp.L2tpTunnelID(tid), Ptid: nll2tp.L2tpTunnelID(ptid), Sid: nll2tp.L2tpSessionID(cfg.SessionID), Psid: nll2tp.L2tpSessionID(cfg.PeerSessionID), PseudowireType: pwtype, SendSeq: cfg.SeqNum, RecvSeq: cfg.SeqNum, IsLNS: false, ReorderTimeout: uint64(cfg.ReorderTimeout.Milliseconds()), LocalCookie: cfg.Cookie, PeerCookie: cfg.PeerCookie, IfName: cfg.InterfaceName, L2SpecType: nll2tp.L2tpL2specType(cfg.L2SpecType), DebugFlags: nll2tp.L2tpDebugFlags(0), }, nil } func (dpf *nlDataPlane) NewTunnel(tcfg *TunnelConfig, sal, sap unix.Sockaddr, fd int) (TunnelDataPlane, error) { nlcfg, err := tunnelCfgToNl(tcfg) if err != nil { return nil, fmt.Errorf("failed to convert tunnel config for netlink use: %v", err) } // If the tunnel has a socket FD, create a managed tunnel dataplane. // Otherwise, create a static dataplane. if fd >= 0 { err = dpf.nlconn.CreateManagedTunnel(fd, nlcfg) } else { var la, ra []byte var lp, rp uint16 la, lp, err = sockaddrAddrPort(sal) if err != nil { return nil, fmt.Errorf("invalid local address %v: %v", sal, err) } ra, rp, err = sockaddrAddrPort(sap) if err != nil { return nil, fmt.Errorf("invalid remote address %v: %v", sap, err) } err = dpf.nlconn.CreateStaticTunnel(la, lp, ra, rp, nlcfg) } if err != nil { return nil, fmt.Errorf("failed to instantiate tunnel via. netlink: %v", err) } return &nlTunnelDataPlane{f: dpf, cfg: nlcfg}, nil } func (dpf *nlDataPlane) NewSession(tid, ptid ControlConnID, scfg *SessionConfig) (SessionDataPlane, error) { nlcfg, err := sessionCfgToNl(tid, ptid, scfg) if err != nil { return nil, fmt.Errorf("failed to convert session config for netlink use: %v", err) } err = dpf.nlconn.CreateSession(nlcfg) if err != nil { return nil, fmt.Errorf("failed to instantiate session via. netlink: %v", err) } return &nlSessionDataPlane{f: dpf, cfg: nlcfg}, nil } func (dpf *nlDataPlane) Close() { if dpf.nlconn != nil { dpf.nlconn.Close() } } func (tdp *nlTunnelDataPlane) Down() error { return tdp.f.nlconn.DeleteTunnel(tdp.cfg) } func (sdp *nlSessionDataPlane) GetStatistics() (*SessionDataPlaneStatistics, error) { info, err := sdp.f.nlconn.GetSessionInfo(sdp.cfg) if err != nil { return nil, err } return &SessionDataPlaneStatistics{ TxPackets: info.Statistics.TxPacketCount, TxBytes: info.Statistics.TxBytes, TxErrors: info.Statistics.TxErrorCount, RxPackets: info.Statistics.RxPacketCount, RxBytes: info.Statistics.RxBytes, RxErrors: info.Statistics.RxErrorCount, }, nil } func (sdp *nlSessionDataPlane) GetInterfaceName() (string, error) { if sdp.interfaceName == "" { info, err := sdp.f.nlconn.GetSessionInfo(sdp.cfg) if err != nil { return "", err } sdp.interfaceName = info.IfName } return sdp.interfaceName, nil } func (sdp *nlSessionDataPlane) Down() error { return sdp.f.nlconn.DeleteSession(sdp.cfg) } func newNetlinkDataPlane() (DataPlane, error) { nlconn, err := nll2tp.Dial() if err != nil { return nil, fmt.Errorf("failed to establish a netlink/L2TP connection: %v", err) } return &nlDataPlane{ nlconn: nlconn, }, nil } golang-github-katalix-go-l2tp-0.1.7/l2tp/null_dataplane.go000066400000000000000000000017431456471064000233720ustar00rootroot00000000000000package l2tp import ( "golang.org/x/sys/unix" ) var _ DataPlane = (*nullDataPlane)(nil) var _ TunnelDataPlane = (*nullTunnelDataPlane)(nil) var _ SessionDataPlane = (*nullSessionDataPlane)(nil) type nullDataPlane struct { } type nullTunnelDataPlane struct { } type nullSessionDataPlane struct { } func (ndp *nullDataPlane) NewTunnel(tcfg *TunnelConfig, sal, sap unix.Sockaddr, fd int) (TunnelDataPlane, error) { return &nullTunnelDataPlane{}, nil } func (ndp *nullDataPlane) NewSession(tid, ptid ControlConnID, scfg *SessionConfig) (SessionDataPlane, error) { return &nullSessionDataPlane{}, nil } func (ndp *nullDataPlane) Close() { } func (tdp *nullTunnelDataPlane) Down() error { return nil } func (sdp *nullSessionDataPlane) GetStatistics() (*SessionDataPlaneStatistics, error) { return &SessionDataPlaneStatistics{}, nil } func (sdp *nullSessionDataPlane) GetInterfaceName() (string, error) { return "", nil } func (tdp *nullSessionDataPlane) Down() error { return nil } golang-github-katalix-go-l2tp-0.1.7/l2tp/transport.go000066400000000000000000000506051456471064000224440ustar00rootroot00000000000000package l2tp import ( "errors" "fmt" "strings" "sync" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "golang.org/x/sys/unix" ) // slowStartState represents state for the transport sequence numbers // and slow start/congestion avoidance algorithm. type slowStartState struct { lock sync.Mutex ns, nr, cwnd, thresh, nacks, ntx uint16 } // xmitMsg encapsulates state for control message transmission, // wrapping the basic controlMessage with transport-specific // metadata. type xmitMsg struct { xport *transport // The message for transmission msg controlMessage // The current retry count for the message. This is bound by // the transport config MaxRetries parameter. nretries uint // When transmission is complete (either: the message has been // transmitted and acked by the peer, transmission itself has // failed, or retransmission has timed out) the resulting error // is sent on this channel. completeChan chan error // Completion state flag used internally by the transport. isComplete bool // Timer for retransmission if the peer doesn't ack the message. retryTimer *time.Timer onComplete func(m *xmitMsg, err error) } // rawMsg represents a raw frame read from the transport socket. type rawMsg struct { b []byte sa unix.Sockaddr } // recvMsg represents a received control message. type recvMsg struct { msg controlMessage from unix.Sockaddr } // nrInd represents a received sequence value. type nrInd struct { msgType avpMsgType nr uint16 } // transportConfig represents the tunable parameters governing // the behaviour of the reliable transport algorithm. type transportConfig struct { // Duration to wait after last message receipt before // sending a HELLO keepalive message. If set to 0, no HELLO messages // are transmitted. HelloTimeout time.Duration // Maximum number of messages we will send to the peer without having // received an acknowledgement. TxWindowSize uint16 // Maximum number of retransmits of an unacknowledged control packet. MaxRetries uint // Duration to wait before first packet retransmit. // Subsequent retransmits up to the limit set by maxRetries occur at // exponentially increasing intervals as per RFC3931. If set to 0, // a default value of 1 second is used. RetryTimeout time.Duration // Duration to wait before explicitly acking a control message. // Most control messages will be implicitly acked by control protocol // responses. AckTimeout time.Duration // Version of the L2TP protocol to use for transport-generated messages. Version ProtocolVersion // Peer control connection ID to use for transport-generated messages PeerControlConnID ControlConnID } // transport represents the RFC2661/RFC3931 // reliable transport algorithm state. type transport struct { logger log.Logger slowStart slowStartState config transportConfig cp *controlPlane helloTimer, ackTimer *time.Timer helloInFlight bool sendChan chan *xmitMsg retryChan chan *xmitMsg recvChan chan *recvMsg nrChan chan []nrInd rxQueue []*recvMsg txQueue, ackQueue []*xmitMsg senderWg sync.WaitGroup receiverWg sync.WaitGroup } // Increment transport sequence number by one avoiding overflow // as per RFC2661/RFC3931 func seqIncrement(seqNum uint16) uint16 { next := uint32(seqNum) next = (next + 1) % 0x10000 return uint16(next) } // Sequence number comparision as per RFC2661/RFC3931 func seqCompare(seq1, seq2 uint16) int { var delta uint16 if seq2 <= seq1 { delta = seq1 - seq2 } else { delta = (0xffff - seq2 + 1) + seq1 } if delta == 0 { return 0 } else if delta < 0x8000 { return 1 } return -1 } func (s *slowStartState) canSend() bool { s.lock.Lock() defer s.lock.Unlock() return s.ntx < s.cwnd } func (s *slowStartState) onSend() { s.lock.Lock() defer s.lock.Unlock() s.ntx++ } func (s *slowStartState) onAck(maxTxWindow uint16) { s.lock.Lock() defer s.lock.Unlock() if s.ntx > 0 { if s.cwnd < maxTxWindow { if s.cwnd < s.thresh { // slow start s.cwnd++ } else { // congestion avoidance s.nacks++ if s.nacks >= s.cwnd { s.nacks = 0 s.cwnd++ } } } s.ntx-- } } func (s *slowStartState) onRetransmit() { s.lock.Lock() defer s.lock.Unlock() s.thresh = s.cwnd / 2 s.cwnd = 1 } func (s *slowStartState) incrementNr() { s.lock.Lock() defer s.lock.Unlock() s.nr = seqIncrement(s.nr) } func (s *slowStartState) incrementNs() { s.lock.Lock() defer s.lock.Unlock() s.ns = seqIncrement(s.ns) } // A message with ns value equal to our nr is the next packet in sequence. func (s *slowStartState) msgIsInSequence(msg controlMessage) bool { s.lock.Lock() defer s.lock.Unlock() return seqCompare(s.nr, msg.ns()) == 0 } // A message with ns value < our nr is stale/duplicated. func (s *slowStartState) msgIsStale(msg controlMessage) bool { s.lock.Lock() defer s.lock.Unlock() return seqCompare(msg.ns(), s.nr) == -1 } func (s *slowStartState) getSequenceNumbers() (ns, nr uint16) { s.lock.Lock() defer s.lock.Unlock() return s.ns, s.nr } func (m *xmitMsg) txComplete(err error) { if !m.isComplete { level.Debug(m.xport.logger).Log( "message", "send complete", "message_type", m.msg.getType(), "error", err) m.isComplete = true if m.retryTimer != nil { m.retryTimer.Stop() } m.onComplete(m, err) } } func newTimer(duration time.Duration) *time.Timer { if duration == 0 { duration = 1 * time.Hour } t := time.NewTimer(duration) t.Stop() return t } func sanitiseConfig(cfg *transportConfig) { if cfg.TxWindowSize == 0 || cfg.TxWindowSize > 65535 { cfg.TxWindowSize = defaulttransportConfig().TxWindowSize } if cfg.RetryTimeout == 0 { cfg.RetryTimeout = defaulttransportConfig().RetryTimeout } if cfg.AckTimeout == 0 { cfg.AckTimeout = defaulttransportConfig().AckTimeout } if cfg.MaxRetries == 0 { cfg.MaxRetries = defaulttransportConfig().MaxRetries } } func (xport *transport) rawRecv() (buffer []byte, from unix.Sockaddr, err error) { buffer = make([]byte, 4096) n, from, err := xport.cp.recvFrom(buffer) if err != nil { return nil, nil, err } buffer = buffer[:n] return } func (xport *transport) receiver() { for { buffer, from, err := xport.rawRecv() if err != nil { close(xport.nrChan) level.Error(xport.logger).Log( "message", "socket read failed", "error", err) return } level.Debug(xport.logger).Log( "message", "socket recv", "length", len(buffer)) // Parse the received frame into control messages, perform early // sequence number validation. messages, err := xport.recvFrame(&rawMsg{b: buffer, sa: from}) if err != nil { // Early packet handling can fail for a variety of reasons. // The most important of these is if a peer sends a mandatory // AVP that we don't recognise: this MUST cause the tunnel to fail // per the RFCs. Anything else we just log for information. level.Error(xport.logger).Log( "message", "frame receive failed", "error", err) if strings.Contains("failed to parse mandatory AVP", err.Error()) { close(xport.nrChan) return } } // Add received messages to the rx queue. Pass the nr values of the received // messages to the sender goroutine for processing of the ack queue and possible // re-opening of the send window. rxNr := []nrInd{} for _, msg := range messages { xport.rxQueue = append(xport.rxQueue, &recvMsg{msg: msg, from: from}) rxNr = append(rxNr, nrInd{msgType: msg.getType(), nr: msg.nr()}) } xport.nrChan <- rxNr xport.processRxQueue() } } func (xport *transport) sender() { for { select { // Transmission request from user code case xmitMsg, ok := <-xport.sendChan: if !ok { xport.down(errors.New("transport shut down by user")) return } level.Debug(xport.logger).Log( "message", "send", "message_type", xmitMsg.msg.getType()) xport.txQueue = append(xport.txQueue, xmitMsg) err := xport.processTxQueue() if err != nil { xport.down(err) return } // Nr sequence updates from receiver case rxNr, ok := <-xport.nrChan: if !ok { xport.down(errors.New("receive path error")) return } // Process the ack queue to see whether the nr updates ack any outstanding // messages. If we manage to dequeue a message it may result in opening the // window for further transmission, in which case process the tx queue. for _, nri := range rxNr { if xport.processAckQueue(nri.nr) { err := xport.processTxQueue() if err != nil { xport.down(err) return } } } // Kick the ack timer if we received any non-ack message. We don't want to // ack an ack message since we'll end up ping-ponging acks back and forth forever. for _, nri := range rxNr { if nri.msgType != avpMsgTypeAck { xport.toggleAckTimer(true) break } } // The fact we've seen any traffic at all means we should reset the hello timer xport.resetHelloTimer() // Message retry request due to timeout waiting for an ack case xmitMsg, ok := <-xport.retryChan: if !ok { return } level.Info(xport.logger).Log( "message", "retransmit", "message_type", xmitMsg.msg.getType()) // It's possible that a message ack could race with the retry timer. // Hence we track completion state in the message struct to avoid // a bogus retransmit. if !xmitMsg.isComplete { err := xport.retransmitMessage(xmitMsg) if err != nil { xmitMsg.txComplete(err) xport.down(err) return } } // Timer fired for sending a hello message case <-xport.helloTimer.C: if !xport.helloInFlight { err := xport.sendHelloMessage() if err != nil { xport.down(err) return } xport.helloInFlight = true } // Timer fired for sending an explicit ack case <-xport.ackTimer.C: err := xport.sendExplicitAck() if err != nil { xport.down(err) return } } } } func (xport *transport) recvFrame(rawMsg *rawMsg) (messages []controlMessage, err error) { messages, err = parseMessageBuffer(rawMsg.b) if err != nil { return nil, err } ns, nr := xport.slowStart.getSequenceNumbers() for _, msg := range messages { // Sanity check the packet sequence number: return an error if it's not OK if seqCompare(msg.nr(), seqIncrement(ns)) > 0 { return nil, fmt.Errorf("dropping invalid packet %s ns %d nr %d (transport ns %d nr %d)", msg.getType(), msg.ns(), msg.nr(), ns, nr) } } return messages, nil } // Find the next message which can be handled (either stale or in-sequence) func (xport *transport) dequeueRxMessage() *recvMsg { for i := 0; i < len(xport.rxQueue); i++ { m := xport.rxQueue[0] if xport.slowStart.msgIsInSequence(m.msg) || xport.slowStart.msgIsStale(m.msg) { xport.rxQueue = append(xport.rxQueue[:i], xport.rxQueue[i+1:]...) return m } } return nil } func (xport *transport) processRxQueue() { // Pop messages off the receive queue in sequence, and process. // Give up when there are no more in-sequence messages to handle. for { m := xport.dequeueRxMessage() if m == nil { return } // We don't need to do anything more with an ack message since // they only serve to update the ack queue. So just ignore them here. if m.msg.getType() != avpMsgTypeAck { // If a message is stale, just ignore it here. It'll be acked // implicitly by the ack timer. if xport.slowStart.msgIsInSequence(m.msg) { level.Debug(xport.logger).Log( "message", "recv", "message_type", m.msg.getType()) xport.slowStart.incrementNr() xport.recvChan <- m } } } } func (xport *transport) sendMessage1(msg controlMessage, isRetransmit bool) error { // Set message sequence numbers. // A retransmitted message should have ns set already. ns, nr := xport.slowStart.getSequenceNumbers() if isRetransmit { msg.setTransportSeqNum(msg.ns(), nr) } else { msg.setTransportSeqNum(ns, nr) } level.Debug(xport.logger).Log( "message", "send", "message_type", msg.getType(), "ns", msg.ns(), "nr", msg.nr(), "isRetransmit", isRetransmit) // Render as a byte slice and send. b, err := msg.toBytes() if err == nil { _, err = xport.cp.write(b) } return err } // Exponential retry timeout scaling as per RFC2661/RFC3931 func (xport *transport) scaleRetryTimeout(msg *xmitMsg) time.Duration { return xport.config.RetryTimeout * (1 << msg.nretries) } func (xport *transport) sendMessage(msg *xmitMsg) error { err := xport.sendMessage1(msg.msg, msg.nretries > 0) if err == nil { xport.toggleAckTimer(false) // we have just sent an implicit ack xport.resetHelloTimer() if msg.msg.getType() != avpMsgTypeAck && msg.nretries == 0 { xport.slowStart.incrementNs() } msg.retryTimer = time.AfterFunc(xport.scaleRetryTimeout(msg), func() { xport.retryChan <- msg }) } return err } func (xport *transport) retransmitMessage(msg *xmitMsg) error { msg.nretries++ if msg.nretries >= xport.config.MaxRetries { return fmt.Errorf("transmit of %s failed after %d retry attempts", msg.msg.getType(), xport.config.MaxRetries) } err := xport.sendMessage(msg) if err == nil { xport.slowStart.onRetransmit() } return err } func (xport *transport) processTxQueue() error { // Loop the transmit queue sending messages in order while // the transmit window is open. for len(xport.txQueue) > 0 { if !xport.slowStart.canSend() { // We've sent all we can for the time being. This is not // an error condition, so return successfully. return nil } // Pop from the tx queue, send, add to the ack queue msg := xport.txQueue[0] xport.txQueue = append(xport.txQueue[:0], xport.txQueue[1:]...) err := xport.sendMessage(msg) if err == nil { xport.ackQueue = append(xport.ackQueue, msg) xport.slowStart.onSend() } else { msg.txComplete(err) return err } } return nil } func (xport *transport) processAckQueue(nr uint16) (found bool) { for i := 0; i < len(xport.ackQueue); i++ { msg := xport.ackQueue[0] if seqCompare(nr, msg.msg.ns()) > 0 { xport.slowStart.onAck(xport.config.TxWindowSize) xport.ackQueue = append(xport.ackQueue[:i], xport.ackQueue[i+1:]...) i-- msg.txComplete(nil) found = true } } return } func (xport *transport) closeReceiver() { var drainWg sync.WaitGroup exit := make(chan interface{}) drainWg.Add(1) go func() { defer drainWg.Done() for { select { case <-exit: return case _, ok := <-xport.recvChan: if !ok { return } case <-xport.nrChan: } } }() xport.cp.close() xport.receiverWg.Wait() drainWg.Wait() } func (xport *transport) down(err error) { // Shut down the receiver xport.closeReceiver() // Flush tx and ack queues: complete these messages to unblock // callers pending on their completion. // Note the rx queue is flushed by the receiver go routine *after* // xport.receiver() has terminated. We don't do it here since // doing so would represent a data race. for len(xport.txQueue) > 0 { msg := xport.txQueue[0] xport.txQueue = append(xport.txQueue[:0], xport.txQueue[1:]...) msg.txComplete(err) } for len(xport.ackQueue) > 0 { msg := xport.ackQueue[0] xport.ackQueue = append(xport.ackQueue[:0], xport.ackQueue[1:]...) msg.txComplete(err) } // Stop timers: we don't care about the return value since // the transport goroutine will return after calling this function // and hence won't be able to process racing timer messages xport.toggleAckTimer(false) _ = xport.helloTimer.Stop() level.Error(xport.logger).Log( "message", "transport down", "error", err) } func (xport *transport) toggleAckTimer(enable bool) { if enable { xport.ackTimer.Reset(xport.config.AckTimeout) } else { // TODO: is this bad? _ = xport.ackTimer.Stop() } } func (xport *transport) resetHelloTimer() { if xport.config.HelloTimeout > 0 { xport.helloTimer.Reset(xport.config.HelloTimeout) } } func (xport *transport) sendHelloMessage() error { var msg controlMessage a, err := newAvp(vendorIDIetf, avpTypeMessage, avpMsgTypeHello) if err != nil { return fmt.Errorf("failed to build hello message type AVP: %v", err) } if xport.config.Version == ProtocolVersion3Fallback || xport.config.Version == ProtocolVersion3 { msg, err = newV3ControlMessage(xport.config.PeerControlConnID, []avp{*a}) } else { msg, err = newV2ControlMessage(xport.config.PeerControlConnID, 0, []avp{*a}) } if err != nil { return fmt.Errorf("failed to build hello message: %v", err) } return xport.sendMessage(&xmitMsg{ xport: xport, msg: msg, onComplete: helloSendComplete, }) } func helloSendComplete(m *xmitMsg, err error) { m.xport.helloInFlight = false } func (xport *transport) sendExplicitAck() (err error) { var msg controlMessage if xport.config.Version == ProtocolVersion3Fallback || xport.config.Version == ProtocolVersion3 { a, err := newAvp(vendorIDIetf, avpTypeMessage, avpMsgTypeAck) if err != nil { return fmt.Errorf("failed to build v3 explicit ack message type AVP: %v", err) } msg, err = newV3ControlMessage(xport.config.PeerControlConnID, []avp{*a}) if err != nil { return fmt.Errorf("failed to build v3 explicit ack message: %v", err) } } else { msg, err = newV2ControlMessage(xport.config.PeerControlConnID, 0, []avp{}) if err != nil { return fmt.Errorf("failed to build v2 ZLB message: %v", err) } } return xport.sendMessage1(msg, false) } // defaulttransportConfig returns a default configuration for the transport. func defaulttransportConfig() transportConfig { return transportConfig{ HelloTimeout: 0 * time.Second, TxWindowSize: 4, MaxRetries: 3, RetryTimeout: 1 * time.Second, AckTimeout: 100 * time.Millisecond, Version: ProtocolVersion3, } } // newTransport creates a new RFC2661/RFC3931 reliable transport. // The control plane passed in is owned by the transport and will // be closed by the transport when the transport is closed. func newTransport(logger log.Logger, cp *controlPlane, cfg transportConfig) (xport *transport, err error) { if cp == nil { return nil, errors.New("illegal nil control plane argument") } // Make sure the config is sane sanitiseConfig(&cfg) // We always create timer instances even if they're not going to be used. // This makes the logic for the transport go routine select easier to manage. helloTimer := newTimer(cfg.HelloTimeout) ackTimer := newTimer(cfg.AckTimeout) xport = &transport{ logger: log.With(logger, "function", "transport"), slowStart: slowStartState{ thresh: cfg.TxWindowSize, cwnd: 1, }, config: cfg, cp: cp, helloTimer: helloTimer, ackTimer: ackTimer, sendChan: make(chan *xmitMsg), retryChan: make(chan *xmitMsg), recvChan: make(chan *recvMsg), nrChan: make(chan []nrInd), rxQueue: []*recvMsg{}, txQueue: []*xmitMsg{}, ackQueue: []*xmitMsg{}, } xport.resetHelloTimer() xport.senderWg.Add(1) go func() { defer xport.senderWg.Done() xport.sender() }() xport.receiverWg.Add(1) go func() { defer xport.receiverWg.Done() xport.receiver() // Flush rx queue xport.rxQueue = xport.rxQueue[0:0] // Unblock user code blocking on receive from the transport close(xport.recvChan) }() return xport, nil } // getConfig allows transport parameters to be queried. func (xport *transport) getConfig() transportConfig { return xport.config } // send sends a control message using the reliable transport. // The caller will block until the message has been acked by the peer. // Failure indicates that the transport has failed and the parent tunnel // should be torn down. func (xport *transport) send(msg controlMessage) error { err := msg.validate() if err != nil { return fmt.Errorf("failed to validate message: %v", err) } cm := xmitMsg{ xport: xport, msg: msg, completeChan: make(chan error), onComplete: sendComplete, } xport.sendChan <- &cm err = <-cm.completeChan return err } func sendComplete(m *xmitMsg, err error) { m.completeChan <- err } // recv receives a control message using the reliable transport. // The caller will block until a message has been received from the peer. // Failure indicates that the transport has failed and the parent tunnel // should be torn down. func (xport *transport) recv() (msg controlMessage, from unix.Sockaddr, err error) { m, ok := <-xport.recvChan if !ok { return nil, nil, errors.New("transport is down") } return m.msg, m.from, nil } // close closes the transport. func (xport *transport) close() { close(xport.sendChan) xport.senderWg.Wait() } golang-github-katalix-go-l2tp-0.1.7/l2tp/transport_test.go000066400000000000000000000245121456471064000235010ustar00rootroot00000000000000package l2tp import ( "fmt" "os" "strings" "testing" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "golang.org/x/sys/unix" ) func TestNilControlPlane(t *testing.T) { xport, err := newTransport(log.NewLogfmtLogger(os.Stderr), nil, defaulttransportConfig()) if xport != nil { t.Fatalf("newTransport() with nil controlplane succeeded") } else if err == nil { t.Fatalf("newTransport() with nil controlplane didn't report error") } } func TestSeqNumIncrement(t *testing.T) { cases := []struct { in, want uint16 }{ {uint16(0), uint16(1)}, {uint16(65534), uint16(65535)}, {uint16(65535), uint16(0)}, } for _, c := range cases { got := seqIncrement(c.in) if got != c.want { t.Errorf("seqIncrement(%d) = %d, want %d", c.in, got, c.want) } } } func TestSeqNumCompare(t *testing.T) { cases := []struct { seq1, seq2 uint16 want int }{ {uint16(15), uint16(15), 0}, {uint16(15), uint16(0), 1}, {uint16(15), uint16(65535), 1}, {uint16(15), uint16(32784), 1}, {uint16(15), uint16(16), -1}, {uint16(15), uint16(15000), -1}, {uint16(15), uint16(32783), -1}, } for _, c := range cases { got := seqCompare(c.seq1, c.seq2) if got != c.want { t.Errorf("seqCompare(%d, %d) = %d, want %d", c.seq1, c.seq2, got, c.want) } } } func checkWindowOpen(ss *slowStartState, t *testing.T) { if !ss.canSend() { t.Fatalf("transport window is closed when we expect it to be open") } } func checkWindowClosed(ss *slowStartState, t *testing.T) { if ss.canSend() { t.Fatalf("transport window is open when we expect it to be closed") } } func checkCwndThresh(ss *slowStartState, cwnd, thresh uint16, t *testing.T) { if ss.cwnd != cwnd { t.Fatalf("transport window didn't correctly reset on retransmission: expected %d, got %d", cwnd, ss.cwnd) } if ss.thresh != thresh { t.Fatalf("transport threshold didn't correctly reset on retransmission: expected %d, got %d", thresh, ss.thresh) } } func TestSlowStart(t *testing.T) { txWindow := uint16(4) // initialise state and validate window is open ss := slowStartState{thresh: txWindow, cwnd: 1} checkWindowOpen(&ss, t) // send a packet, validate window is now closed ss.onSend() checkWindowClosed(&ss, t) // ack the packet: should now be able to send two packets before window closes ss.onAck(txWindow) for i := 0; i < 2; i++ { checkWindowOpen(&ss, t) ss.onSend() } checkWindowClosed(&ss, t) // ack the two packets in flight: should now be able to send four packets for i := 0; i < 2; i++ { ss.onAck(txWindow) } for i := 0; i < 4; i++ { checkWindowOpen(&ss, t) ss.onSend() } checkWindowClosed(&ss, t) // ack the four packets in flight, validate the state hasn't exceeded the max window for i := 0; i < 4; i++ { ss.onAck(txWindow) checkWindowOpen(&ss, t) if ss.cwnd > txWindow { t.Fatalf("transport window %d exceeded max %d", ss.cwnd, txWindow) } } // retransmit: validate threshold is reduced and cwnd is reset checkWindowOpen(&ss, t) ss.onSend() ss.onRetransmit() checkWindowClosed(&ss, t) checkCwndThresh(&ss, 1, 2, t) // ack the retransmit, validate we're in slow-start still ss.onAck(txWindow) checkWindowOpen(&ss, t) checkCwndThresh(&ss, 2, 2, t) // send packets, recv acks, validate congestion avoidance is applied checkWindowOpen(&ss, t) ss.onSend() ss.onAck(txWindow) checkCwndThresh(&ss, 2, 2, t) for i := 0; i < 3; i++ { checkWindowOpen(&ss, t) ss.onSend() ss.onAck(txWindow) checkCwndThresh(&ss, 3, 2, t) } checkWindowOpen(&ss, t) ss.onSend() ss.onAck(txWindow) checkCwndThresh(&ss, 4, 2, t) // lots more transmission, validate we don't exceed max tx window in congestion avoidance for i := 0; i < 100; i++ { checkWindowOpen(&ss, t) ss.onSend() ss.onAck(txWindow) checkCwndThresh(&ss, 4, 2, t) } } type transportSendRecvTestInfo struct { local, peer string tid ControlConnID encap EncapType xcfg transportConfig sender, receiver func(xport *transport) error } func flipTestInfo(info *transportSendRecvTestInfo) *transportSendRecvTestInfo { flipped := *info tmp1 := flipped.local flipped.local = flipped.peer flipped.peer = tmp1 tmp2 := flipped.tid flipped.tid = flipped.xcfg.PeerControlConnID flipped.xcfg.PeerControlConnID = tmp2 return &flipped } func transportTestnewTransport(testCfg *transportSendRecvTestInfo) (xport *transport, err error) { var sal, sap unix.Sockaddr var cp *controlPlane switch testCfg.encap { case EncapTypeUDP: sal, sap, err = newUDPAddressPair(testCfg.local, testCfg.peer) case EncapTypeIP: sal, sap, err = newIPAddressPair(testCfg.local, testCfg.tid, testCfg.peer, testCfg.xcfg.PeerControlConnID) default: err = fmt.Errorf("unhandled encap type %v", testCfg.encap) } if err != nil { return nil, fmt.Errorf("failed to init tunnel address structures: %v", err) } cp, err = newL2tpControlPlane(sal, sap) if err != nil { return nil, fmt.Errorf("failed to create control plane: %v", err) } err = cp.bind() if err != nil { return nil, fmt.Errorf("failed to bind control plane socket: %v", err) } err = cp.connect() if err != nil { return nil, fmt.Errorf("failed to connect control plane socket: %v", err) } return newTransport( level.NewFilter(log.NewLogfmtLogger(os.Stderr), level.AllowDebug(), level.AllowInfo()), cp, testCfg.xcfg) } func testBasicSendRecvSenderNewHelloMsg(cfg *transportConfig) (msg controlMessage, err error) { if cfg.Version == ProtocolVersion2 { msg, err = newV2ControlMessage(cfg.PeerControlConnID, 0, []avp{}) if err != nil { return nil, err } } else if cfg.Version == ProtocolVersion3 { msg, err = newV3ControlMessage(cfg.PeerControlConnID, []avp{}) if err != nil { return nil, err } } else { return nil, fmt.Errorf("testBasicSendRecvSenderNewMsg: unhandled protocol version") } avp, err := newAvp(vendorIDIetf, avpTypeMessage, avpMsgTypeHello) if err != nil { return nil, err } msg.appendAvp(avp) return msg, nil } func testBasicSendRecvHelloSender(xport *transport) error { // Send sufficient HELLO messages to exercise slowstart a bit for i := uint16(0); i < 3*xport.getConfig().TxWindowSize; i++ { cfg := xport.getConfig() msg, err := testBasicSendRecvSenderNewHelloMsg(&cfg) if err != nil { return fmt.Errorf("failed to build Hello message: %v", err) } err = xport.send(msg) if err != nil { return fmt.Errorf("failed to send Hello message: %v", err) } } return nil } func testBasicSendRecvHelloReceiver(xport *transport) error { for i := uint16(0); i < 3*xport.getConfig().TxWindowSize; i++ { msg, _, err := xport.recv() if err != nil { return fmt.Errorf("failed to receive message: %v", err) } if msg.getType() != avpMsgTypeHello { return fmt.Errorf("expected message %v, got %v", avpMsgTypeHello, msg.getType()) } } return nil } func TestBasicSendReceive(t *testing.T) { cases := []transportSendRecvTestInfo{ { local: "127.0.0.1:9000", tid: 42, peer: "127.0.0.1:9001", encap: EncapTypeUDP, xcfg: transportConfig{ Version: ProtocolVersion2, AckTimeout: 5 * time.Millisecond, PeerControlConnID: 90, }, sender: testBasicSendRecvHelloSender, receiver: testBasicSendRecvHelloReceiver, }, { local: "[::1]:9000", tid: 42, peer: "[::1]:9001", encap: EncapTypeUDP, xcfg: transportConfig{ Version: ProtocolVersion2, AckTimeout: 5 * time.Millisecond, PeerControlConnID: 90, }, sender: testBasicSendRecvHelloSender, receiver: testBasicSendRecvHelloReceiver, }, { local: "127.0.0.1:9000", tid: 42, peer: "127.0.0.1:9001", encap: EncapTypeUDP, xcfg: transportConfig{ Version: ProtocolVersion3, AckTimeout: 5 * time.Millisecond, PeerControlConnID: 90, }, sender: testBasicSendRecvHelloSender, receiver: testBasicSendRecvHelloReceiver, }, { local: "[::1]:9000", tid: 42, peer: "[::1]:9001", encap: EncapTypeUDP, xcfg: transportConfig{ Version: ProtocolVersion3, AckTimeout: 5 * time.Millisecond, PeerControlConnID: 90, }, sender: testBasicSendRecvHelloSender, receiver: testBasicSendRecvHelloReceiver, }, { local: "127.0.0.1:9000", tid: 42, peer: "127.0.0.1:9001", encap: EncapTypeIP, xcfg: transportConfig{ Version: ProtocolVersion3, AckTimeout: 5 * time.Millisecond, PeerControlConnID: 90, }, sender: testBasicSendRecvHelloSender, receiver: testBasicSendRecvHelloReceiver, }, // FIXME: upstream kernel commit 9d4c75800f61 unfortunately breaks L2TPIP6. // It would be nice to figure out a way to detect whether the running kernel // has this change and skip this test accordingly, but in the meantime just // avoid it across the board. /* { local: "[::1]:9000", tid: 42, peer: "[::1]:9001", encap: EncapTypeIP, xcfg: transportConfig{ Version: ProtocolVersion3, AckTimeout: 5 * time.Millisecond, PeerControlConnID: 90, }, sender: testBasicSendRecvHelloSender, receiver: testBasicSendRecvHelloReceiver, }, */ } for i, c := range cases { t.Run( fmt.Sprintf("%d: send/recv %s %s L2TPv%v %s", i, c.local, c.peer, c.xcfg.Version, c.encap), func(t *testing.T) { tx, err := transportTestnewTransport(&c) if err != nil { // On systems without l2tp_[ip6] modules loaded we will // see EncapTypeIP failing to create the control plane // socket with EPROTONOSUPPORT. Skip the test in that // scenario. if c.encap == EncapTypeIP && strings.Contains(err.Error(), "protocol not supported") { t.Skip("System does not support L2TP IP encapsulation") } t.Fatalf("transportTestnewTransport(%v) said: %v", c, err) } defer tx.close() pcfg := flipTestInfo(&c) rx, err := transportTestnewTransport(pcfg) if err != nil { t.Fatalf("transportTestnewTransport(%v) said: %v", pcfg, err) } defer rx.close() txCompletion := make(chan error) rxCompletion := make(chan error) go func() { txCompletion <- c.sender(tx) }() go func() { rxCompletion <- c.receiver(rx) }() err = <-txCompletion if err != nil { t.Errorf("test sender function reported an error: %v", err) } err = <-rxCompletion if err != nil { t.Errorf("test receiver function reported an error: %v", err) } }) } } golang-github-katalix-go-l2tp-0.1.7/pppoe/000077500000000000000000000000001456471064000203155ustar00rootroot00000000000000golang-github-katalix-go-l2tp-0.1.7/pppoe/conn.go000066400000000000000000000062201456471064000216010ustar00rootroot00000000000000package pppoe import ( "fmt" "net" "os" "golang.org/x/sys/unix" ) // PPPoEConn represents a PPPoE discovery connection, allowing // receipt and transmission of PPPoE discovery packets. // // Because raw sockets are used for sending Ethernet frames, it is // necessary to have root permissions to create PPPoEConn instances. type PPPoEConn struct { iface *net.Interface fd int file *os.File } func newRawSocket(protocol int) (fd int, err error) { // raw socket since we want to read/write link-level packets fd, err = unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, protocol) if err != nil { return -1, fmt.Errorf("socket: %v", err) } // make the socket nonblocking so we can use it with the runtime poller if err = unix.SetNonblock(fd, true); err != nil { unix.Close(fd) return -1, fmt.Errorf("failed to set socket nonblocking: %v", err) } // set the socket CLOEXEC to prevent passing it to child processes flags, err := unix.FcntlInt(uintptr(fd), unix.F_GETFD, 0) if err != nil { unix.Close(fd) return -1, fmt.Errorf("fcntl(F_GETFD): %v", err) } _, err = unix.FcntlInt(uintptr(fd), unix.F_SETFD, flags|unix.FD_CLOEXEC) if err != nil { unix.Close(fd) return -1, fmt.Errorf("fcntl(F_SETFD, FD_CLOEXEC): %v", err) } // allow broadcast err = unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_BROADCAST, 1) if err != nil { unix.Close(fd) return -1, fmt.Errorf("setsockopt(SO_BROADCAST): %v", err) } return } // NewDiscoveryConnection creates a new PPPoE discovery connection on // the specified network interface. func NewDiscoveryConnection(ifname string) (conn *PPPoEConn, err error) { iface, err := net.InterfaceByName(ifname) if err != nil { return nil, fmt.Errorf("failed to obtain details of interface \"%s\": %v", ifname, err) } fd, err := newRawSocket(int(ethTypeDiscoveryNetUint16())) if err != nil { return nil, fmt.Errorf("failed to create raw socket: %v", err) } // bind to the interface specified sa := unix.SockaddrLinklayer{ Protocol: ethTypeDiscoveryNetUint16(), Ifindex: iface.Index, } err = unix.Bind(fd, &sa) if err != nil { unix.Close(fd) return nil, fmt.Errorf("failed to bind socket: %v", err) } // register the socket with the runtime file := os.NewFile(uintptr(fd), "pppoe") return &PPPoEConn{ iface: iface, fd: fd, file: file, }, nil } // Close closes the discovery connection, releasing allocated resources. func (c *PPPoEConn) Close() (err error) { if c.file != nil { err = c.file.Close() c.file = nil } return } // Send sends a frame over the discovery connection. func (c *PPPoEConn) Send(b []byte) (n int, err error) { return c.file.Write(b) } // Recv receives one or more frames over the discovery connection. func (c *PPPoEConn) Recv(b []byte) (n int, err error) { return c.file.Read(b) } // HWAddr returns the hardware address of the interface the discovery // connection is using. func (c *PPPoEConn) HWAddr() (addr [6]byte) { if len(c.iface.HardwareAddr) >= 6 { return [6]byte{ c.iface.HardwareAddr[0], c.iface.HardwareAddr[1], c.iface.HardwareAddr[2], c.iface.HardwareAddr[3], c.iface.HardwareAddr[4], c.iface.HardwareAddr[5], } } return [6]byte{} } golang-github-katalix-go-l2tp-0.1.7/pppoe/const.go000066400000000000000000000030601456471064000217710ustar00rootroot00000000000000package pppoe // PPPoECode indicates the PPPoE packet type. type PPPoECode uint8 // PPPoESessionID, in combination with the peer's Ethernet addresses, // uniquely identifies a given PPPoE session. type PPPoESessionID uint16 // PPPoETagType identifies the tags contained in the data payload of // PPPoE discovery packets. type PPPoETagType uint16 // PPPoE packet codes. const ( // PPPoE Active Discovery Initiation packet PPPoECodePADI PPPoECode = 0x09 // PPPoE Active Discovery Offer packet PPPoECodePADO PPPoECode = 0x07 // PPPoE Active Discovery Request packet PPPoECodePADR PPPoECode = 0x19 // PPPoE Active Discovery Session-confirmation packet PPPoECodePADS PPPoECode = 0x65 // PPPoE Active Discovery Terminate packet PPPoECodePADT PPPoECode = 0xa7 ) // PPPoE Tag types. // // PPPoE packets may contain zero or more tags, which are // TLV constructs. const ( PPPoETagTypeEOL PPPoETagType = 0x0000 PPPoETagTypeServiceName PPPoETagType = 0x0101 PPPoETagTypeACName PPPoETagType = 0x0102 PPPoETagTypeHostUniq PPPoETagType = 0x0103 PPPoETagTypeACCookie PPPoETagType = 0x0104 PPPoETagTypeVendorSpecific PPPoETagType = 0x0105 PPPoETagTypeRelaySessionID PPPoETagType = 0x0110 PPPoETagTypeServiceNameError PPPoETagType = 0x0201 PPPoETagTypeACSystemError PPPoETagType = 0x0202 PPPoETagTypeGenericError PPPoETagType = 0x0203 ) // internal constants const ( pppoePacketMinLength = 20 // raw packet: 14 bytes Ethernet header, 6 bytes PPPoE header pppoeTagMinLength = 4 // bytes: 2 for type, 2 for length ) golang-github-katalix-go-l2tp-0.1.7/pppoe/doc.go000066400000000000000000000033011456471064000214060ustar00rootroot00000000000000/* Package pppoe is a library for PPP over Ethernet applications running on Linux systems. PPPoE is specified by RFC2516, and is widely used in home broadband links when connecting the client's router into the Internet Service Provider network. Currently package pppoe implements: * Connection and protocol support for the PPPoE Active Discovery protocol. This is a simple sequence of messages which is used to instantiate and tear down a PPPoE connection. Protocol support for both client and server applications is provided. * Integration with the Linux kernel's L2TP access concentrator subsystem used to control the switching of PPPoE session data packets into an L2TP session for transmission to the LNS. This is of use when building PPPoE servers. Actual session data packets are managed using a PPP daemon and are outside the scope of package pppoe. Usage # Note we're ignoring errors for brevity import ( "fmt" "github.com/katalix/go-l2tp/pppoe" ) // Create a new PPPoE discovery connection on interface eth0 conn, _ := pppoe.NewDiscoveryConnection("eth0") // Build a PADI packet to kick off the discovery process. // Add two service name tags indicating the services we're interested in. padi, _ := pppoe.NewPADI(conn.HWAddr(), "SuperBroadbandServiceName") padi.AddServiceNameTag("MegaBroadbandServiceName") // Encode the packet ready to send on the connection. b, _ := padi.ToBytes() // Send the packet. Hopefully a server responds! conn.Send(b) // Receive any replies to our PADI packet. rcv, _ := conn.Recv() // Parse the received frames into PPPoE packets. parsed, _ := pppoe.ParsePacketBuffer(rcv) fmt.Printf("received: %v\n", parsed[0]) */ package pppoe golang-github-katalix-go-l2tp-0.1.7/pppoe/pppoe.go000066400000000000000000000431001456471064000217650ustar00rootroot00000000000000package pppoe import ( "bytes" "encoding/binary" "fmt" "io" ) // PPPoETag represents the TLV data structures which make up // the data payload of PPPoE discovery packets. type PPPoETag struct { Type PPPoETagType Data []byte } // PPPoEPacket represents a PPPoE discovery packet. type PPPoEPacket struct { // SrcHWAddr is the Ethernet address of the sender of the packet. SrcHWAddr [6]byte // DstHWAddr is the Ethernet address of the receiver of the packet. DstHWAddr [6]byte // Code is the code per RFC2516 which identifes the packet. Code PPPoECode // SessionID is the allocated session ID, once it has been set. // Up until that point in the discovery sequence the ID is zero. SessionID PPPoESessionID // Tags is the data payload of the packet. Tags []*PPPoETag } // String provides a human-readable representation of PPPoECode. func (code PPPoECode) String() string { switch code { case PPPoECodePADI: return "PADI" case PPPoECodePADO: return "PADO" case PPPoECodePADR: return "PADR" case PPPoECodePADS: return "PADS" case PPPoECodePADT: return "PADT" } return "???" } // String provides a human-readable representation of PPPoETagType. func (typ PPPoETagType) String() string { switch typ { case PPPoETagTypeEOL: return "EOL" case PPPoETagTypeServiceName: return "Service Name" case PPPoETagTypeACName: return "AC Name" case PPPoETagTypeHostUniq: return "Host Uniq" case PPPoETagTypeACCookie: return "AC Cookie" case PPPoETagTypeVendorSpecific: return "Vendor Specific" case PPPoETagTypeRelaySessionID: return "Relay Session ID" case PPPoETagTypeServiceNameError: return "Service Name Error" case PPPoETagTypeACSystemError: return "AC System Error" case PPPoETagTypeGenericError: return "Generic Error" default: return "Unknown" } } // String provides a human-readable representation of PPPoETag. // // For tags specified by the RFC to contain strings, a string representation // of the tag data is rendered. For all other tags a dump of the raw hex bytes // is provided. func (tag *PPPoETag) String() string { // Render string tag payloads as strings switch tag.Type { case PPPoETagTypeServiceName, PPPoETagTypeACName, PPPoETagTypeServiceNameError, PPPoETagTypeACSystemError, PPPoETagTypeGenericError: return fmt.Sprintf("%v: '%s'", tag.Type, string(tag.Data)) } return fmt.Sprintf("%v: %#v", tag.Type, tag.Data) } // String provides a human-readable representation of PPPoEPacket. func (packet *PPPoEPacket) String() string { s := fmt.Sprintf("%s: src %s, dst %s, session %v, tags:", packet.Code, fmt.Sprintf("0x%02x:%02x:%02x:%02x:%02x:%02x", packet.SrcHWAddr[0], packet.SrcHWAddr[1], packet.SrcHWAddr[2], packet.SrcHWAddr[3], packet.SrcHWAddr[4], packet.SrcHWAddr[5]), fmt.Sprintf("0x%02x:%02x:%02x:%02x:%02x:%02x", packet.DstHWAddr[0], packet.DstHWAddr[1], packet.DstHWAddr[2], packet.DstHWAddr[3], packet.DstHWAddr[4], packet.DstHWAddr[5]), packet.SessionID) for _, tag := range packet.Tags { s += fmt.Sprintf(" %s,", tag) } return s } // ethTypeDiscovery returns the Ethernet type for PPPoE // discovery packets in host byte order. func ethTypeDiscovery() uint16 { return 0x8863 } // ethTypeDiscoveryNetBytes returns the Ethernet type for // PPPoE discovery packets as a network byte order byte slice. func ethTypeDiscoveryNetBytes() []byte { ethType := make([]byte, 2) binary.BigEndian.PutUint16(ethType, ethTypeDiscovery()) return ethType } // ethTypeDiscoveryNetUint16 returns the Ethernet type for // PPPoE discovery packets in network byte order. func ethTypeDiscoveryNetUint16() uint16 { b := ethTypeDiscoveryNetBytes() return uint16(b[1])<<8 + uint16(b[0]) } // NewPADI returns a PADI packet with the RFC-mandated service name // tag included. // // PADI packets are used by the client to initiate the PPPoE discovery // sequence. // // Clients which wish to use any service available should pass an empty // string. // // PADI packets are sent to the Ethernet broadcast address, so only the // source address must be specified. func NewPADI(sourceHWAddr [6]byte, serviceName string) (packet *PPPoEPacket, err error) { packet = &PPPoEPacket{ SrcHWAddr: sourceHWAddr, DstHWAddr: [6]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, Code: PPPoECodePADI, SessionID: 0, } err = packet.AddServiceNameTag(serviceName) if err != nil { return nil, err } return } // NewPADO returns a PADO packet with the RFC-mandated service name and // AC name tags included. // // PADO packets are used by the server respond to a client's PADI. func NewPADO(sourceHWAddr [6]byte, destHWAddr [6]byte, serviceName string, acName string) (packet *PPPoEPacket, err error) { packet = &PPPoEPacket{ SrcHWAddr: sourceHWAddr, DstHWAddr: destHWAddr, Code: PPPoECodePADO, SessionID: 0, } err = packet.AddServiceNameTag(serviceName) if err != nil { return nil, err } err = packet.AddACNameTag(acName) if err != nil { return nil, err } return } // NewPADR returns a PADR packet with the RFC-mandated service name // tag included. // // PADR packets are used by the client to request a specific service from // a server, based on a server's PADO. // // The service name tag should be derived from the PADO packet received // from the server. func NewPADR(sourceHWAddr [6]byte, destHWAddr [6]byte, serviceName string) (packet *PPPoEPacket, err error) { packet = &PPPoEPacket{ SrcHWAddr: sourceHWAddr, DstHWAddr: destHWAddr, Code: PPPoECodePADR, SessionID: 0, } err = packet.AddServiceNameTag(serviceName) if err != nil { return nil, err } return } // NewPADS returns a PADS packet including an allocated session ID and the // RFC-mandated service name tag. // // PADS packets are used by the server to respond to a client's PADR. They // represent the completion of the PPPoE discovery sequence, and may indicate // either success or failure to establish the connection. // // If the PADS packet indicates success, the session ID should be a non-zero // value which is unique for the PPPoE peers. // // If the PADS packet indicates failure, the session ID should be zero, and // the packet should have the PPPoETagTypeServiceNameError tag appended. func NewPADS(sourceHWAddr [6]byte, destHWAddr [6]byte, serviceName string, sid PPPoESessionID) (packet *PPPoEPacket, err error) { packet = &PPPoEPacket{ SrcHWAddr: sourceHWAddr, DstHWAddr: destHWAddr, Code: PPPoECodePADS, SessionID: sid, } err = packet.AddServiceNameTag(serviceName) if err != nil { return nil, err } return } // NewPADT returns a PADT packet for the specified session ID. // // PADT packets are used by either client or server to terminate the PPPoE // connection once established. func NewPADT(sourceHWAddr [6]byte, destHWAddr [6]byte, sid PPPoESessionID) (packet *PPPoEPacket, err error) { return &PPPoEPacket{ SrcHWAddr: sourceHWAddr, DstHWAddr: destHWAddr, Code: PPPoECodePADT, SessionID: sid, }, nil } // pppoeHeader is the on-the-wire structure which we use for parsing // raw data buffers received on a connection. type pppoeHeader struct { // Ethernet header DstHWAddr [6]byte SrcHWAddr [6]byte EtherType uint16 // PPPoE header VerType uint8 Code uint8 SessionID uint16 Length uint16 } func findTag(typ PPPoETagType, tags []*PPPoETag) (tag *PPPoETag, err error) { for _, tag = range tags { if tag.Type == typ { return tag, nil } } return nil, fmt.Errorf("no tag %v found", typ) } // packetSpec is used to define the requirements of each PPPoE packet // as per RFC2516, allowing packets to be validated on receipt and prior // to transmission. type packetSpec struct { zeroSessionID bool mandatoryTags []PPPoETagType } // Validate validates a packet meets the requirements of RFC2516, checking // the mandatory tags are included and the session ID is set correctly. func (packet *PPPoEPacket) Validate() (err error) { specMap := map[PPPoECode]*packetSpec{ PPPoECodePADI: &packetSpec{ zeroSessionID: true, mandatoryTags: []PPPoETagType{PPPoETagTypeServiceName}, }, PPPoECodePADO: &packetSpec{ zeroSessionID: true, mandatoryTags: []PPPoETagType{PPPoETagTypeServiceName, PPPoETagTypeACName}, }, PPPoECodePADR: &packetSpec{ zeroSessionID: true, mandatoryTags: []PPPoETagType{PPPoETagTypeServiceName}, }, // PPPoECodePADS is a special case :-| PPPoECodePADT: &packetSpec{ zeroSessionID: false, }, } var spec *packetSpec var tags []*PPPoETag var ok bool if spec, ok = specMap[packet.Code]; !ok { // PADS is a special case: its mandatory tag list varies depending on whether // the access concentrator likes the service name in the PADR or not. The session // ID is used to determine whether it's the happy or sad path: session ID of zero // is used in the sad path. if packet.Code == PPPoECodePADS { if packet.SessionID == 0 { spec = &packetSpec{ zeroSessionID: true, mandatoryTags: []PPPoETagType{PPPoETagTypeServiceNameError}, } } else { spec = &packetSpec{ zeroSessionID: false, mandatoryTags: []PPPoETagType{PPPoETagTypeServiceName}, } } } else { return fmt.Errorf("unrecognised packet code %v", packet.Code) } } if spec.zeroSessionID { if packet.SessionID != 0 { return fmt.Errorf("nonzero session ID in %v; must have zero", packet.Code) } } else { if packet.SessionID == 0 { return fmt.Errorf("zero session ID in %v; must have nonzero", packet.Code) } } if len(packet.Tags) < len(spec.mandatoryTags) { return fmt.Errorf("expect minimum of %d tags in %v; only got %d", len(spec.mandatoryTags), packet.Code, len(tags)) } for _, tagType := range spec.mandatoryTags { _, err := findTag(tagType, packet.Tags) if err != nil { return fmt.Errorf("missing mandatory tag %v in %v", tagType, packet.Code) } } return nil } // pppoeTagHeader is the on-the-wire structure which we use for parsing // raw TLV tags received on a connection. type pppoeTagHeader struct { Type PPPoETagType Length uint16 } func newTagListFromBuffer(buf []byte) (tags []*PPPoETag, err error) { r := bytes.NewReader(buf) for r.Len() >= pppoeTagMinLength { var cursor int64 var hdr pppoeTagHeader if cursor, err = r.Seek(0, io.SeekCurrent); err != nil { return nil, fmt.Errorf("failed to determine tag buffer offset: %v", err) } if err = binary.Read(r, binary.BigEndian, &hdr); err != nil { return nil, err } if int(hdr.Length) > r.Len() { return nil, fmt.Errorf("malformed tag: length %d exceeds buffer bounds of %d", hdr.Length, r.Len()) } tags = append(tags, &PPPoETag{ Type: hdr.Type, Data: buf[cursor+pppoeTagMinLength : cursor+pppoeTagMinLength+int64(hdr.Length)], }) if _, err := r.Seek(int64(hdr.Length), io.SeekCurrent); err != nil { return nil, fmt.Errorf("malformed tag buffer: invalid length for current tag") } } return } func newPacketFromBuffer(hdr *pppoeHeader, payload []byte) (packet *PPPoEPacket, err error) { // make sure we recognise the packet type switch PPPoECode(hdr.Code) { case PPPoECodePADI: case PPPoECodePADO: case PPPoECodePADR: case PPPoECodePADS: case PPPoECodePADT: default: return nil, fmt.Errorf("unrecognised packet code %x", hdr.Code) } tags, err := newTagListFromBuffer(payload) if err != nil { return nil, fmt.Errorf("failed to parse packet tags: %v", err) } packet = &PPPoEPacket{ SrcHWAddr: hdr.SrcHWAddr, DstHWAddr: hdr.DstHWAddr, Code: PPPoECode(hdr.Code), SessionID: PPPoESessionID(hdr.SessionID), Tags: tags, } err = packet.Validate() if err != nil { return nil, fmt.Errorf("failed to validate packet: %v", err) } return } // ParsePacketBuffer parses a raw received frame into one or more PPPoE // packets. func ParsePacketBuffer(b []byte) (packets []*PPPoEPacket, err error) { r := bytes.NewReader(b) for r.Len() >= pppoePacketMinLength { var cursor int64 var hdr pppoeHeader if cursor, err = r.Seek(0, io.SeekCurrent); err != nil { return nil, fmt.Errorf("failed to determine packet buffer offset: %v", err) } if err = binary.Read(r, binary.BigEndian, &hdr); err != nil { return nil, err } if int(hdr.Length) > r.Len() { return nil, fmt.Errorf("malformed packet: length %d exceeds buffer bounds of %d", hdr.Length, r.Len()) } // Silently ignore packets which are not PPPoE discovery packets if hdr.EtherType == ethTypeDiscovery() { packet, err := newPacketFromBuffer(&hdr, b[cursor+pppoePacketMinLength:cursor+pppoePacketMinLength+int64(hdr.Length)]) if err != nil { return nil, fmt.Errorf("failed to parse packet: %v", err) } packets = append(packets, packet) } if _, err := r.Seek(int64(hdr.Length), io.SeekCurrent); err != nil { return nil, fmt.Errorf("malformed packet buffer: invalid length for current tag") } } return } func newTag(typ PPPoETagType, length int, data []byte) *PPPoETag { return &PPPoETag{ Type: typ, Data: data, } } func (tag *PPPoETag) toBytes() (encoded []byte, err error) { encBuf := new(bytes.Buffer) err = binary.Write(encBuf, binary.BigEndian, tag.Type) if err != nil { return nil, fmt.Errorf("unable to write tag type: %v", err) } err = binary.Write(encBuf, binary.BigEndian, uint16(len(tag.Data))) if err != nil { return nil, fmt.Errorf("unable to write tag length: %v", err) } _, _ = encBuf.Write(tag.Data) return encBuf.Bytes(), nil } func (packet *PPPoEPacket) appendTag(tag *PPPoETag) (err error) { packet.Tags = append(packet.Tags, tag) return } // GetTag searches a packet's tags to find one of the specified type. // // The first tag matching the specified type is returned on success. func (packet *PPPoEPacket) GetTag(typ PPPoETagType) (tag *PPPoETag, err error) { return findTag(typ, packet.Tags) } // AddServiceNameTag adds a service name tag to the packet. // The service name is an arbitrary string. func (packet *PPPoEPacket) AddServiceNameTag(name string) (err error) { return packet.appendTag(newTag(PPPoETagTypeServiceName, len(name), []byte(name))) } // AddACNameTag adds an access concentrator name tag to the packet. // The AC name is an arbitrary string. func (packet *PPPoEPacket) AddACNameTag(name string) (err error) { return packet.appendTag(newTag(PPPoETagTypeACName, len(name), []byte(name))) } // AddHostUniqTag adds a host unique tag to the packet. // The host unique value is an arbitrary byte slice which is used by // the client to associate a given response (PADO or PADS) to a particular // request (PADI or PADR). func (packet *PPPoEPacket) AddHostUniqTag(hostUniq []byte) (err error) { return packet.appendTag(newTag(PPPoETagTypeHostUniq, len(hostUniq), hostUniq)) } // AddACCookieTag adds an access concentrator cookie tag to the packet. // The AC cookie value is an arbitrary byte slice which is used by the // access concentrator to aid in protecting against DoS attacks. // Refer to RFC2516 for details. func (packet *PPPoEPacket) AddACCookieTag(cookie []byte) (err error) { return packet.appendTag(newTag(PPPoETagTypeACCookie, len(cookie), cookie)) } // AddServiceNameErrorTag adds a service name error tag to the packet. // The value may be an empty string, but should preferably be a human-readable // string explaining why the request was denied. func (packet *PPPoEPacket) AddServiceNameErrorTag(reason string) (err error) { return packet.appendTag(newTag(PPPoETagTypeServiceNameError, len(reason), []byte(reason))) } // AddACSystemErrorTag adds an access concentrator system error tag to the packet. // The value may be an empty string, but should preferably be a human-readable // string explaining the nature of the error. func (packet *PPPoEPacket) AddACSystemErrorTag(reason string) (err error) { return packet.appendTag(newTag(PPPoETagTypeACSystemError, len(reason), []byte(reason))) } // AddGenericErrorTag adds an generic error tag to the packet. // The value may be an empty string, but should preferably be a human-readable // string explaining the nature of the error. func (packet *PPPoEPacket) AddGenericErrorTag(reason string) (err error) { return packet.appendTag(newTag(PPPoETagTypeGenericError, len(reason), []byte(reason))) } // AddTag adds a generic tag to the packet. // The caller is responsible for ensuring that the data type matches the tag type. func (packet *PPPoEPacket) AddTag(typ PPPoETagType, data []byte) (err error) { return packet.appendTag(newTag(typ, len(data), data)) } func (packet *PPPoEPacket) tagListBytes() (encoded []byte, err error) { encBuf := new(bytes.Buffer) for _, tag := range packet.Tags { encodedTag, err := tag.toBytes() if err != nil { return nil, fmt.Errorf("failed to encode tag %v: %v", tag, err) } _, _ = encBuf.Write(encodedTag) } return encBuf.Bytes(), nil } // ToBytes renders the PPPoE packet to a byte slice ready for transmission // over a PPPoEConn connection. // // Prior to calling ToBytes a packet should ideally be validated using Validate // to ensure it adheres to the RFC requirements. func (packet *PPPoEPacket) ToBytes() (encoded []byte, err error) { encBuf := new(bytes.Buffer) encodedTags, err := packet.tagListBytes() if err != nil { return nil, err } // bytes.Buffer.Write always returns a nil error // Ethernet header: dst, src, type _, _ = encBuf.Write(packet.DstHWAddr[:]) _, _ = encBuf.Write(packet.SrcHWAddr[:]) _, _ = encBuf.Write(ethTypeDiscoveryNetBytes()) // PPPoE header: VerType, code, session ID, length, payload _, _ = encBuf.Write([]byte{0x11}) _, _ = encBuf.Write([]byte{byte(packet.Code)}) err = binary.Write(encBuf, binary.BigEndian, packet.SessionID) if err != nil { return nil, fmt.Errorf("unable to write session ID: %v", err) } err = binary.Write(encBuf, binary.BigEndian, uint16(len(encodedTags))) if err != nil { return nil, fmt.Errorf("unable to write data length: %v", err) } _, _ = encBuf.Write(encodedTags) return encBuf.Bytes(), nil } golang-github-katalix-go-l2tp-0.1.7/pppoe/pppoe_test.go000066400000000000000000000225361456471064000230360ustar00rootroot00000000000000package pppoe import ( "fmt" "os/exec" "os/user" "reflect" "sync" "testing" ) const ( testVeth0 = "vetest0" testVeth1 = "vetest1" ) func TestTagRenderAndParse(t *testing.T) { cases := []struct { name string tags []*PPPoETag }{ { name: "service name", tags: []*PPPoETag{ &PPPoETag{ Type: PPPoETagTypeServiceName, Data: []byte("myMagicService"), }, }, }, { name: "ac name", tags: []*PPPoETag{ &PPPoETag{ Type: PPPoETagTypeACName, Data: []byte("ThisSpecialAC"), }, }, }, { name: "host uniq", tags: []*PPPoETag{ &PPPoETag{ Type: PPPoETagTypeHostUniq, Data: []byte{0x42, 0x81, 0xba, 0x3b, 0xc6, 0x1e, 0x94, 0xb1}, }, }, }, { name: "cookie", tags: []*PPPoETag{ &PPPoETag{ Type: PPPoETagTypeACCookie, Data: []byte{0x37, 0xd0, 0xba, 0x3b, 0x94, 0x82, 0xc6, 0x1e, 0x01, 0xc3, 0x42, 0x81, 0xa5, 0x93, 0xf9, 0x13}, }, }, }, { name: "service name error", tags: []*PPPoETag{ &PPPoETag{ Type: PPPoETagTypeServiceNameError, Data: []byte{}, }, }, }, { name: "ac system error", tags: []*PPPoETag{ &PPPoETag{ Type: PPPoETagTypeACSystemError, Data: []byte("insufficient resources to create a virtual circuit"), }, }, }, { name: "generic error", tags: []*PPPoETag{ &PPPoETag{ Type: PPPoETagTypeGenericError, Data: []byte("out of cheese error"), }, }, }, { name: "multiple tags", tags: []*PPPoETag{ &PPPoETag{ Type: PPPoETagTypeHostUniq, Data: []byte{0x42, 0x81, 0xba, 0x3b, 0xc6, 0x1e, 0x94, 0xb1}, }, &PPPoETag{ Type: PPPoETagTypeACCookie, Data: []byte{0x37, 0xd0, 0xba, 0x3b, 0x94, 0x82, 0xc6, 0x1e, 0x01, 0xc3, 0x42, 0x81, 0xa5, 0x93, 0xf9, 0x13}, }, &PPPoETag{ Type: PPPoETagTypeServiceName, Data: []byte("myMagicService"), }, &PPPoETag{ Type: PPPoETagTypeACName, Data: []byte("ThisSpecialAC"), }, }, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { srcHWAddr := [6]byte{0x12, 0x42, 0xae, 0x10, 0xf9, 0x48} dstHWAddr := [6]byte{0x22, 0xa2, 0xa4, 0x19, 0xfb, 0xc8} sid := PPPoESessionID(15241) // use PADT because it doesn't contain any tags by default pkt, err := NewPADT(srcHWAddr, dstHWAddr, sid) if err != nil { t.Fatalf("NewPADT(%v, %v, %v): %v", srcHWAddr, dstHWAddr, sid, err) } for _, tag := range c.tags { err = pkt.AddTag(tag.Type, tag.Data) if err != nil { t.Fatalf("AddTag(%v): %v", tag, err) } } b, err := pkt.tagListBytes() if err != nil { t.Fatalf("tagListBytes: %v", err) } tags, err := newTagListFromBuffer(b) if err != nil { t.Fatalf("newTagListFromBuffer(%q): %v", tags, err) } if !reflect.DeepEqual(tags, c.tags) { t.Errorf("Expect: %v, got: %v", c.tags, tags) } }) } } func TestPacketRenderAndParse(t *testing.T) { cases := []struct { name string genPacket func(t *testing.T) *PPPoEPacket }{ { name: "PADI", genPacket: func(t *testing.T) *PPPoEPacket { packet, err := NewPADI([6]byte{0x81, 0x82, 0x83, 0x84, 0x85, 0x86}, "MegaCorpAC") if err != nil { t.Fatalf("NewPADI: %v", err) } err = packet.AddHostUniqTag([]byte("wakw39485ryjn398")) if err != nil { t.Fatalf("AddHostUniqTag: %v", err) } return packet }, }, { name: "PADO", genPacket: func(t *testing.T) *PPPoEPacket { packet, err := NewPADO( [6]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6}, [6]byte{0x81, 0x82, 0x83, 0x84, 0x85, 0x86}, "MegaCorpAC", "WunderAC_2001") if err != nil { t.Fatalf("NewPADO: %v", err) } for _, sn := range []string{"WomblesFC", "BatmanLives", "CuriousEarthling", "WilliamWonka"} { err = packet.AddServiceNameTag(sn) if err != nil { t.Fatalf("AddServiceNameTag: %v", err) } } err = packet.AddHostUniqTag([]byte("wakw39485ryjn398")) if err != nil { t.Fatalf("AddHostUniqTag: %v", err) } err = packet.AddACCookieTag([]byte("0912340u9q23ejow3er09u235oih")) if err != nil { t.Fatalf("AddACCookieTag: %v", err) } return packet }, }, { name: "PADR", genPacket: func(t *testing.T) *PPPoEPacket { packet, err := NewPADR( [6]byte{0x81, 0x82, 0x83, 0x84, 0x85, 0x86}, [6]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6}, "MegaCorpAC") if err != nil { t.Fatalf("NewPADR: %v", err) } err = packet.AddHostUniqTag([]byte("wakw39485ryjn398")) if err != nil { t.Fatalf("AddHostUniqTag: %v", err) } err = packet.AddACCookieTag([]byte("0912340u9q23ejow3er09u235oih")) if err != nil { t.Fatalf("AddACCookieTag: %v", err) } return packet }, }, { name: "PADS", genPacket: func(t *testing.T) *PPPoEPacket { packet, err := NewPADS( [6]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6}, [6]byte{0x81, 0x82, 0x83, 0x84, 0x85, 0x86}, "MegaCorpAC", PPPoESessionID(12345)) if err != nil { t.Fatalf("NewPADS: %v", err) } err = packet.AddHostUniqTag([]byte("wakw39485ryjn398")) if err != nil { t.Fatalf("AddHostUniqTag: %v", err) } return packet }, }, { name: "PADSError", genPacket: func(t *testing.T) *PPPoEPacket { packet, err := NewPADS( [6]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6}, [6]byte{0x81, 0x82, 0x83, 0x84, 0x85, 0x86}, "MegaCorpAC", PPPoESessionID(0)) if err != nil { t.Fatalf("NewPADS: %v", err) } err = packet.AddHostUniqTag([]byte("wakw39485ryjn398")) if err != nil { t.Fatalf("AddHostUniqTag: %v", err) } err = packet.AddServiceNameErrorTag("I don't like this service name after all, sorry") if err != nil { t.Fatalf("AddServiceNameErrorTag: %v", err) } return packet }, }, { name: "PADT", genPacket: func(t *testing.T) *PPPoEPacket { packet, err := NewPADT( [6]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6}, [6]byte{0x81, 0x82, 0x83, 0x84, 0x85, 0x86}, PPPoESessionID(12345)) if err != nil { t.Fatalf("NewPADT: %v", err) } err = packet.AddACSystemErrorTag("OUT OF CHEESE ERROR") if err != nil { t.Fatalf("AddACSystemErrorTag: %v", err) } return packet }, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { packet := c.genPacket(t) fmt.Printf("%v\n", packet) encoded, err := packet.ToBytes() if err != nil { t.Fatalf("ToBytes: %v", err) } parsed, err := ParsePacketBuffer(encoded) if err != nil { t.Fatalf("ParsePacketBuffer(%x): %v", encoded, err) } if len(parsed) != 1 { t.Fatalf("expected 1 parsed packet, got %d", len(parsed)) } if !reflect.DeepEqual(parsed[0], packet) { t.Errorf("Expect: %v, got: %v", packet, parsed[0]) } }) } } func createTestVethPair() (err error) { cmd := exec.Command("sudo", "ip", "link", "add", "dev", testVeth0, "type", "veth", "peer", "name", testVeth1) err = cmd.Run() if err != nil { return fmt.Errorf("unable to create veth pair: %v", err) } cmd = exec.Command("sudo", "ip", "link", "set", testVeth0, "up") err = cmd.Run() if err != nil { return fmt.Errorf("unable to set %s up: %v", testVeth0, err) } cmd = exec.Command("sudo", "ip", "link", "set", testVeth1, "up") err = cmd.Run() if err != nil { return fmt.Errorf("unable to set %s up: %v", testVeth1, err) } return nil } func deleteTestVethPair() (err error) { cmd := exec.Command("sudo", "ip", "link", "delete", "dev", testVeth0) err = cmd.Run() if err != nil { return fmt.Errorf("failed to delete veth interface %s: %v", testVeth0, err) } return nil } func testConnSendRecv(t *testing.T) { recvBuf := make([]byte, 1500) conn0, err := NewDiscoveryConnection(testVeth0) if err != nil { t.Fatalf("NewDiscoveryConnection: %v", err) } defer conn0.Close() conn1, err := NewDiscoveryConnection(testVeth1) if err != nil { t.Fatalf("NewDiscoveryConnection: %v", err) } defer conn1.Close() var startWg, endWg sync.WaitGroup startWg.Add(1) endWg.Add(1) go func() { startWg.Done() _, err = conn1.Recv(recvBuf) if err != nil { t.Errorf("Recv: %v", err) } endWg.Done() }() startWg.Wait() pkt, err := NewPADI(conn0.HWAddr(), "BobsService") if err != nil { t.Fatalf("NewPADI: %v", err) } b, err := pkt.ToBytes() if err != nil { t.Fatalf("ToBytes: %v", err) } _, err = conn0.Send(b) if err != nil { t.Fatalf("Send: %v", err) } endWg.Wait() parsed, err := ParsePacketBuffer(recvBuf) if err != nil { t.Fatalf("ParsePacketBuffer(%x): %v", recvBuf, err) } if len(parsed) != 1 { t.Fatalf("expected 1 parsed packet, got %d", len(parsed)) } if !reflect.DeepEqual(parsed[0], pkt) { t.Errorf("Expect: %v, got: %v", pkt, parsed[0]) } } func TestRequiresRoot(t *testing.T) { // These tests need root permissions, so verify we have those first of all user, err := user.Current() if err != nil { t.Errorf("Unable to obtain current user: %q", err) } if user.Uid != "0" { t.Skip("skipping test because we don't have root permissions") } // Set up veth pair to use for connection tests err = createTestVethPair() if err != nil { t.Fatalf("%v", err) } tests := []struct { name string testFn func(t *testing.T) }{ { name: "conn send/recv", testFn: testConnSendRecv, }, } for _, sub := range tests { t.Run(sub.name, sub.testFn) } // Tear down veth pair err = deleteTestVethPair() if err != nil { t.Errorf("%v", err) } } golang-github-katalix-go-l2tp-0.1.7/runtests.sh000077500000000000000000000007551456471064000214270ustar00rootroot00000000000000#!/bin/bash -x SUBDIRS=$(find . -name "*_test.go" | xargs grep -rl TestRequiresRoot | { while read l; do dirname $l; done } | sort | uniq) rm -f coverage.out coverage.html go test -coverprofile coverage.out -v ./... || exit 1 for sub in $SUBDIRS; do go test -exec sudo -run TestRequiresRoot -coverprofile coverage.tmp $sub && \ grep -v "^mode" coverage.tmp >> coverage.out && \ rm coverage.tmp || \ exit 1 done go tool cover -html=coverage.out -o coverage.html