From 9a0dfc3c55686ed331929a6eace0b5c5b9c18471 Mon Sep 17 00:00:00 2001 From: Tom Parkin Date: Thu, 12 Mar 2020 20:42:32 +0000 Subject: [PATCH] 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 --- unix/linux/types.go | 8 ++ unix/mkerrors.sh | 5 + unix/syscall_internal_linux_test.go | 186 +++++++++++++++++++++++++++- unix/syscall_linux.go | 97 ++++++++++++--- unix/zerrors_linux.go | 1 + unix/ztypes_linux.go | 19 +++ 6 files changed, 295 insertions(+), 21 deletions(-) diff --git a/unix/linux/types.go b/unix/linux/types.go index b12f9d1e..2cdb7dd1 100644 --- a/unix/linux/types.go +++ b/unix/linux/types.go @@ -205,6 +205,8 @@ union sockaddr_all { 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 RawSockaddrPPPoX [C.sizeof_struct_sockaddr_pppox]byte 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 @@ const ( 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 96bf2a91..c5d0c068 100755 --- a/unix/mkerrors.sh +++ b/unix/mkerrors.sh @@ -280,6 +280,11 @@ struct ltchars { // 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 89982036..4dafac80 100644 --- a/unix/syscall_internal_linux_test.go +++ b/unix/syscall_internal_linux_test.go @@ -12,12 +12,20 @@ import ( "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", @@ -89,6 +97,45 @@ func Test_anyToSockaddr(t *testing.T) { }, }, }, + { + 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{ @@ -103,8 +150,21 @@ func Test_anyToSockaddr(t *testing.T) { 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 TestSockaddrTIPC_sockaddr(t *testing.T) { } } +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 95f7a159..bbe1abbc 100644 --- a/unix/syscall_linux.go +++ b/unix/syscall_linux.go @@ -839,6 +839,40 @@ func (sa *SockaddrTIPC) sockaddr() (unsafe.Pointer, _Socklen, error) { 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 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { 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 + } + + 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 } - 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 + } + + 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 } - return sa, nil case AF_VSOCK: pp := (*RawSockaddrVM)(unsafe.Pointer(rsa)) diff --git a/unix/zerrors_linux.go b/unix/zerrors_linux.go index 5be454c0..84c599c5 100644 --- a/unix/zerrors_linux.go +++ b/unix/zerrors_linux.go @@ -890,6 +890,7 @@ const ( 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 6c81e751..cb5e06c6 100644 --- a/unix/ztypes_linux.go +++ b/unix/ztypes_linux.go @@ -243,6 +243,23 @@ type RawSockaddrTIPC struct { 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 @@ const ( SizeofSockaddrXDP = 0x10 SizeofSockaddrPPPoX = 0x1e SizeofSockaddrTIPC = 0x10 + SizeofSockaddrL2TPIP = 0x10 + SizeofSockaddrL2TPIP6 = 0x20 SizeofLinger = 0x8 SizeofIPMreq = 0x8 SizeofIPMreqn = 0xc