unix: implement L2TPIP socket address on Linux
Add constants and types to support IP-encapsulated L2TP/RFC3931
tunnels on Linux systems.
The L2TP subsystem for IP encapsulated tunnels hooks into the inet
kernel code using a specific IP protocol value. In order to handle
this, anyToSockaddr now has to query the socket protocol type using
GetsockoptInt for the AF_INET and AF_INET6 address families.
Although this change is reasonably simple, unit tests have been added
to validate handling of the new types.
Fixes golang/go#37787
Change-Id: I16ae1e24dcced4ccc6ce6a79a90a5a2f6a560967
GitHub-Last-Rev: ca554ad1b6d3a5e55c70f05cf0e542968fa2418c
GitHub-Pull-Request: golang/sys#60
Reviewed-on: https://go-review.googlesource.com/c/sys/+/223157
Reviewed-by: Ian Lance Taylor <iant@golang.org>
diff --git a/unix/linux/types.go b/unix/linux/types.go
index b12f9d1..2cdb7dd 100644
--- a/unix/linux/types.go
+++ b/unix/linux/types.go
@@ -205,6 +205,8 @@
struct sockaddr_ll s5;
struct sockaddr_nl s6;
struct sockaddr_pppox s7;
+ struct sockaddr_l2tpip s8;
+ struct sockaddr_l2tpip6 s9;
};
struct sockaddr_any {
@@ -509,6 +511,10 @@
type RawSockaddrTIPC C.struct_sockaddr_tipc
+type RawSockaddrL2TPIP C.struct_sockaddr_l2tpip
+
+type RawSockaddrL2TPIP6 C.struct_sockaddr_l2tpip6
+
type RawSockaddr C.struct_sockaddr
type RawSockaddrAny C.struct_sockaddr_any
@@ -561,6 +567,8 @@
SizeofSockaddrXDP = C.sizeof_struct_sockaddr_xdp
SizeofSockaddrPPPoX = C.sizeof_struct_sockaddr_pppox
SizeofSockaddrTIPC = C.sizeof_struct_sockaddr_tipc
+ SizeofSockaddrL2TPIP = C.sizeof_struct_sockaddr_l2tpip
+ SizeofSockaddrL2TPIP6 = C.sizeof_struct_sockaddr_l2tpip6
SizeofLinger = C.sizeof_struct_linger
SizeofIovec = C.sizeof_struct_iovec
SizeofIPMreq = C.sizeof_struct_ip_mreq
diff --git a/unix/mkerrors.sh b/unix/mkerrors.sh
index 96bf2a9..c5d0c06 100755
--- a/unix/mkerrors.sh
+++ b/unix/mkerrors.sh
@@ -280,6 +280,11 @@
// for the tipc_subscr timeout __u32 field.
#undef TIPC_WAIT_FOREVER
#define TIPC_WAIT_FOREVER 0xffffffff
+
+// Copied from linux/l2tp.h
+// Including linux/l2tp.h here causes conflicts between linux/in.h
+// and netinet/in.h included via net/route.h above.
+#define IPPROTO_L2TP 115
'
includes_NetBSD='
diff --git a/unix/syscall_internal_linux_test.go b/unix/syscall_internal_linux_test.go
index 8998203..4dafac8 100644
--- a/unix/syscall_internal_linux_test.go
+++ b/unix/syscall_internal_linux_test.go
@@ -12,12 +12,20 @@
"unsafe"
)
+// as per socket(2)
+type SocketSpec struct {
+ domain int
+ typ int
+ protocol int
+}
+
func Test_anyToSockaddr(t *testing.T) {
tests := []struct {
name string
rsa *RawSockaddrAny
sa Sockaddr
err error
+ skt SocketSpec
}{
{
name: "AF_TIPC bad addrtype",
@@ -90,6 +98,45 @@
},
},
{
+ name: "AF_INET IPPROTO_L2TP",
+ rsa: sockaddrL2TPIPToAny(RawSockaddrL2TPIP{
+ Family: AF_INET,
+ Addr: [4]byte{0xef, 0x10, 0x5b, 0xa2},
+ Conn_id: 0x1234abcd,
+ }),
+ sa: &SockaddrL2TPIP{
+ Addr: [4]byte{0xef, 0x10, 0x5b, 0xa2},
+ ConnId: 0x1234abcd,
+ },
+ skt: SocketSpec{domain: AF_INET, typ: SOCK_DGRAM, protocol: IPPROTO_L2TP},
+ },
+ {
+ name: "AF_INET6 IPPROTO_L2TP",
+ rsa: sockaddrL2TPIP6ToAny(RawSockaddrL2TPIP6{
+ Family: AF_INET6,
+ Flowinfo: 42,
+ Addr: [16]byte{
+ 0x20, 0x01, 0x0d, 0xb8,
+ 0x85, 0xa3, 0x00, 0x00,
+ 0x00, 0x00, 0x8a, 0x2e,
+ 0x03, 0x70, 0x73, 0x34,
+ },
+ Scope_id: 90210,
+ Conn_id: 0x1234abcd,
+ }),
+ sa: &SockaddrL2TPIP6{
+ Addr: [16]byte{
+ 0x20, 0x01, 0x0d, 0xb8,
+ 0x85, 0xa3, 0x00, 0x00,
+ 0x00, 0x00, 0x8a, 0x2e,
+ 0x03, 0x70, 0x73, 0x34,
+ },
+ ZoneId: 90210,
+ ConnId: 0x1234abcd,
+ },
+ skt: SocketSpec{domain: AF_INET6, typ: SOCK_DGRAM, protocol: IPPROTO_L2TP},
+ },
+ {
name: "AF_MAX EAFNOSUPPORT",
rsa: &RawSockaddrAny{
Addr: RawSockaddr{
@@ -103,8 +150,21 @@
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- // TODO: parameterize fd (and its setup) when needed.
- sa, err := anyToSockaddr(0, tt.rsa)
+ fd := int(0)
+ var err error
+ if tt.skt.domain != 0 {
+ fd, err = Socket(tt.skt.domain, tt.skt.typ, tt.skt.protocol)
+ // Some sockaddr types need specific kernel modules running: if these
+ // are not present we'll get EPROTONOSUPPORT back when trying to create
+ // the socket. Skip the test in this situation.
+ if err == EPROTONOSUPPORT {
+ t.Skip("socket family/protocol not supported by kernel")
+ } else if err != nil {
+ t.Fatalf("socket(%v): %v", tt.skt, err)
+ }
+ defer Close(fd)
+ }
+ sa, err := anyToSockaddr(fd, tt.rsa)
if err != tt.err {
t.Fatalf("unexpected error: %v, want: %v", err, tt.err)
}
@@ -215,16 +275,130 @@
}
}
+func TestSockaddrL2TPIP_sockaddr(t *testing.T) {
+ tests := []struct {
+ name string
+ sa *SockaddrL2TPIP
+ raw *RawSockaddrL2TPIP
+ err error
+ }{
+ {
+ name: "L2TPIP",
+ sa: &SockaddrL2TPIP{
+ Addr: [4]byte{0xef, 0x10, 0x5b, 0xa2},
+ ConnId: 0x1234abcd,
+ },
+ raw: &RawSockaddrL2TPIP{
+ Family: AF_INET,
+ Addr: [4]byte{0xef, 0x10, 0x5b, 0xa2},
+ Conn_id: 0x1234abcd,
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ out, l, err := tt.sa.sockaddr()
+ if err != tt.err {
+ t.Fatalf("unexpected error: %v, want: %v", err, tt.err)
+ }
+
+ // Must be 0 on error or a fixed size otherwise.
+ if (tt.err != nil && l != 0) || (tt.raw != nil && l != SizeofSockaddrL2TPIP) {
+ t.Fatalf("unexpected Socklen: %d", l)
+ }
+
+ if out != nil {
+ raw := (*RawSockaddrL2TPIP)(out)
+ if !reflect.DeepEqual(raw, tt.raw) {
+ t.Fatalf("unexpected RawSockaddrL2TPIP:\n got: %#v\nwant: %#v", raw, tt.raw)
+ }
+ }
+ })
+ }
+}
+
+func TestSockaddrL2TPIP6_sockaddr(t *testing.T) {
+ tests := []struct {
+ name string
+ sa *SockaddrL2TPIP6
+ raw *RawSockaddrL2TPIP6
+ err error
+ }{
+ {
+ name: "L2TPIP6",
+ sa: &SockaddrL2TPIP6{
+ Addr: [16]byte{
+ 0x20, 0x01, 0x0d, 0xb8,
+ 0x85, 0xa3, 0x00, 0x00,
+ 0x00, 0x00, 0x8a, 0x2e,
+ 0x03, 0x70, 0x73, 0x34,
+ },
+ ZoneId: 90210,
+ ConnId: 0x1234abcd,
+ },
+ raw: &RawSockaddrL2TPIP6{
+ Family: AF_INET6,
+ Addr: [16]byte{
+ 0x20, 0x01, 0x0d, 0xb8,
+ 0x85, 0xa3, 0x00, 0x00,
+ 0x00, 0x00, 0x8a, 0x2e,
+ 0x03, 0x70, 0x73, 0x34,
+ },
+ Scope_id: 90210,
+ Conn_id: 0x1234abcd,
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ out, l, err := tt.sa.sockaddr()
+ if err != tt.err {
+ t.Fatalf("unexpected error: %v, want: %v", err, tt.err)
+ }
+
+ // Must be 0 on error or a fixed size otherwise.
+ if (tt.err != nil && l != 0) || (tt.raw != nil && l != SizeofSockaddrL2TPIP6) {
+ t.Fatalf("unexpected Socklen: %d", l)
+ }
+
+ if out != nil {
+ raw := (*RawSockaddrL2TPIP6)(out)
+ if !reflect.DeepEqual(raw, tt.raw) {
+ t.Fatalf("unexpected RawSockaddrL2TPIP6:\n got: %#v\nwant: %#v", raw, tt.raw)
+ }
+ }
+ })
+ }
+}
+
+// These helpers explicitly copy the contents of in into out to produce
+// the correct sockaddr structure, without relying on unsafe casting to
+// a type of a larger size.
func sockaddrTIPCToAny(in RawSockaddrTIPC) *RawSockaddrAny {
var out RawSockaddrAny
-
- // Explicitly copy the contents of in into out to produce the correct
- // sockaddr structure, without relying on unsafe casting to a type of a
- // larger size.
copy(
(*(*[SizeofSockaddrAny]byte)(unsafe.Pointer(&out)))[:],
(*(*[SizeofSockaddrTIPC]byte)(unsafe.Pointer(&in)))[:],
)
+ return &out
+}
+func sockaddrL2TPIPToAny(in RawSockaddrL2TPIP) *RawSockaddrAny {
+ var out RawSockaddrAny
+ copy(
+ (*(*[SizeofSockaddrAny]byte)(unsafe.Pointer(&out)))[:],
+ (*(*[SizeofSockaddrL2TPIP]byte)(unsafe.Pointer(&in)))[:],
+ )
+ return &out
+}
+
+func sockaddrL2TPIP6ToAny(in RawSockaddrL2TPIP6) *RawSockaddrAny {
+ var out RawSockaddrAny
+ copy(
+ (*(*[SizeofSockaddrAny]byte)(unsafe.Pointer(&out)))[:],
+ (*(*[SizeofSockaddrL2TPIP6]byte)(unsafe.Pointer(&in)))[:],
+ )
return &out
}
diff --git a/unix/syscall_linux.go b/unix/syscall_linux.go
index 95f7a15..bbe1abb 100644
--- a/unix/syscall_linux.go
+++ b/unix/syscall_linux.go
@@ -839,6 +839,40 @@
return unsafe.Pointer(&sa.raw), SizeofSockaddrTIPC, nil
}
+// SockaddrL2TPIP implements the Sockaddr interface for IPPROTO_L2TP/AF_INET sockets.
+type SockaddrL2TPIP struct {
+ Addr [4]byte
+ ConnId uint32
+ raw RawSockaddrL2TPIP
+}
+
+func (sa *SockaddrL2TPIP) sockaddr() (unsafe.Pointer, _Socklen, error) {
+ sa.raw.Family = AF_INET
+ sa.raw.Conn_id = sa.ConnId
+ for i := 0; i < len(sa.Addr); i++ {
+ sa.raw.Addr[i] = sa.Addr[i]
+ }
+ return unsafe.Pointer(&sa.raw), SizeofSockaddrL2TPIP, nil
+}
+
+// SockaddrL2TPIP6 implements the Sockaddr interface for IPPROTO_L2TP/AF_INET6 sockets.
+type SockaddrL2TPIP6 struct {
+ Addr [16]byte
+ ZoneId uint32
+ ConnId uint32
+ raw RawSockaddrL2TPIP6
+}
+
+func (sa *SockaddrL2TPIP6) sockaddr() (unsafe.Pointer, _Socklen, error) {
+ sa.raw.Family = AF_INET6
+ sa.raw.Conn_id = sa.ConnId
+ sa.raw.Scope_id = sa.ZoneId
+ for i := 0; i < len(sa.Addr); i++ {
+ sa.raw.Addr[i] = sa.Addr[i]
+ }
+ return unsafe.Pointer(&sa.raw), SizeofSockaddrL2TPIP6, nil
+}
+
func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) {
switch rsa.Addr.Family {
case AF_NETLINK:
@@ -889,25 +923,58 @@
return sa, nil
case AF_INET:
- pp := (*RawSockaddrInet4)(unsafe.Pointer(rsa))
- sa := new(SockaddrInet4)
- p := (*[2]byte)(unsafe.Pointer(&pp.Port))
- sa.Port = int(p[0])<<8 + int(p[1])
- for i := 0; i < len(sa.Addr); i++ {
- sa.Addr[i] = pp.Addr[i]
+ proto, err := GetsockoptInt(fd, SOL_SOCKET, SO_PROTOCOL)
+ if err != nil {
+ return nil, err
}
- return sa, nil
+
+ switch proto {
+ case IPPROTO_L2TP:
+ pp := (*RawSockaddrL2TPIP)(unsafe.Pointer(rsa))
+ sa := new(SockaddrL2TPIP)
+ sa.ConnId = pp.Conn_id
+ for i := 0; i < len(sa.Addr); i++ {
+ sa.Addr[i] = pp.Addr[i]
+ }
+ return sa, nil
+ default:
+ pp := (*RawSockaddrInet4)(unsafe.Pointer(rsa))
+ sa := new(SockaddrInet4)
+ p := (*[2]byte)(unsafe.Pointer(&pp.Port))
+ sa.Port = int(p[0])<<8 + int(p[1])
+ for i := 0; i < len(sa.Addr); i++ {
+ sa.Addr[i] = pp.Addr[i]
+ }
+ return sa, nil
+ }
case AF_INET6:
- pp := (*RawSockaddrInet6)(unsafe.Pointer(rsa))
- sa := new(SockaddrInet6)
- p := (*[2]byte)(unsafe.Pointer(&pp.Port))
- sa.Port = int(p[0])<<8 + int(p[1])
- sa.ZoneId = pp.Scope_id
- for i := 0; i < len(sa.Addr); i++ {
- sa.Addr[i] = pp.Addr[i]
+ proto, err := GetsockoptInt(fd, SOL_SOCKET, SO_PROTOCOL)
+ if err != nil {
+ return nil, err
}
- return sa, nil
+
+ switch proto {
+ case IPPROTO_L2TP:
+ pp := (*RawSockaddrL2TPIP6)(unsafe.Pointer(rsa))
+ sa := new(SockaddrL2TPIP6)
+ sa.ConnId = pp.Conn_id
+ sa.ZoneId = pp.Scope_id
+ for i := 0; i < len(sa.Addr); i++ {
+ sa.Addr[i] = pp.Addr[i]
+ }
+ return sa, nil
+ default:
+ pp := (*RawSockaddrInet6)(unsafe.Pointer(rsa))
+ sa := new(SockaddrInet6)
+ p := (*[2]byte)(unsafe.Pointer(&pp.Port))
+ sa.Port = int(p[0])<<8 + int(p[1])
+ sa.ZoneId = pp.Scope_id
+ for i := 0; i < len(sa.Addr); i++ {
+ sa.Addr[i] = pp.Addr[i]
+ }
+ return sa, nil
+ }
case AF_VSOCK:
pp := (*RawSockaddrVM)(unsafe.Pointer(rsa))
diff --git a/unix/zerrors_linux.go b/unix/zerrors_linux.go
index 5be454c..84c599c 100644
--- a/unix/zerrors_linux.go
+++ b/unix/zerrors_linux.go
@@ -890,6 +890,7 @@
IPPROTO_IP = 0x0
IPPROTO_IPIP = 0x4
IPPROTO_IPV6 = 0x29
+ IPPROTO_L2TP = 0x73
IPPROTO_MH = 0x87
IPPROTO_MPLS = 0x89
IPPROTO_MTP = 0x5c
diff --git a/unix/ztypes_linux.go b/unix/ztypes_linux.go
index 6c81e75..cb5e06c 100644
--- a/unix/ztypes_linux.go
+++ b/unix/ztypes_linux.go
@@ -243,6 +243,23 @@
Addr [12]byte
}
+type RawSockaddrL2TPIP struct {
+ Family uint16
+ Unused uint16
+ Addr [4]byte /* in_addr */
+ Conn_id uint32
+ _ [4]uint8
+}
+
+type RawSockaddrL2TPIP6 struct {
+ Family uint16
+ Unused uint16
+ Flowinfo uint32
+ Addr [16]byte /* in6_addr */
+ Scope_id uint32
+ Conn_id uint32
+}
+
type _Socklen uint32
type Linger struct {
@@ -353,6 +370,8 @@
SizeofSockaddrXDP = 0x10
SizeofSockaddrPPPoX = 0x1e
SizeofSockaddrTIPC = 0x10
+ SizeofSockaddrL2TPIP = 0x10
+ SizeofSockaddrL2TPIP6 = 0x20
SizeofLinger = 0x8
SizeofIPMreq = 0x8
SizeofIPMreqn = 0xc