summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@tailscale.com>2021-03-26 13:44:55 -0700
committerBrad Fitzpatrick <bradfitz@tailscale.com>2021-03-26 13:46:01 -0700
commit1642dfdb0719782ef47744e93837787a9fc2b41c (patch)
tree615e1440ef2bce9de215f86ad66cb58c846396c3
parentbcf571ec971783bc53d7ae5acc3ba5b630cd0e78 (diff)
downloadtailscale-1642dfdb0719782ef47744e93837787a9fc2b41c.tar.xz
tailscale-1642dfdb0719782ef47744e93837787a9fc2b41c.zip
ipn/ipnlocal: get peerapi ~working in macOS/iOS NetworkExtension sandbox
IPv4 and IPv6 both work remotely, but IPv6 doesn't yet work from the machine itself due to routing mysteries. Untested yet on iOS, but previous prototype worked on iOS, so should work the same. Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
-rw-r--r--ipn/ipnlocal/local.go14
-rw-r--r--ipn/ipnlocal/peerapi.go35
-rw-r--r--ipn/ipnlocal/peerapi_macios_ext.go54
3 files changed, 94 insertions, 9 deletions
diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go
index edebb356d..0b21c6bf5 100644
--- a/ipn/ipnlocal/local.go
+++ b/ipn/ipnlocal/local.go
@@ -9,8 +9,10 @@ import (
"context"
"errors"
"fmt"
+ "net"
"os"
"runtime"
+ "strconv"
"strings"
"sync"
"time"
@@ -248,7 +250,7 @@ func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func
})
sb.MutateSelfStatus(func(ss *ipnstate.PeerStatus) {
for _, pln := range b.peerAPIListeners {
- ss.PeerAPIURL = append(ss.PeerAPIURL, "http://"+pln.ln.Addr().String())
+ ss.PeerAPIURL = append(ss.PeerAPIURL, pln.urlStr)
}
})
// TODO: hostinfo, and its networkinfo
@@ -1446,8 +1448,14 @@ func (b *LocalBackend) initPeerAPIListener() {
}
b.peerAPIListeners = nil
+ var tunName string
+ if ge, ok := b.e.(wgengine.InternalsGetter); ok {
+ tunDev, _ := ge.GetInternals()
+ tunName, _ = tunDev.Name()
+ }
+
for _, a := range b.netMap.Addresses {
- ln, err := peerAPIListen(a.IP, b.prevIfState)
+ ln, err := peerAPIListen(a.IP, b.prevIfState, tunName)
if err != nil {
b.logf("[unexpected] peerAPI listen(%q) error: %v", a.IP, err)
continue
@@ -1456,6 +1464,8 @@ func (b *LocalBackend) initPeerAPIListener() {
ln: ln,
lb: b,
}
+ pln.urlStr = "http://" + net.JoinHostPort(a.IP.String(), strconv.Itoa(pln.Port()))
+
go pln.serve()
b.peerAPIListeners = append(b.peerAPIListeners, pln)
}
diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go
index 29bcd0abe..e04429840 100644
--- a/ipn/ipnlocal/peerapi.go
+++ b/ipn/ipnlocal/peerapi.go
@@ -13,6 +13,7 @@ import (
"io"
"net"
"net/http"
+ "runtime"
"strconv"
"inet.af/netaddr"
@@ -20,18 +21,29 @@ import (
"tailscale.com/tailcfg"
)
-var initListenConfig func(*net.ListenConfig, netaddr.IP, *interfaces.State) error
+var initListenConfig func(*net.ListenConfig, netaddr.IP, *interfaces.State, string) error
+
+func peerAPIListen(ip netaddr.IP, ifState *interfaces.State, tunIfName string) (ln net.Listener, err error) {
+ ipStr := ip.String()
-func peerAPIListen(ip netaddr.IP, ifState *interfaces.State) (ln net.Listener, err error) {
var lc net.ListenConfig
if initListenConfig != nil {
// On iOS/macOS, this sets the lc.Control hook to
// setsockopt the interface index to bind to, to get
// out of the network sandbox.
- if err := initListenConfig(&lc, ip, ifState); err != nil {
+ if err := initListenConfig(&lc, ip, ifState, tunIfName); err != nil {
return nil, err
}
+ if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
+ ipStr = ""
+ }
+ }
+
+ tcp4or6 := "tcp4"
+ if ip.Is6() {
+ tcp4or6 = "tcp6"
}
+
// Make a best effort to pick a deterministic port number for
// the ip The lower three bytes are the same for IPv4 and IPv6
// Tailscale addresses (at least currently), so we'll usually
@@ -45,18 +57,27 @@ func peerAPIListen(ip netaddr.IP, ifState *interfaces.State) (ln net.Listener, e
hashData := a16[len(a16)-3:]
hashData[0] += try
tryPort := (32 << 10) | uint16(crc32.ChecksumIEEE(hashData))
- ln, err = lc.Listen(context.Background(), "tcp", net.JoinHostPort(ip.String(), strconv.Itoa(int(tryPort))))
+ ln, err = lc.Listen(context.Background(), tcp4or6, net.JoinHostPort(ipStr, strconv.Itoa(int(tryPort))))
if err == nil {
return ln, nil
}
}
// Fall back to random ephemeral port.
- return lc.Listen(context.Background(), "tcp", net.JoinHostPort(ip.String(), "0"))
+ return lc.Listen(context.Background(), tcp4or6, net.JoinHostPort(ipStr, "0"))
}
type peerAPIListener struct {
- ln net.Listener
- lb *LocalBackend
+ ln net.Listener
+ lb *LocalBackend
+ urlStr string
+}
+
+func (pln *peerAPIListener) Port() int {
+ ta, ok := pln.ln.Addr().(*net.TCPAddr)
+ if !ok {
+ return 0
+ }
+ return ta.Port
}
func (pln *peerAPIListener) serve() {
diff --git a/ipn/ipnlocal/peerapi_macios_ext.go b/ipn/ipnlocal/peerapi_macios_ext.go
new file mode 100644
index 000000000..a75e18eed
--- /dev/null
+++ b/ipn/ipnlocal/peerapi_macios_ext.go
@@ -0,0 +1,54 @@
+// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin,redo ios,redo
+
+package ipnlocal
+
+import (
+ "fmt"
+ "log"
+ "net"
+ "strings"
+ "syscall"
+
+ "golang.org/x/sys/unix"
+ "inet.af/netaddr"
+ "tailscale.com/net/interfaces"
+)
+
+func init() {
+ initListenConfig = initListenConfigNetworkExtension
+}
+
+// initListenConfigNetworkExtension configures nc for listening on IP
+// through the iOS/macOS Network/System Extension (Packet Tunnel
+// Provider) sandbox.
+func initListenConfigNetworkExtension(nc *net.ListenConfig, ip netaddr.IP, st *interfaces.State, tunIfName string) error {
+ tunIf, ok := st.Interface[tunIfName]
+ if !ok {
+ return fmt.Errorf("no interface with name %q", tunIfName)
+ }
+ nc.Control = func(network, address string, c syscall.RawConn) error {
+ var sockErr error
+ err := c.Control(func(fd uintptr) {
+
+ v6 := strings.Contains(address, "]:") || strings.HasSuffix(network, "6") // hacky test for v6
+ proto := unix.IPPROTO_IP
+ opt := unix.IP_BOUND_IF
+ if v6 {
+ proto = unix.IPPROTO_IPV6
+ opt = unix.IPV6_BOUND_IF
+ }
+
+ sockErr = unix.SetsockoptInt(int(fd), proto, opt, tunIf.Index)
+ log.Printf("peerapi: bind(%q, %q) on index %v = %v", network, address, tunIf.Index, sockErr)
+ })
+ if err != nil {
+ return err
+ }
+ return sockErr
+ }
+ return nil
+}