File: controlplane.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 (145 lines) | stat: -rw-r--r-- 3,114 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
package 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
}