135 lines
3.5 KiB
Go
135 lines
3.5 KiB
Go
package core
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"net"
|
|
|
|
"tailscale.com/tsnet"
|
|
)
|
|
|
|
func runTCPForwarder(ctx context.Context, srv *tsnet.Server, rule ForwardRule, logger *slog.Logger) {
|
|
ip := getSelfTsnetAddr(srv)
|
|
ln, err := srv.Listen("tcp", fmt.Sprintf("%s:%d", ip.String(), rule.TailscalePort))
|
|
if err != nil {
|
|
logger.Error("failed to listen", "error", err)
|
|
return
|
|
}
|
|
logger.Debug("listening", slog.String("on", fmt.Sprintf("tailscale:%s:%d", ip.String(), rule.TailscalePort)))
|
|
|
|
go func() {
|
|
<-ctx.Done()
|
|
ln.Close()
|
|
}()
|
|
|
|
for {
|
|
conn, err := ln.Accept()
|
|
if err != nil {
|
|
if ctx.Err() != nil {
|
|
return
|
|
}
|
|
logger.Error("accept error", "error", err)
|
|
continue
|
|
}
|
|
go handleTCPForward(ctx, srv, conn, rule, logger)
|
|
}
|
|
}
|
|
|
|
func handleTCPForward(ctx context.Context, srv *tsnet.Server, conn net.Conn, rule ForwardRule, logger *slog.Logger) {
|
|
remoteAddrStr := conn.RemoteAddr().String()
|
|
clog := logger.With(slog.String("remote", remoteAddrStr))
|
|
|
|
lc, err := srv.LocalClient()
|
|
if err == nil {
|
|
who, err := lc.WhoIs(ctx, remoteAddrStr)
|
|
if err == nil {
|
|
clog = clog.With(slog.String("user", who.UserProfile.LoginName))
|
|
}
|
|
}
|
|
|
|
connType := getConnType(ctx, srv, remoteAddrStr)
|
|
clog.Info("accepted connection",
|
|
slog.String("conn_type", connType),
|
|
slog.String("local_addr", rule.LocalAddr),
|
|
)
|
|
|
|
localConn, err := dialTCP(ctx, rule.LocalAddr)
|
|
if err != nil {
|
|
clog.Error("failed to dial local", "error", err)
|
|
conn.Close()
|
|
return
|
|
}
|
|
|
|
stop := context.AfterFunc(ctx, func() {
|
|
conn.Close()
|
|
localConn.Close()
|
|
})
|
|
defer stop()
|
|
|
|
toLocal, toTs := pipeConns(conn, localConn)
|
|
clog.Info("connection closed", slog.Int64("ts_rx_bytes", toLocal), slog.Int64("ts_tx_bytes", toTs))
|
|
}
|
|
|
|
func runTCPConnector(ctx context.Context, srv *tsnet.Server, rule ConnectRule, logger *slog.Logger) {
|
|
bindIP := rule.BindIP()
|
|
if rule.LANEnabled() && rule.LocalAddr != "" && rule.LocalAddr != "0.0.0.0" {
|
|
logger.Warn("lan_enable forces local_addr to 0.0.0.0, overriding")
|
|
}
|
|
addr := fmt.Sprintf("%s:%d", bindIP, rule.LocalPort)
|
|
ln, err := net.Listen("tcp", addr)
|
|
if err != nil {
|
|
logger.Error("failed to listen locally", "error", err)
|
|
return
|
|
}
|
|
logger.Debug("listening", slog.String("on", addr))
|
|
|
|
go func() {
|
|
<-ctx.Done()
|
|
ln.Close()
|
|
}()
|
|
|
|
for {
|
|
conn, err := ln.Accept()
|
|
if err != nil {
|
|
if ctx.Err() != nil {
|
|
return
|
|
}
|
|
logger.Error("accept error", "error", err)
|
|
continue
|
|
}
|
|
go handleTCPConnect(ctx, srv, conn, rule, logger)
|
|
}
|
|
}
|
|
|
|
func handleTCPConnect(ctx context.Context, srv *tsnet.Server, conn net.Conn, rule ConnectRule, logger *slog.Logger) {
|
|
clog := logger.With(slog.String("local_client", conn.RemoteAddr().String()))
|
|
|
|
// Resolve MagicDNS / split-DNS names through the tailnet resolver before
|
|
// dialing; tsnet's own Dial cannot resolve custom split-DNS suffixes.
|
|
dstAddr := rule.DstAddr
|
|
if resolved, rerr := resolveDialAddr(ctx, srv, rule.DstAddr); rerr != nil {
|
|
clog.Debug("failed to resolve dst via tailnet dns, dialing name directly",
|
|
slog.String("dst", rule.DstAddr), slog.String("error", rerr.Error()))
|
|
} else {
|
|
dstAddr = resolved
|
|
}
|
|
|
|
tsConn, err := dialTsnet(ctx, srv, "tcp", dstAddr)
|
|
if err != nil {
|
|
clog.Error("failed to dial tailscale", "error", err)
|
|
conn.Close()
|
|
return
|
|
}
|
|
|
|
stop := context.AfterFunc(ctx, func() {
|
|
conn.Close()
|
|
tsConn.Close()
|
|
})
|
|
defer stop()
|
|
|
|
clog.Info("accepted connection", slog.String("dst_addr", rule.DstAddr), slog.String("resolved", dstAddr))
|
|
toConn, toTs := pipeConns(conn, tsConn)
|
|
clog.Info("connection closed", slog.Int64("ts_rx_bytes", toTs), slog.Int64("ts_tx_bytes", toConn))
|
|
}
|