tailscale/net/socks5/tssocks/tssocks.go

80 lines
1.9 KiB
Go
Raw Permalink Normal View History

// Copyright (c) 2021 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 tssocks is the glue between Tailscale and the net/socks5 package.
package tssocks
import (
"context"
"net"
"sync"
"inet.af/netaddr"
"tailscale.com/net/socks5"
"tailscale.com/net/tsaddr"
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
"tailscale.com/wgengine"
"tailscale.com/wgengine/netstack"
)
// NewServer returns a new SOCKS5 server configured to dial out to
// Tailscale addresses.
//
// The returned server is not yet listening. The caller must call
// Serve with a listener.
//
// If ns is non-nil, it is used for dialing when needed.
func NewServer(logf logger.Logf, e wgengine.Engine, ns *netstack.Impl) *socks5.Server {
d := &dialer{ns: ns}
e.AddNetworkMapCallback(d.onNewNetmap)
return &socks5.Server{
Logf: logf,
Dialer: d.DialContext,
}
}
// dialer is the Tailscale SOCKS5 dialer.
type dialer struct {
ns *netstack.Impl
mu sync.Mutex
dns netstack.DNSMap
}
func (d *dialer) onNewNetmap(nm *netmap.NetworkMap) {
d.mu.Lock()
defer d.mu.Unlock()
d.dns = netstack.DNSMapFromNetworkMap(nm)
}
func (d *dialer) resolve(ctx context.Context, addr string) (netaddr.IPPort, error) {
d.mu.Lock()
dns := d.dns
d.mu.Unlock()
return dns.Resolve(ctx, addr)
}
func (d *dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
ipp, err := d.resolve(ctx, addr)
if err != nil {
return nil, err
}
if d.ns != nil && d.useNetstackForIP(ipp.IP()) {
return d.ns.DialContextTCP(ctx, ipp.String())
}
var stdDialer net.Dialer
return stdDialer.DialContext(ctx, network, ipp.String())
}
func (d *dialer) useNetstackForIP(ip netaddr.IP) bool {
if d.ns == nil {
return false
}
// TODO(bradfitz): this isn't exactly right.
// We should also support subnets when the
// prefs are configured as such.
return tsaddr.IsTailscaleIP(ip)
}