summaryrefslogtreecommitdiffstats
path: root/device/sticky_linux.go
diff options
context:
space:
mode:
authorDavid Crawshaw <crawshaw@tailscale.com>2019-11-07 11:13:05 -0500
committerJason A. Donenfeld <Jason@zx2c4.com>2020-05-02 01:46:42 -0600
commit203554620dc8114de1ff70bb30b80f828e9e26ad (patch)
tree49f36961f2090dc07d15cad3204a1a3531dc233d /device/sticky_linux.go
parentwintun: split error message for create vs open namespace. (diff)
downloadwireguard-go-203554620dc8114de1ff70bb30b80f828e9e26ad.tar.xz
wireguard-go-203554620dc8114de1ff70bb30b80f828e9e26ad.zip
conn: introduce new package that splits out the Bind and Endpoint types
The sticky socket code stays in the device package for now, as it reaches deeply into the peer list. This is the first step in an effort to split some code out of the very busy device package. Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
Diffstat (limited to 'device/sticky_linux.go')
-rw-r--r--device/sticky_linux.go215
1 files changed, 215 insertions, 0 deletions
diff --git a/device/sticky_linux.go b/device/sticky_linux.go
new file mode 100644
index 0000000..f9522c2
--- /dev/null
+++ b/device/sticky_linux.go
@@ -0,0 +1,215 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
+ *
+ * This implements userspace semantics of "sticky sockets", modeled after
+ * WireGuard's kernelspace implementation. This is more or less a straight port
+ * of the sticky-sockets.c example code:
+ * https://git.zx2c4.com/WireGuard/tree/contrib/examples/sticky-sockets/sticky-sockets.c
+ *
+ * Currently there is no way to achieve this within the net package:
+ * See e.g. https://github.com/golang/go/issues/17930
+ * So this code is remains platform dependent.
+ */
+
+package device
+
+import (
+ "sync"
+ "unsafe"
+
+ "golang.org/x/sys/unix"
+ "golang.zx2c4.com/wireguard/conn"
+ "golang.zx2c4.com/wireguard/rwcancel"
+)
+
+func (device *Device) startRouteListener(bind conn.Bind) (*rwcancel.RWCancel, error) {
+ netlinkSock, err := createNetlinkRouteSocket()
+ if err != nil {
+ return nil, err
+ }
+ netlinkCancel, err := rwcancel.NewRWCancel(netlinkSock)
+ if err != nil {
+ unix.Close(netlinkSock)
+ return nil, err
+ }
+
+ go device.routineRouteListener(bind, netlinkSock, netlinkCancel)
+
+ return netlinkCancel, nil
+}
+
+func (device *Device) routineRouteListener(bind conn.Bind, netlinkSock int, netlinkCancel *rwcancel.RWCancel) {
+ type peerEndpointPtr struct {
+ peer *Peer
+ endpoint *conn.Endpoint
+ }
+ var reqPeer map[uint32]peerEndpointPtr
+ var reqPeerLock sync.Mutex
+
+ defer unix.Close(netlinkSock)
+
+ for msg := make([]byte, 1<<16); ; {
+ var err error
+ var msgn int
+ for {
+ msgn, _, _, _, err = unix.Recvmsg(netlinkSock, msg[:], nil, 0)
+ if err == nil || !rwcancel.RetryAfterError(err) {
+ break
+ }
+ if !netlinkCancel.ReadyRead() {
+ return
+ }
+ }
+ if err != nil {
+ return
+ }
+
+ for remain := msg[:msgn]; len(remain) >= unix.SizeofNlMsghdr; {
+
+ hdr := *(*unix.NlMsghdr)(unsafe.Pointer(&remain[0]))
+
+ if uint(hdr.Len) > uint(len(remain)) {
+ break
+ }
+
+ switch hdr.Type {
+ case unix.RTM_NEWROUTE, unix.RTM_DELROUTE:
+ if hdr.Seq <= MaxPeers && hdr.Seq > 0 {
+ if uint(len(remain)) < uint(hdr.Len) {
+ break
+ }
+ if hdr.Len > unix.SizeofNlMsghdr+unix.SizeofRtMsg {
+ attr := remain[unix.SizeofNlMsghdr+unix.SizeofRtMsg:]
+ for {
+ if uint(len(attr)) < uint(unix.SizeofRtAttr) {
+ break
+ }
+ attrhdr := *(*unix.RtAttr)(unsafe.Pointer(&attr[0]))
+ if attrhdr.Len < unix.SizeofRtAttr || uint(len(attr)) < uint(attrhdr.Len) {
+ break
+ }
+ if attrhdr.Type == unix.RTA_OIF && attrhdr.Len == unix.SizeofRtAttr+4 {
+ ifidx := *(*uint32)(unsafe.Pointer(&attr[unix.SizeofRtAttr]))
+ reqPeerLock.Lock()
+ if reqPeer == nil {
+ reqPeerLock.Unlock()
+ break
+ }
+ pePtr, ok := reqPeer[hdr.Seq]
+ reqPeerLock.Unlock()
+ if !ok {
+ break
+ }
+ pePtr.peer.Lock()
+ if &pePtr.peer.endpoint != pePtr.endpoint {
+ pePtr.peer.Unlock()
+ break
+ }
+ if uint32(pePtr.peer.endpoint.(*conn.NativeEndpoint).Src4().Ifindex) == ifidx {
+ pePtr.peer.Unlock()
+ break
+ }
+ pePtr.peer.endpoint.(*conn.NativeEndpoint).ClearSrc()
+ pePtr.peer.Unlock()
+ }
+ attr = attr[attrhdr.Len:]
+ }
+ }
+ break
+ }
+ reqPeerLock.Lock()
+ reqPeer = make(map[uint32]peerEndpointPtr)
+ reqPeerLock.Unlock()
+ go func() {
+ device.peers.RLock()
+ i := uint32(1)
+ for _, peer := range device.peers.keyMap {
+ peer.RLock()
+ if peer.endpoint == nil {
+ peer.RUnlock()
+ continue
+ }
+ nativeEP, _ := peer.endpoint.(*conn.NativeEndpoint)
+ if nativeEP == nil {
+ peer.RUnlock()
+ continue
+ }
+ if nativeEP.IsV6() || nativeEP.Src4().Ifindex == 0 {
+ peer.RUnlock()
+ break
+ }
+ nlmsg := struct {
+ hdr unix.NlMsghdr
+ msg unix.RtMsg
+ dsthdr unix.RtAttr
+ dst [4]byte
+ srchdr unix.RtAttr
+ src [4]byte
+ markhdr unix.RtAttr
+ mark uint32
+ }{
+ unix.NlMsghdr{
+ Type: uint16(unix.RTM_GETROUTE),
+ Flags: unix.NLM_F_REQUEST,
+ Seq: i,
+ },
+ unix.RtMsg{
+ Family: unix.AF_INET,
+ Dst_len: 32,
+ Src_len: 32,
+ },
+ unix.RtAttr{
+ Len: 8,
+ Type: unix.RTA_DST,
+ },
+ nativeEP.Dst4().Addr,
+ unix.RtAttr{
+ Len: 8,
+ Type: unix.RTA_SRC,
+ },
+ nativeEP.Src4().Src,
+ unix.RtAttr{
+ Len: 8,
+ Type: unix.RTA_MARK,
+ },
+ uint32(bind.LastMark()),
+ }
+ nlmsg.hdr.Len = uint32(unsafe.Sizeof(nlmsg))
+ reqPeerLock.Lock()
+ reqPeer[i] = peerEndpointPtr{
+ peer: peer,
+ endpoint: &peer.endpoint,
+ }
+ reqPeerLock.Unlock()
+ peer.RUnlock()
+ i++
+ _, err := netlinkCancel.Write((*[unsafe.Sizeof(nlmsg)]byte)(unsafe.Pointer(&nlmsg))[:])
+ if err != nil {
+ break
+ }
+ }
+ device.peers.RUnlock()
+ }()
+ }
+ remain = remain[hdr.Len:]
+ }
+ }
+}
+
+func createNetlinkRouteSocket() (int, error) {
+ sock, err := unix.Socket(unix.AF_NETLINK, unix.SOCK_RAW, unix.NETLINK_ROUTE)
+ if err != nil {
+ return -1, err
+ }
+ saddr := &unix.SockaddrNetlink{
+ Family: unix.AF_NETLINK,
+ Groups: uint32(1 << (unix.RTNLGRP_IPV4_ROUTE - 1)),
+ }
+ err = unix.Bind(sock, saddr)
+ if err != nil {
+ unix.Close(sock)
+ return -1, err
+ }
+ return sock, nil
+}