cmd/tailscaled, wgengine/netstack: add start of gvisor userspace netstack work

Not usefully functional yet (mostly a proof of concept), but getting
it submitted for some work @namansood is going to do atop this.

Updates #707
Updates #634
Updates #48
Updates #835
This commit is contained in:
Brad Fitzpatrick
2020-09-03 15:45:41 -07:00
parent 5efb0a8bca
commit 5aa5db89d6
11 changed files with 490 additions and 15 deletions
+19
View File
@@ -3497,3 +3497,22 @@ type ippCacheKey struct {
// derpStr replaces DERP IPs in s with "derp-".
func derpStr(s string) string { return strings.ReplaceAll(s, "127.3.3.40:", "derp-") }
// WhoIs reports the node and user who owns the node with the given IP.
// If ok == true, n and u are valid.
func (c *Conn) WhoIs(ip netaddr.IP) (n *tailcfg.Node, u tailcfg.UserProfile, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
if c.netMap == nil {
return n, u, false
}
for _, p := range c.netMap.Peers {
for _, ipp := range p.Addresses {
if ipp.IsSingleIP() && ipp.IP == ip {
u, ok := c.netMap.UserProfiles[p.User]
return p, u, ok
}
}
}
return nil, u, false
}
+154
View File
@@ -0,0 +1,154 @@
// Copyright (c) 2020 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.
// Package netstack wires up gVisor's netstack into Tailscale.
package netstack
import (
"context"
"errors"
"fmt"
"log"
"net"
"strings"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"gvisor.dev/gvisor/pkg/waiter"
"inet.af/netaddr"
"tailscale.com/net/packet"
"tailscale.com/types/logger"
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/magicsock"
"tailscale.com/wgengine/tstun"
)
func Impl(logf logger.Logf, tundev *tstun.TUN, e wgengine.Engine, mc *magicsock.Conn) error {
if mc == nil {
return errors.New("nil magicsock.Conn")
}
if tundev == nil {
return errors.New("nil tundev")
}
if logf == nil {
return errors.New("nil logger")
}
if e == nil {
return errors.New("nil Engine")
}
ipstack := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocol{ipv4.NewProtocol()},
TransportProtocols: []stack.TransportProtocol{tcp.NewProtocol(), udp.NewProtocol(), icmp.NewProtocol4()},
})
const mtu = 1500
linkEP := channel.New(512, mtu, "")
const nicID = 1
if err := ipstack.CreateNIC(nicID, linkEP); err != nil {
log.Fatal(err)
}
ipstack.AddAddress(nicID, ipv4.ProtocolNumber, tcpip.Address(net.ParseIP("100.96.188.101").To4()))
// Add 0.0.0.0/0 default route.
subnet, _ := tcpip.NewSubnet(tcpip.Address(strings.Repeat("\x00", 4)), tcpip.AddressMask(strings.Repeat("\x00", 4)))
ipstack.SetRouteTable([]tcpip.Route{
{
Destination: subnet,
NIC: nicID,
},
})
// use Forwarder to accept any connection from stack
fwd := tcp.NewForwarder(ipstack, 0, 16, func(r *tcp.ForwarderRequest) {
logf("XXX ForwarderRequest: %v", r)
var wq waiter.Queue
ep, err := r.CreateEndpoint(&wq)
if err != nil {
r.Complete(true)
return
}
r.Complete(false)
c := gonet.NewTCPConn(&wq, ep)
// TCP echo
go echo(c, e, mc)
})
ipstack.SetTransportProtocolHandler(tcp.ProtocolNumber, fwd.HandlePacket)
go func() {
for {
packetInfo, ok := linkEP.ReadContext(context.Background())
if !ok {
logf("XXX ReadContext-for-write = ok=false")
continue
}
pkt := packetInfo.Pkt
hdrNetwork := pkt.NetworkHeader()
hdrTransport := pkt.TransportHeader()
full := make([]byte, 0, pkt.Size())
full = append(full, hdrNetwork.View()...)
full = append(full, hdrTransport.View()...)
full = append(full, pkt.Data.ToView()...)
logf("XXX packet Write out: % x", full)
if err := tundev.InjectOutbound(full); err != nil {
log.Printf("netstack inject outbound: %v", err)
return
}
}
}()
tundev.PostFilterIn = func(p *packet.Parsed, t *tstun.TUN) filter.Response {
var pn tcpip.NetworkProtocolNumber
switch p.IPVersion {
case 4:
pn = header.IPv4ProtocolNumber
case 6:
pn = header.IPv6ProtocolNumber
}
logf("XXX packet in (from %v): % x", p.Src, p.Buffer())
vv := buffer.View(append([]byte(nil), p.Buffer()...)).ToVectorisedView()
packetBuf := stack.NewPacketBuffer(stack.PacketBufferOptions{
Data: vv,
})
linkEP.InjectInbound(pn, packetBuf)
return filter.Accept
}
return nil
}
func echo(c *gonet.TCPConn, e wgengine.Engine, mc *magicsock.Conn) {
defer c.Close()
src, _ := netaddr.FromStdIP(c.RemoteAddr().(*net.TCPAddr).IP)
who := ""
if n, u, ok := mc.WhoIs(src); ok {
who = fmt.Sprintf("%v from %v", u.DisplayName, n.Name)
}
fmt.Fprintf(c, "Hello, %s! Thanks for connecting to me on port %v (Try other ports too!)\nEchoing...\n",
who,
c.LocalAddr().(*net.TCPAddr).Port)
buf := make([]byte, 1500)
for {
n, err := c.Read(buf)
if err != nil {
log.Printf("Err: %v", err)
break
}
c.Write(buf[:n])
}
log.Print("Connection closed")
}
+25 -7
View File
@@ -138,9 +138,19 @@ type EngineConfig struct {
// Fake determines whether this engine is running in fake mode,
// which disables such features as DNS configuration and unrestricted ICMP Echo responses.
Fake bool
// FakeImpl, if non-nil, specifies which type of fake implementation to
// use. Two values are typical: nil, for a basic ping-only fake
// implementation, and netstack.Impl, which brings in gvisor's netstack
// to the binary. The desire to keep that out of some binaries is why
// this func exists, so wgengine need not depend on gvisor.
FakeImpl FakeImplFunc
}
func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error) {
// FakeImplFunc is the type used by EngineConfig.FakeImpl. See docs there.
type FakeImplFunc func(logger.Logf, *tstun.TUN, Engine, *magicsock.Conn) error
func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16, impl FakeImplFunc) (Engine, error) {
logf("Starting userspace wireguard engine (with fake TUN device)")
conf := EngineConfig{
Logf: logf,
@@ -148,6 +158,7 @@ func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error)
RouterGen: router.NewFake,
ListenPort: listenPort,
Fake: true,
FakeImpl: impl,
}
return NewUserspaceEngineAdvanced(conf)
}
@@ -209,12 +220,6 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) {
e.linkState, _ = getLinkState()
logf("link state: %+v", e.linkState)
// Respond to all pings only in fake mode.
if conf.Fake {
e.tundev.PostFilterIn = echoRespondToAll
}
e.tundev.PreFilterOut = e.handleLocalPackets
mon, err := monitor.New(logf, func() {
e.LinkChange(false)
tshttpproxy.InvalidateCache()
@@ -247,6 +252,19 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) {
}
e.magicConn.SetNetworkUp(e.linkState.AnyInterfaceUp())
// Respond to all pings only in fake mode.
if conf.Fake {
if impl := conf.FakeImpl; impl != nil {
if err := impl(logf, e.tundev, e, e.magicConn); err != nil {
return nil, err
}
} else {
// Respond to all pings only in fake mode.
e.tundev.PostFilterIn = echoRespondToAll
}
}
e.tundev.PreFilterOut = e.handleLocalPackets
// wireguard-go logs as it starts and stops routines.
// Silence those; there are a lot of them, and they're just noise.
allowLogf := func(s string) bool {
+1 -1
View File
@@ -84,7 +84,7 @@ func TestNoteReceiveActivity(t *testing.T) {
}
func TestUserspaceEngineReconfig(t *testing.T) {
e, err := NewFakeUserspaceEngine(t.Logf, 0)
e, err := NewFakeUserspaceEngine(t.Logf, 0, nil)
if err != nil {
t.Fatal(err)
}
+2 -2
View File
@@ -17,7 +17,7 @@ func TestWatchdog(t *testing.T) {
t.Run("default watchdog does not fire", func(t *testing.T) {
t.Parallel()
e, err := NewFakeUserspaceEngine(t.Logf, 0)
e, err := NewFakeUserspaceEngine(t.Logf, 0, nil)
if err != nil {
t.Fatal(err)
}
@@ -35,7 +35,7 @@ func TestWatchdog(t *testing.T) {
t.Run("watchdog fires on blocked getStatus", func(t *testing.T) {
t.Parallel()
e, err := NewFakeUserspaceEngine(t.Logf, 0)
e, err := NewFakeUserspaceEngine(t.Logf, 0, nil)
if err != nil {
t.Fatal(err)
}