File: nl_dataplane.go

package info (click to toggle)
golang-github-katalix-go-l2tp 0.1.8-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 632 kB
  • sloc: ansic: 127; sh: 10; makefile: 4
file content (182 lines) | stat: -rw-r--r-- 5,156 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package 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
}