diff options
| author | David Anderson <danderson@tailscale.com> | 2021-08-30 14:30:06 -0700 |
|---|---|---|
| committer | David Anderson <danderson@tailscale.com> | 2021-08-30 14:30:06 -0700 |
| commit | 9c0a1375eb59721a7c9f46c5560531d8da45b750 (patch) | |
| tree | d23efab8641eebffd77c2187680b926dd624320a /wgengine | |
| parent | a35c3ba2214bac4c05bac4c60d5e067607fdab11 (diff) | |
| download | tailscale-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.go | 255 | ||||
| -rw-r--r-- | wgengine/kproxy/proxy.go | 136 | ||||
| -rw-r--r-- | wgengine/router/router_linux.go | 9 |
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 { |
