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"
"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)
}

@ -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() {

@ -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