diff options
Diffstat (limited to 'wgengine/kernel.go')
| -rw-r--r-- | wgengine/kernel.go | 255 |
1 files changed, 255 insertions, 0 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 } |
