summaryrefslogtreecommitdiffhomepage
path: root/wgengine
diff options
context:
space:
mode:
authorDavid Anderson <danderson@tailscale.com>2021-08-30 14:30:06 -0700
committerDavid Anderson <danderson@tailscale.com>2021-08-30 14:30:06 -0700
commit9c0a1375eb59721a7c9f46c5560531d8da45b750 (patch)
treed23efab8641eebffd77c2187680b926dd624320a /wgengine
parenta35c3ba2214bac4c05bac4c60d5e067607fdab11 (diff)
downloadtailscale-danderson/kernel-tailscale.tar.xz
tailscale-danderson/kernel-tailscale.zip
WIP: kernel accelerated tailscaleddanderson/kernel-tailscale
Diffstat (limited to 'wgengine')
-rw-r--r--wgengine/kernel.go255
-rw-r--r--wgengine/kproxy/proxy.go136
-rw-r--r--wgengine/router/router_linux.go9
3 files changed, 396 insertions, 4 deletions
diff --git a/wgengine/kernel.go b/wgengine/kernel.go
new file mode 100644
index 000000000..1e7ca6a7b
--- /dev/null
+++ b/wgengine/kernel.go
@@ -0,0 +1,255 @@
+// 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 wgengine
+
+import (
+ "errors"
+ "fmt"
+ "sync"
+
+ "golang.zx2c4.com/wireguard/wgctrl"
+ "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
+ "inet.af/netaddr"
+ "tailscale.com/health"
+ "tailscale.com/ipn/ipnstate"
+ "tailscale.com/net/dns"
+ "tailscale.com/tailcfg"
+ "tailscale.com/types/key"
+ "tailscale.com/types/logger"
+ "tailscale.com/types/netmap"
+ "tailscale.com/types/wgkey"
+ "tailscale.com/wgengine/filter"
+ "tailscale.com/wgengine/kproxy"
+ "tailscale.com/wgengine/magicsock"
+ "tailscale.com/wgengine/monitor"
+ "tailscale.com/wgengine/router"
+ "tailscale.com/wgengine/wgcfg"
+)
+
+type kernelEngine struct {
+ logf logger.Logf
+ magicConn *magicsock.Conn
+ linkMon *monitor.Mon
+ linkMonOwned bool // whether we created linkMon (and thus need to close it)
+ router router.Router
+ dns *dns.Manager
+ confListenPort uint16 // original conf.ListenPort
+ wg *wgctrl.Client
+ proxy *kproxy.Proxy
+ proxyMap map[tailcfg.NodeKey]netaddr.IPPort
+
+ wgLock sync.Mutex
+}
+
+func NewKernelEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) {
+ var closePool closeOnErrorPool
+ defer closePool.closeAllIfError(&reterr)
+
+ const tunName = "tailscale0" // TODO: plumb somehow for variable name
+
+ if conf.Tun != nil {
+ return nil, errors.New("can't use a tun interface in kernel mode")
+ }
+ if conf.Router == nil {
+ conf.Router = router.NewFake(logf)
+ }
+ if conf.DNS == nil {
+ d, err := dns.NewNoopManager()
+ if err != nil {
+ return nil, err
+ }
+ conf.DNS = d
+ }
+
+ e := &kernelEngine{
+ logf: logf,
+ router: conf.Router,
+ confListenPort: conf.ListenPort,
+ }
+ if conf.LinkMonitor != nil {
+ e.linkMon = conf.LinkMonitor
+ } else {
+ mon, err := monitor.New(logf)
+ if err != nil {
+ return nil, err
+ }
+ closePool.add(mon)
+ e.linkMon = mon
+ e.linkMonOwned = true
+ }
+ e.dns = dns.NewManager(logf, conf.DNS, e.linkMon, nil) // TODO: make a fwdLinkSelector
+
+ magicsockOpts := magicsock.Options{
+ Logf: logf,
+ Port: conf.ListenPort,
+ LinkMonitor: e.linkMon,
+ }
+
+ var err error
+ e.magicConn, err = magicsock.NewConn(magicsockOpts)
+ if err != nil {
+ return nil, fmt.Errorf("wgengine: %v", err)
+ }
+ closePool.add(e.magicConn)
+ e.magicConn.SetNetworkUp(true)
+
+ e.proxy, err = kproxy.New(e.magicConn)
+ if err != nil {
+ return nil, fmt.Errorf("proxy: %v", err)
+ }
+
+ e.wg, err = wgctrl.New()
+ if err != nil {
+ return nil, fmt.Errorf("wgctrl: %v", err)
+ }
+ closePool.add(e.wg)
+
+ err = e.wg.ConfigureDevice(tunName, wgtypes.Config{
+ PrivateKey: &wgtypes.Key{},
+ ReplacePeers: true,
+ Peers: []wgtypes.PeerConfig{},
+ })
+ if err != nil {
+ return nil, fmt.Errorf("wgctrl: initial config: %v", err)
+ }
+
+ if err := e.router.Up(); err != nil {
+ return nil, err
+ }
+ e.magicConn.Start()
+
+ return e, nil
+}
+
+func (e *kernelEngine) Reconfig(wcfg *wgcfg.Config, rcfg *router.Config, dcfg *dns.Config, dbg *tailcfg.Debug) error {
+ if rcfg == nil {
+ panic("rcfg must not be nil")
+ }
+ if dcfg == nil {
+ panic("dcfg must not be nil")
+ }
+
+ e.wgLock.Lock()
+ defer e.wgLock.Unlock()
+
+ peerSet := make(map[key.Public]struct{}, len(wcfg.Peers))
+ for _, p := range wcfg.Peers {
+ peerSet[key.Public(p.PublicKey)] = struct{}{}
+ }
+
+ if err := e.magicConn.SetPrivateKey(wgkey.Private(wcfg.PrivateKey)); err != nil {
+ e.logf("wgengine: Reconfig: SetPrivateKey: %v", err)
+ }
+ e.magicConn.UpdatePeers(peerSet)
+ e.magicConn.SetPreferredPort(e.confListenPort)
+
+ port := 4242
+ cfg := wgtypes.Config{
+ PrivateKey: (*wgtypes.Key)(&wcfg.PrivateKey),
+ ListenPort: &port,
+ ReplacePeers: true,
+ }
+ for _, p := range wcfg.Peers {
+ v := wgtypes.PeerConfig{
+ PublicKey: wgtypes.Key(p.PublicKey),
+ Endpoint: e.proxyMap[tailcfg.NodeKey(p.PublicKey)].UDPAddr(),
+ ReplaceAllowedIPs: true,
+ }
+ for _, pfx := range p.AllowedIPs {
+ v.AllowedIPs = append(v.AllowedIPs, *pfx.IPNet())
+ }
+ cfg.Peers = append(cfg.Peers, v)
+ }
+ if err := e.wg.ConfigureDevice("tailscale0", cfg); err != nil {
+ return fmt.Errorf("configuring kernel: %v", err)
+ }
+
+ err := e.router.Set(rcfg)
+ health.SetRouterHealth(err)
+ if err != nil {
+ return err
+ }
+
+ // TODO: set DNS, but it'll just break my machine right now, I
+ // mean look at the state of me.
+
+ return nil
+}
+
+func (e *kernelEngine) GetFilter() *filter.Filter { return nil }
+
+func (e *kernelEngine) SetFilter(f *filter.Filter) {}
+
+func (e *kernelEngine) SetStatusCallback(cb StatusCallback) {}
+
+func (e *kernelEngine) GetLinkMonitor() *monitor.Mon { return e.linkMon }
+
+func (e *kernelEngine) RequestStatus() {}
+
+func (e *kernelEngine) Close() {}
+
+func (e *kernelEngine) Wait() {}
+
+func (e *kernelEngine) LinkChange(isExpensive bool) {}
+
+func (e *kernelEngine) SetDERPMap(m *tailcfg.DERPMap) {
+ e.magicConn.SetDERPMap(m)
+}
+
+func (e *kernelEngine) SetNetworkMap(nm *netmap.NetworkMap) {
+ e.magicConn.SetNetworkMap(nm)
+ m, err := e.proxy.SetNetworkMap(nm)
+ if err != nil {
+ e.logf("MUCH SADNESS: %v", err)
+ }
+ e.wgLock.Lock()
+ defer e.wgLock.Unlock()
+ e.proxyMap = m
+}
+
+func (e *kernelEngine) AddNetworkMapCallback(cb NetworkMapCallback) (rm func()) { return func() {} }
+
+func (e *kernelEngine) SetNetInfoCallback(cb NetInfoCallback) {}
+
+func (e *kernelEngine) DiscoPublicKey() tailcfg.DiscoKey { return e.magicConn.DiscoPublicKey() }
+
+func (e *kernelEngine) getStatus() (*Status, error) {
+ // Grab derpConns before acquiring wgLock to not violate lock ordering;
+ // the DERPs method acquires magicsock.Conn.mu.
+ // (See comment in userspaceEngine's declaration.)
+ derpConns := e.magicConn.DERPs()
+
+ return &Status{
+ LocalAddrs: []tailcfg.Endpoint{},
+ Peers: []ipnstate.PeerStatusLite{},
+ DERPs: derpConns,
+ }, nil
+}
+
+func (e *kernelEngine) UpdateStatus(sb *ipnstate.StatusBuilder) {
+ st, err := e.getStatus()
+ if err != nil {
+ e.logf("wgengine: getStatus: %v", err)
+ return
+ }
+ for _, ps := range st.Peers {
+ sb.AddPeer(key.Public(ps.NodeKey), &ipnstate.PeerStatus{
+ RxBytes: int64(ps.RxBytes),
+ TxBytes: int64(ps.TxBytes),
+ LastHandshake: ps.LastHandshake,
+ InEngine: true,
+ })
+ }
+
+ e.magicConn.UpdateStatus(sb)
+}
+
+func (e *kernelEngine) Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.PingResult)) {}
+
+func (e *kernelEngine) RegisterIPPortIdentity(ipp netaddr.IPPort, ip netaddr.IP) {}
+
+func (e *kernelEngine) UnregisterIPPortIdentity(ipp netaddr.IPPort) {}
+
+func (e *kernelEngine) WhoIsIPPort(netaddr.IPPort) (netaddr.IP, bool) { return netaddr.IP{}, false }
diff --git a/wgengine/kproxy/proxy.go b/wgengine/kproxy/proxy.go
new file mode 100644
index 000000000..0174b2bd3
--- /dev/null
+++ b/wgengine/kproxy/proxy.go
@@ -0,0 +1,136 @@
+// 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 kproxy
+
+import (
+ "encoding/json"
+ "net"
+ "sync"
+
+ "golang.zx2c4.com/wireguard/conn"
+ "inet.af/netaddr"
+ "tailscale.com/tailcfg"
+ "tailscale.com/types/netmap"
+ "tailscale.com/types/wgkey"
+ "tailscale.com/wgengine/magicsock"
+ "tailscale.com/wgengine/wgcfg"
+)
+
+type Proxy struct {
+ mu sync.RWMutex
+ conn *magicsock.Conn
+ byKey map[tailcfg.NodeKey]*pipe
+ byEndpoint map[conn.Endpoint]*pipe
+}
+
+func New(c *magicsock.Conn) (*Proxy, error) {
+ ret := &Proxy{
+ conn: c,
+ byEndpoint: map[conn.Endpoint]*pipe{},
+ byKey: map[tailcfg.NodeKey]*pipe{},
+ }
+ fns, _, err := c.Bind().Open(0)
+ if err != nil {
+ return nil, err
+ }
+ for _, fn := range fns {
+ go func(fn conn.ReceiveFunc) {
+ for {
+ var bs [1500]byte
+ n, ep, err := fn(bs[:])
+ if err != nil {
+ // Sadness.
+ continue
+ }
+ ret.mu.RLock()
+ pip, ok := ret.byEndpoint[ep]
+ ret.mu.RUnlock()
+ if ok {
+ if _, err := pip.proxy.Write(bs[:n]); err != nil {
+ _ = err // TODO
+ }
+ }
+ }
+ }(fn)
+ }
+
+ return ret, nil
+}
+
+var proxyListenIP = netaddr.MustParseIPPort("127.0.0.1:0")
+var wgIP = netaddr.MustParseIPPort("127.0.0.1:4242")
+
+func (p *Proxy) SetNetworkMap(nm *netmap.NetworkMap) (map[tailcfg.NodeKey]netaddr.IPPort, error) {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+ ret := make(map[tailcfg.NodeKey]netaddr.IPPort, len(nm.Peers))
+ for _, peer := range nm.Peers {
+ if pip, ok := p.byKey[peer.Key]; ok {
+ ret[peer.Key] = pip.proxyAddr
+ } else {
+ wgEp := wgcfg.Endpoints{
+ PublicKey: wgkey.Key(peer.Key),
+ DiscoKey: peer.DiscoKey,
+ }
+ bs, err := json.Marshal(wgEp)
+ if err != nil {
+ return nil, err
+ }
+ ep, err := p.conn.ParseEndpoint(string(bs))
+ if err != nil {
+ return nil, err
+ }
+ conn, err := net.DialUDP("udp4", proxyListenIP.UDPAddr(), wgIP.UDPAddr())
+ if err != nil {
+ return nil, err
+ }
+ connAddr := netaddr.MustParseIPPort(conn.LocalAddr().String())
+ pip = &pipe{
+ ep: ep,
+ proxy: conn,
+ proxyAddr: connAddr,
+ }
+ go func() {
+ for {
+ var bs [1500]byte
+ n, ua, err := conn.ReadFromUDP(bs[:])
+ if err != nil {
+ return // TODO: more noise
+ }
+ ip, ok := netaddr.FromStdIP(ua.IP)
+ if !ok {
+ // ???
+ continue
+ }
+ if netaddr.IPPortFrom(ip, uint16(ua.Port)) != wgIP {
+ // Random noise that isn't kernel wg
+ continue
+ }
+ if err := p.conn.Send(bs[:n], ep); err != nil {
+ // Probably complain a bit
+ continue
+ }
+ }
+ }()
+ p.byKey[peer.Key] = pip
+ p.byEndpoint[ep] = pip
+ ret[peer.Key] = pip.proxyAddr
+ }
+ }
+ for key, pip := range p.byKey {
+ if _, ok := ret[key]; !ok {
+ pip.proxy.Close()
+ delete(p.byKey, key)
+ delete(p.byEndpoint, pip.ep)
+ }
+ }
+ return ret, nil
+}
+
+type pipe struct {
+ ep conn.Endpoint
+ proxy net.Conn
+ proxyAddr netaddr.IPPort
+}
diff --git a/wgengine/router/router_linux.go b/wgengine/router/router_linux.go
index 279d8c93b..eecc482b1 100644
--- a/wgengine/router/router_linux.go
+++ b/wgengine/router/router_linux.go
@@ -125,10 +125,11 @@ type linuxRouter struct {
}
func newUserspaceRouter(logf logger.Logf, tunDev tun.Device, linkMon *monitor.Mon) (Router, error) {
- tunname, err := tunDev.Name()
- if err != nil {
- return nil, err
- }
+ tunname := "tailscale0"
+ // , err := tunDev.Name()
+ // if err != nil {
+ // return nil, err
+ // }
ipt4, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
if err != nil {