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>
pull/1593/head
Brad Fitzpatrick 3 years ago
parent bcf571ec97
commit 1642dfdb07

@ -9,8 +9,10 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"net"
"os" "os"
"runtime" "runtime"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -248,7 +250,7 @@ func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func
}) })
sb.MutateSelfStatus(func(ss *ipnstate.PeerStatus) { sb.MutateSelfStatus(func(ss *ipnstate.PeerStatus) {
for _, pln := range b.peerAPIListeners { 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 // TODO: hostinfo, and its networkinfo
@ -1446,8 +1448,14 @@ func (b *LocalBackend) initPeerAPIListener() {
} }
b.peerAPIListeners = nil 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 { for _, a := range b.netMap.Addresses {
ln, err := peerAPIListen(a.IP, b.prevIfState) ln, err := peerAPIListen(a.IP, b.prevIfState, tunName)
if err != nil { if err != nil {
b.logf("[unexpected] peerAPI listen(%q) error: %v", a.IP, err) b.logf("[unexpected] peerAPI listen(%q) error: %v", a.IP, err)
continue continue
@ -1456,6 +1464,8 @@ func (b *LocalBackend) initPeerAPIListener() {
ln: ln, ln: ln,
lb: b, lb: b,
} }
pln.urlStr = "http://" + net.JoinHostPort(a.IP.String(), strconv.Itoa(pln.Port()))
go pln.serve() go pln.serve()
b.peerAPIListeners = append(b.peerAPIListeners, pln) b.peerAPIListeners = append(b.peerAPIListeners, pln)
} }

@ -13,6 +13,7 @@ import (
"io" "io"
"net" "net"
"net/http" "net/http"
"runtime"
"strconv" "strconv"
"inet.af/netaddr" "inet.af/netaddr"
@ -20,18 +21,29 @@ import (
"tailscale.com/tailcfg" "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 var lc net.ListenConfig
if initListenConfig != nil { if initListenConfig != nil {
// On iOS/macOS, this sets the lc.Control hook to // On iOS/macOS, this sets the lc.Control hook to
// setsockopt the interface index to bind to, to get // setsockopt the interface index to bind to, to get
// out of the network sandbox. // 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 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 // Make a best effort to pick a deterministic port number for
// the ip The lower three bytes are the same for IPv4 and IPv6 // the ip The lower three bytes are the same for IPv4 and IPv6
// Tailscale addresses (at least currently), so we'll usually // 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 := a16[len(a16)-3:]
hashData[0] += try hashData[0] += try
tryPort := (32 << 10) | uint16(crc32.ChecksumIEEE(hashData)) 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 { if err == nil {
return ln, nil return ln, nil
} }
} }
// Fall back to random ephemeral port. // 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 { type peerAPIListener struct {
ln net.Listener ln net.Listener
lb *LocalBackend 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() { func (pln *peerAPIListener) serve() {

@ -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
}
Loading…
Cancel
Save