Files
coder/peer/peerwg/wireguard.go
T

442 lines
11 KiB
Go

package peerwg
import (
"context"
"fmt"
"hash/fnv"
"io"
"log"
"net"
"strconv"
"sync"
"time"
"github.com/google/uuid"
"github.com/tabbed/pqtype"
"golang.org/x/xerrors"
"inet.af/netaddr"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/dns"
"tailscale.com/net/netns"
"tailscale.com/net/tsdial"
"tailscale.com/tailcfg"
"tailscale.com/types/ipproto"
"tailscale.com/types/key"
tslogger "tailscale.com/types/logger"
"tailscale.com/types/netmap"
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/magicsock"
"tailscale.com/wgengine/monitor"
"tailscale.com/wgengine/netstack"
"tailscale.com/wgengine/router"
"tailscale.com/wgengine/wgcfg/nmcfg"
"cdr.dev/slog"
)
var Logf tslogger.Logf = log.Printf
func init() {
// Globally disable network namespacing.
// All networking happens in userspace.
netns.SetEnabled(false)
}
func UUIDToInet(uid uuid.UUID) pqtype.Inet {
uid = privateUUID(uid)
return pqtype.Inet{
Valid: true,
IPNet: net.IPNet{
IP: uid[:],
Mask: net.CIDRMask(128, 128),
},
}
}
func UUIDToNetaddr(uid uuid.UUID) netaddr.IP {
return netaddr.IPFrom16(privateUUID(uid))
}
// privateUUID sets the uid to have the tailscale private ipv6 prefix.
func privateUUID(uid uuid.UUID) uuid.UUID {
// fd7a:115c:a1e0
uid[0] = 0xfd
uid[1] = 0x7a
uid[2] = 0x11
uid[3] = 0x5c
uid[4] = 0xa1
uid[5] = 0xe0
return uid
}
type Network struct {
mu sync.Mutex
logger slog.Logger
Netstack *netstack.Impl
magicSock *magicsock.Conn
netMap *netmap.NetworkMap
router *router.Config
wgEngine wgengine.Engine
// listeners is a map of listening sockets that will be forwarded traffic
// from the wireguard interface.
listeners map[listenKey]*listener
DiscoPublicKey key.DiscoPublic
NodePrivateKey key.NodePrivate
}
// New constructs a Wireguard network that filters traffic
// to destinations matching the addresses provided.
func New(logger slog.Logger, addresses []netaddr.IPPrefix) (*Network, error) {
nodePrivateKey := key.NewNode()
nodePublicKey := nodePrivateKey.Public()
id, stableID := nodeIDs(nodePublicKey)
netMap := &netmap.NetworkMap{
NodeKey: nodePublicKey,
PrivateKey: nodePrivateKey,
Addresses: addresses,
PacketFilter: []filter.Match{{
// Allow any protocol!
IPProto: []ipproto.Proto{ipproto.TCP, ipproto.UDP, ipproto.ICMPv4, ipproto.ICMPv6, ipproto.SCTP},
// Allow traffic sourced from anywhere.
Srcs: []netaddr.IPPrefix{
netaddr.IPPrefixFrom(netaddr.IPv4(0, 0, 0, 0), 0),
netaddr.IPPrefixFrom(netaddr.IPv6Unspecified(), 0),
},
// Allow traffic to route anywhere.
Dsts: []filter.NetPortRange{
{
Net: netaddr.IPPrefixFrom(netaddr.IPv4(0, 0, 0, 0), 0),
Ports: filter.PortRange{
First: 0,
Last: 65535,
},
},
{
Net: netaddr.IPPrefixFrom(netaddr.IPv6Unspecified(), 0),
Ports: filter.PortRange{
First: 0,
Last: 65535,
},
},
},
Caps: []filter.CapMatch{},
}},
}
// Identify itself as a node on the network with the addresses provided.
netMap.SelfNode = &tailcfg.Node{
ID: id,
StableID: stableID,
Key: nodePublicKey,
Addresses: netMap.Addresses,
AllowedIPs: append(netMap.Addresses, netaddr.MustParseIPPrefix("::/0")),
Endpoints: []string{},
DERP: DefaultDerpHome,
}
wgMonitor, err := monitor.New(Logf)
if err != nil {
return nil, xerrors.Errorf("create link monitor: %w", err)
}
dialer := new(tsdial.Dialer)
dialer.Logf = Logf
// Create a wireguard engine in userspace.
engine, err := wgengine.NewUserspaceEngine(Logf, wgengine.Config{
LinkMonitor: wgMonitor,
Dialer: dialer,
})
if err != nil {
return nil, xerrors.Errorf("create wgengine: %w", err)
}
// This is taken from Tailscale:
// https://github.com/tailscale/tailscale/blob/0f05b2c13ff0c305aa7a1655fa9c17ed969d65be/tsnet/tsnet.go#L247-L255
// nolint
tunDev, magicConn, dnsManager, ok := engine.(wgengine.InternalsGetter).GetInternals()
if !ok {
return nil, xerrors.New("could not get wgengine internals")
}
// Update the keys for the magic connection!
err = magicConn.SetPrivateKey(nodePrivateKey)
if err != nil {
return nil, xerrors.Errorf("set node private key: %w", err)
}
netMap.SelfNode.DiscoKey = magicConn.DiscoPublicKey()
// Create the networking stack.
// This is called to route connections.
netStack, err := netstack.Create(Logf, tunDev, engine, magicConn, dialer, dnsManager)
if err != nil {
return nil, xerrors.Errorf("create netstack: %w", err)
}
netStack.ProcessLocalIPs = true
netStack.ProcessSubnets = true
dialer.UseNetstackForIP = func(ip netaddr.IP) bool {
_, ok := engine.PeerForIP(ip)
return ok
}
dialer.NetstackDialTCP = func(ctx context.Context, dst netaddr.IPPort) (net.Conn, error) {
return netStack.DialContextTCP(ctx, dst)
}
err = netStack.Start()
if err != nil {
return nil, xerrors.Errorf("start netstack: %w", err)
}
engine = wgengine.NewWatchdog(engine)
// Update the wireguard configuration to allow traffic to flow.
cfg, err := nmcfg.WGCfg(netMap, Logf, netmap.AllowSingleHosts|netmap.AllowSubnetRoutes, netMap.SelfNode.StableID)
if err != nil {
return nil, xerrors.Errorf("create wgcfg: %w", err)
}
rtr := &router.Config{
LocalAddrs: cfg.Addresses,
}
err = engine.Reconfig(cfg, rtr, &dns.Config{}, &tailcfg.Debug{})
if err != nil {
return nil, xerrors.Errorf("reconfig: %w", err)
}
engine.SetDERPMap(DerpMap)
engine.SetNetworkMap(copyNetMap(netMap))
ipb := netaddr.IPSetBuilder{}
for _, addr := range netMap.Addresses {
ipb.AddPrefix(addr)
}
ips, _ := ipb.IPSet()
iplb := netaddr.IPSetBuilder{}
ipl, _ := iplb.IPSet()
engine.SetFilter(filter.New(netMap.PacketFilter, ips, ipl, nil, Logf))
wn := &Network{
logger: logger,
NodePrivateKey: nodePrivateKey,
DiscoPublicKey: magicConn.DiscoPublicKey(),
wgEngine: engine,
Netstack: netStack,
magicSock: magicConn,
netMap: netMap,
router: rtr,
listeners: map[listenKey]*listener{},
}
netStack.ForwardTCPIn = wn.forwardTCP
return wn, nil
}
// forwardTCP handles incoming connections from Wireguard in userspace.
func (n *Network) forwardTCP(conn net.Conn, port uint16) {
n.mu.Lock()
listener, ok := n.listeners[listenKey{"tcp", "", fmt.Sprint(port)}]
n.mu.Unlock()
if !ok {
// No in-memory listener exists, forward to host.
n.forwardTCPToLocalHandler(conn, port)
return
}
timer := time.NewTimer(time.Second)
defer timer.Stop()
select {
case listener.conn <- conn:
case <-timer.C:
_ = conn.Close()
}
}
// forwardTCPToLocalHandler forwards the provided net.Conn to the
// matching port bound to localhost.
func (n *Network) forwardTCPToLocalHandler(c net.Conn, port uint16) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
defer c.Close()
dialAddrStr := net.JoinHostPort("127.0.0.1", strconv.Itoa(int(port)))
var stdDialer net.Dialer
server, err := stdDialer.DialContext(ctx, "tcp", dialAddrStr)
if err != nil {
n.logger.Debug(ctx, "dial local port", slog.F("port", port), slog.Error(err))
return
}
defer server.Close()
connClosed := make(chan error, 2)
go func() {
_, err := io.Copy(server, c)
connClosed <- err
}()
go func() {
_, err := io.Copy(c, server)
connClosed <- err
}()
err = <-connClosed
if err != nil {
n.logger.Debug(ctx, "proxy connection closed with error", slog.Error(err))
}
n.logger.Debug(ctx, "forwarded connection closed", slog.F("local_addr", dialAddrStr))
}
// AddPeer allows connections from another Wireguard instance with the
// handshake credentials.
func (n *Network) AddPeer(handshake Handshake) error {
n.mu.Lock()
defer n.mu.Unlock()
// If the peer already exists in the network map, do nothing.
for _, p := range n.netMap.Peers {
if p.Key == handshake.NodePublicKey {
n.logger.Debug(context.Background(), "peer already in netmap", slog.F("peer", handshake.NodePublicKey.ShortString()))
return nil
}
}
// The Tailscale engine owns this slice, so we need to copy to make
// modifications.
peers := append(([]*tailcfg.Node)(nil), n.netMap.Peers...)
id, stableID := nodeIDs(handshake.NodePublicKey)
peers = append(peers, &tailcfg.Node{
ID: id,
StableID: stableID,
Name: handshake.NodePublicKey.String() + ".com",
Key: handshake.NodePublicKey,
DiscoKey: handshake.DiscoPublicKey,
Addresses: []netaddr.IPPrefix{netaddr.IPPrefixFrom(handshake.IPv6, 128)},
AllowedIPs: []netaddr.IPPrefix{netaddr.IPPrefixFrom(handshake.IPv6, 128)},
DERP: DefaultDerpHome,
Endpoints: []string{DefaultDerpHome},
})
n.netMap.Peers = peers
cfg, err := nmcfg.WGCfg(n.netMap, Logf, netmap.AllowSingleHosts|netmap.AllowSubnetRoutes, tailcfg.StableNodeID("nBBoJZ5CNTRL"))
if err != nil {
return xerrors.Errorf("create wgcfg: %w", err)
}
err = n.wgEngine.Reconfig(cfg, n.router, &dns.Config{}, &tailcfg.Debug{})
if err != nil {
return xerrors.Errorf("reconfig: %w", err)
}
// Always give the Tailscale engine a copy of our network map.
n.wgEngine.SetNetworkMap(copyNetMap(n.netMap))
return nil
}
// Ping sends a discovery ping to the provided peer.
// The peer address must be connected before a successful ping will work.
func (n *Network) Ping(ip netaddr.IP) *ipnstate.PingResult {
ch := make(chan *ipnstate.PingResult)
n.wgEngine.Ping(ip, tailcfg.PingDisco, func(pr *ipnstate.PingResult) {
ch <- pr
})
return <-ch
}
// Listener returns a net.Listener in userspace that can be used to accept
// connections from the Wireguard network to the specified address. If a
// listener exists for a given address, all connections will be forwarded to the
// listener instead of being routed to the host.
func (n *Network) Listen(network, addr string) (net.Listener, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, xerrors.Errorf("split addr host port: %w", err)
}
lkey := listenKey{network, host, port}
ln := &listener{
wn: n,
key: lkey,
addr: addr,
conn: make(chan net.Conn, 1),
}
n.mu.Lock()
defer n.mu.Unlock()
if _, ok := n.listeners[lkey]; ok {
return nil, xerrors.Errorf("listener already open for %s, %s", network, addr)
}
n.listeners[lkey] = ln
return ln, nil
}
func (n *Network) Close() error {
// Close all listeners.
for _, l := range n.listeners {
_ = l.Close()
}
// Close the Wireguard netstack and engine.
_ = n.Netstack.Close()
n.wgEngine.Close()
return nil
}
type listenKey struct {
network string
host string
port string
}
type listener struct {
wn *Network
key listenKey
addr string
conn chan net.Conn
}
func (ln *listener) Accept() (net.Conn, error) {
c, ok := <-ln.conn
if !ok {
return nil, xerrors.Errorf("tsnet: %w", net.ErrClosed)
}
return c, nil
}
func (ln *listener) Addr() net.Addr { return addr{ln} }
func (ln *listener) Close() error {
ln.wn.mu.Lock()
defer ln.wn.mu.Unlock()
if v, ok := ln.wn.listeners[ln.key]; ok && v == ln {
delete(ln.wn.listeners, ln.key)
close(ln.conn)
}
return nil
}
type addr struct{ ln *listener }
func (a addr) Network() string { return a.ln.key.network }
func (a addr) String() string { return a.ln.addr }
// nodeIDs generates Tailscale node IDs for the provided public key.
func nodeIDs(public key.NodePublic) (tailcfg.NodeID, tailcfg.StableNodeID) {
idhash := fnv.New64()
pub, _ := public.MarshalText()
_, _ = idhash.Write(pub)
return tailcfg.NodeID(idhash.Sum64()), tailcfg.StableNodeID(pub)
}
func copyNetMap(nm *netmap.NetworkMap) *netmap.NetworkMap {
nmCopy := *nm
return &nmCopy
}