rename vtun to tungo

This commit is contained in:
ginuerzh
2025-07-26 16:17:04 +08:00
parent 281295d02f
commit 35a049fb03
30 changed files with 801 additions and 307 deletions
@@ -1,6 +1,4 @@
// Package iobased provides the implementation of io.ReadWriter
// based data-link layer endpoints.
package tun
package tungo
import (
"context"
+180
View File
@@ -0,0 +1,180 @@
package tungo
import (
"context"
"errors"
"net"
"time"
"github.com/go-gost/core/handler"
"github.com/go-gost/core/limiter"
"github.com/go-gost/core/limiter/traffic"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/observer"
"github.com/go-gost/core/recorder"
ctxvalue "github.com/go-gost/x/ctx"
stats_util "github.com/go-gost/x/internal/util/stats"
tun_util "github.com/go-gost/x/internal/util/tun"
cache_limiter "github.com/go-gost/x/limiter/traffic/cache"
xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry"
"github.com/xjasonlyu/tun2socks/v2/core"
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
)
func init() {
registry.HandlerRegistry().Register("tungo", NewHandler)
}
type tungoHandler struct {
options handler.Options
md metadata
stats *stats_util.HandlerStats
limiter traffic.TrafficLimiter
cancel context.CancelFunc
recorder recorder.RecorderObject
}
func NewHandler(opts ...handler.Option) handler.Handler {
options := handler.Options{}
for _, opt := range opts {
opt(&options)
}
return &tungoHandler{
options: options,
}
}
func (h *tungoHandler) Init(md md.Metadata) (err error) {
if err = h.parseMetadata(md); err != nil {
return
}
ctx, cancel := context.WithCancel(context.Background())
h.cancel = cancel
if h.options.Observer != nil {
h.stats = stats_util.NewHandlerStats(h.options.Service, h.md.observerResetTraffic)
go h.observeStats(ctx)
}
if h.options.Limiter != nil {
h.limiter = cache_limiter.NewCachedTrafficLimiter(h.options.Limiter,
cache_limiter.RefreshIntervalOption(h.md.limiterRefreshInterval),
cache_limiter.CleanupIntervalOption(h.md.limiterCleanupInterval),
cache_limiter.ScopeOption(limiter.ScopeClient),
)
}
for _, ro := range h.options.Recorders {
if ro.Record == xrecorder.RecorderServiceHandler {
h.recorder = ro
break
}
}
return
}
func (h *tungoHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error {
defer conn.Close()
log := h.options.Logger
var config *tun_util.Config
if v, _ := conn.(md.Metadatable); v != nil {
config = v.Metadata().Get("config").(*tun_util.Config)
}
if config == nil {
err := errors.New("tun: wrong connection type")
log.Error(err)
return err
}
start := time.Now()
log = log.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() {
log.WithFields(map[string]any{
"duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}()
th := &transportHandler{
tcpQueue: make(chan adapter.TCPConn),
udpQueue: make(chan adapter.UDPConn),
udpTimeout: h.md.udpTimeout,
procCancel: func() { /* nop */ },
sniffing: h.md.sniffing,
sniffingTimeout: h.md.sniffingTimeout,
sniffingResponseTimeout: h.md.sniffingResponseTimeout,
sniffingFallback: h.md.sniffingFallback,
recorder: h.recorder,
stats: h.stats,
opts: &h.options,
}
th.ProcessAsync()
defer th.Close()
stack, err := core.CreateStack(&core.Config{
LinkEndpoint: newEndpoint(conn, config.MTU, log),
TransportHandler: th,
})
if err != nil {
return err
}
defer stack.Close()
<-ctx.Done()
return nil
}
// Close implements io.Closer interface.
func (h *tungoHandler) Close() error {
if h.cancel != nil {
h.cancel()
}
return nil
}
func (h *tungoHandler) observeStats(ctx context.Context) {
if h.options.Observer == nil {
return
}
var events []observer.Event
ticker := time.NewTicker(h.md.observerPeriod)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if len(events) > 0 {
if err := h.options.Observer.Observe(ctx, events); err == nil {
events = nil
}
break
}
evs := h.stats.Events()
if err := h.options.Observer.Observe(ctx, evs); err != nil {
events = evs
}
case <-ctx.Done():
return
}
}
}
+46
View File
@@ -0,0 +1,46 @@
package tungo
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
udpTimeout time.Duration
sniffing bool
sniffingTimeout time.Duration
sniffingResponseTimeout time.Duration
sniffingFallback bool
observerPeriod time.Duration
observerResetTraffic bool
limiterRefreshInterval time.Duration
limiterCleanupInterval time.Duration
}
func (h *tungoHandler) parseMetadata(md mdata.Metadata) (err error) {
h.md.udpTimeout = mdutil.GetDuration(md, "udpTimeout")
h.md.sniffing = mdutil.GetBool(md, "sniffing")
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
h.md.sniffingResponseTimeout = mdutil.GetDuration(md, "sniffing.responseTimeout")
h.md.sniffingFallback = mdutil.GetBool(md, "sniffing.fallback")
h.md.observerPeriod = mdutil.GetDuration(md, "observePeriod", "observer.period", "observer.observePeriod")
if h.md.observerPeriod == 0 {
h.md.observerPeriod = 5 * time.Second
}
if h.md.observerPeriod < time.Second {
h.md.observerPeriod = time.Second
}
h.md.observerResetTraffic = mdutil.GetBool(md, "observer.resetTraffic")
h.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
h.md.limiterCleanupInterval = mdutil.GetDuration(md, "limiter.cleanupInterval")
return
}
+377
View File
@@ -0,0 +1,377 @@
package tungo
import (
"bufio"
"bytes"
"context"
"crypto/tls"
"io"
"math"
"net"
"net/netip"
"strings"
"sync"
"time"
"github.com/go-gost/core/handler"
"github.com/go-gost/core/observer/stats"
"github.com/go-gost/core/recorder"
ctxvalue "github.com/go-gost/x/ctx"
xnet "github.com/go-gost/x/internal/net"
"github.com/go-gost/x/internal/util/sniffing"
stats_util "github.com/go-gost/x/internal/util/stats"
xstats "github.com/go-gost/x/observer/stats"
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
xrecorder "github.com/go-gost/x/recorder"
"github.com/rs/xid"
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
)
const (
// udpSessionTimeout is the default timeout for UDP sessions.
udpSessionTimeout = 30 * time.Second
)
var _ adapter.TransportHandler = (*transportHandler)(nil)
type transportHandler struct {
// Unbuffered TCP/UDP queues.
tcpQueue chan adapter.TCPConn
udpQueue chan adapter.UDPConn
// UDP session timeout.
udpTimeout time.Duration
procOnce sync.Once
procCancel context.CancelFunc
sniffing bool
sniffingTimeout time.Duration
sniffingResponseTimeout time.Duration
sniffingFallback bool
stats *stats_util.HandlerStats
recorder recorder.RecorderObject
opts *handler.Options
}
func (h *transportHandler) HandleTCP(conn adapter.TCPConn) {
h.tcpQueue <- conn
}
func (h *transportHandler) HandleUDP(conn adapter.UDPConn) {
h.udpQueue <- conn
}
func (h *transportHandler) process(ctx context.Context) {
for {
select {
case conn := <-h.tcpQueue:
go h.handleTCPConn(conn)
case conn := <-h.udpQueue:
go h.handleUDPConn(conn)
case <-ctx.Done():
return
}
}
}
// ProcessAsync can be safely called multiple times, but will only be effective once.
func (h *transportHandler) ProcessAsync() {
h.procOnce.Do(func() {
ctx, cancel := context.WithCancel(context.Background())
h.procCancel = cancel
go h.process(ctx)
})
}
// Close closes the Tunnel and releases its resources.
func (h *transportHandler) Close() {
h.procCancel()
}
func (h *transportHandler) handleTCPConn(originConn adapter.TCPConn) {
defer originConn.Close()
id := originConn.ID()
remoteIP, _ := netip.AddrFromSlice(id.RemoteAddress.AsSlice())
dstIP, _ := netip.AddrFromSlice(id.LocalAddress.AsSlice())
remoteAddr := netip.AddrPortFrom(remoteIP, id.RemotePort)
dstAddr := netip.AddrPortFrom(dstIP, id.LocalPort)
start := time.Now()
sid := xid.New().String()
ctx := ctxvalue.ContextWithSid(context.Background(), ctxvalue.Sid(sid))
ro := &xrecorder.HandlerRecorderObject{
Service: h.opts.Service,
Network: "tcp",
RemoteAddr: remoteAddr.String(),
Dst: dstAddr.String(),
ClientIP: remoteAddr.String(),
Time: start,
SID: sid,
}
if h, _, _ := net.SplitHostPort(ro.ClientIP); h != "" {
ro.ClientIP = h
}
log := h.opts.Logger.WithFields(map[string]any{
"network": ro.Network,
"remote": ro.RemoteAddr,
"dst": ro.Dst,
"client": ro.ClientIP,
"sid": ro.SID,
})
log.Debugf("%s <> %s", remoteAddr.String(), dstAddr.String())
var err error
var conn net.Conn = originConn
pStats := xstats.Stats{}
conn = stats_wrapper.WrapConn(conn, &pStats)
defer func() {
if err != nil {
ro.Err = err.Error()
}
ro.InputBytes = pStats.Get(stats.KindInputBytes)
ro.OutputBytes = pStats.Get(stats.KindOutputBytes)
ro.Duration = time.Since(start)
log.WithFields(map[string]any{
"src": ro.Src,
"duration": time.Since(start),
"inputBytes": ro.InputBytes,
"outputBytes": ro.OutputBytes,
}).Infof("%s >< %s", remoteAddr.String(), dstAddr.String())
}()
if pstats := h.stats.Stats(""); pstats != nil {
pstats.Add(stats.KindTotalConns, 1)
pstats.Add(stats.KindCurrentConns, 1)
defer pstats.Add(stats.KindCurrentConns, -1)
rw := stats_wrapper.WrapReadWriter(conn, pstats)
conn = xnet.NewReadWriteConn(rw, rw, conn)
}
if h.sniffing {
if h.sniffingTimeout > 0 {
conn.SetReadDeadline(time.Now().Add(h.sniffingTimeout))
}
br := bufio.NewReader(conn)
proto, _ := sniffing.Sniff(ctx, br)
ro.Proto = proto
if h.sniffingTimeout > 0 {
conn.SetReadDeadline(time.Time{})
}
dial := func(ctx context.Context, network, address string) (net.Conn, error) {
log.Debugf("dial %s/%s", address, network)
var cc net.Conn
var err error
if address != "" {
host, _, _ := net.SplitHostPort(address)
if host == "" {
host = address
}
_, port, _ := net.SplitHostPort(dstAddr.String())
address = net.JoinHostPort(strings.Trim(host, "[]"), port)
ro.Host = address
var buf bytes.Buffer
cc, err = h.opts.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), "tcp", address)
ro.Route = buf.String()
if err != nil && !h.sniffingFallback {
return nil, err
}
}
if cc == nil {
var buf bytes.Buffer
cc, err = h.opts.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), "tcp", dstAddr.String())
ro.Route = buf.String()
ro.Host = dstAddr.String()
}
return cc, err
}
dialTLS := func(ctx context.Context, network, address string, cfg *tls.Config) (net.Conn, error) {
return dial(ctx, network, address)
}
sniffer := &sniffing.Sniffer{
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
ReadTimeout: h.sniffingResponseTimeout,
}
conn = xnet.NewReadWriteConn(br, conn, conn)
switch proto {
case sniffing.ProtoHTTP:
sniffer.HandleHTTP(ctx, conn,
sniffing.WithDial(dial),
sniffing.WithDialTLS(dialTLS),
sniffing.WithBypass(h.opts.Bypass),
sniffing.WithRecorderObject(ro),
sniffing.WithLog(log),
)
return
case sniffing.ProtoTLS:
sniffer.HandleTLS(ctx, conn,
sniffing.WithDial(dial),
sniffing.WithDialTLS(dialTLS),
sniffing.WithBypass(h.opts.Bypass),
sniffing.WithRecorderObject(ro),
sniffing.WithLog(log),
)
return
}
}
if h.opts.Bypass != nil &&
h.opts.Bypass.Contains(ctx, "tcp", dstAddr.String()) {
log.Debug("bypass: ", dstAddr)
return
}
var buf bytes.Buffer
cc, err := h.opts.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), "tcp", dstAddr.String())
ro.Route = buf.String()
if err != nil {
log.Errorf("dial %s: %v", dstAddr.String(), err)
return
}
defer cc.Close()
ro.Src = cc.LocalAddr().String()
log = log.WithFields(map[string]any{"src": ro.Src})
t := time.Now()
log.Infof("%s <-> %s", remoteAddr, dstAddr)
xnet.Transport(conn, cc)
log.WithFields(map[string]any{
"duration": time.Since(t),
}).Infof("%s >-< %s", remoteAddr, dstAddr)
}
func (h *transportHandler) handleUDPConn(uc adapter.UDPConn) {
defer uc.Close()
id := uc.ID()
remoteIP, _ := netip.AddrFromSlice(id.RemoteAddress.AsSlice())
dstIP, _ := netip.AddrFromSlice(id.LocalAddress.AsSlice())
remoteAddr := netip.AddrPortFrom(remoteIP, id.RemotePort)
dstAddr := netip.AddrPortFrom(dstIP, id.LocalPort)
start := time.Now()
sid := xid.New().String()
ctx := ctxvalue.ContextWithSid(context.Background(), ctxvalue.Sid(sid))
ro := &xrecorder.HandlerRecorderObject{
Service: h.opts.Service,
Network: "udp",
RemoteAddr: remoteAddr.String(),
Dst: dstAddr.String(),
Time: start,
SID: sid,
}
log := h.opts.Logger.WithFields(map[string]any{
"network": ro.Network,
"remote": ro.RemoteAddr,
"dst": ro.Dst,
"sid": ro.SID,
})
log.Debugf("%s <> %s", remoteAddr.String(), dstAddr.String())
var err error
var conn net.Conn = uc
pStats := xstats.Stats{}
conn = stats_wrapper.WrapConn(conn, &pStats)
defer func() {
if err != nil {
ro.Err = err.Error()
}
ro.InputBytes = pStats.Get(stats.KindInputBytes)
ro.OutputBytes = pStats.Get(stats.KindOutputBytes)
ro.Duration = time.Since(start)
log.WithFields(map[string]any{
"src": ro.Src,
"duration": time.Since(start),
"inputBytes": ro.InputBytes,
"outputBytes": ro.OutputBytes,
}).Infof("%s >< %s", remoteAddr.String(), dstAddr.String())
}()
cc, err := h.opts.Router.Dial(ctx, "udp", dstAddr.String())
if err != nil {
log.Errorf("dial %s: %v", dstAddr.String(), err)
return
}
defer cc.Close()
ro.Src = cc.LocalAddr().String()
log = log.WithFields(map[string]any{"src": ro.Src})
t := time.Now()
log.Infof("%s <-> %s", remoteAddr, dstAddr)
pipeData(conn, cc, h.udpTimeout)
log.WithFields(map[string]any{
"duration": time.Since(t),
}).Infof("%s >-< %s", remoteAddr, dstAddr)
}
func pipeData(conn1, conn2 net.Conn, timeout time.Duration) {
if timeout <= 0 {
timeout = udpSessionTimeout
}
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
copyData(conn1, conn2, timeout)
}()
go func() {
defer wg.Done()
copyData(conn2, conn1, timeout)
}()
wg.Wait()
}
func copyData(dst, src net.Conn, timeout time.Duration) error {
buf := make([]byte, math.MaxUint16)
for {
src.SetReadDeadline(time.Now().Add(timeout))
n, err := src.Read(buf)
if ne, ok := err.(net.Error); ok && ne.Timeout() {
return nil /* ignore I/O timeout */
} else if err == io.EOF {
return nil /* ignore EOF */
} else if err != nil {
return err
}
if _, err = dst.Write(buf[:n]); err != nil {
return err
}
dst.SetReadDeadline(time.Now().Add(timeout))
}
}
-89
View File
@@ -1,89 +0,0 @@
package tun
import (
"context"
"errors"
"net"
"time"
"github.com/go-gost/core/handler"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
tun_util "github.com/go-gost/x/internal/util/tun"
"github.com/go-gost/x/registry"
"github.com/xjasonlyu/tun2socks/v2/core"
)
func init() {
registry.HandlerRegistry().Register("vtun", NewHandler)
}
type tunHandler struct {
options handler.Options
}
func NewHandler(opts ...handler.Option) handler.Handler {
options := handler.Options{}
for _, opt := range opts {
opt(&options)
}
return &tunHandler{
options: options,
}
}
func (h *tunHandler) Init(md md.Metadata) (err error) {
if err = h.parseMetadata(md); err != nil {
return
}
return
}
func (h *tunHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error {
defer conn.Close()
log := h.options.Logger
var config *tun_util.Config
if v, _ := conn.(md.Metadatable); v != nil {
config = v.Metadata().Get("config").(*tun_util.Config)
}
if config == nil {
err := errors.New("tun: wrong connection type")
log.Error(err)
return err
}
start := time.Now()
log = log.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() {
log.WithFields(map[string]any{
"duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}()
th := newTransportHandler(&h.options)
th.ProcessAsync()
defer th.Close()
stack, err := core.CreateStack(&core.Config{
LinkEndpoint: newEndpoint(conn, config.MTU, log),
TransportHandler: th,
})
if err != nil {
return err
}
defer stack.Close()
<-ctx.Done()
return nil
}
-12
View File
@@ -1,12 +0,0 @@
package tun
import (
mdata "github.com/go-gost/core/metadata"
)
type metadata struct {
}
func (h *tunHandler) parseMetadata(md mdata.Metadata) (err error) {
return
}
-166
View File
@@ -1,166 +0,0 @@
package tun
import (
"context"
"net"
"net/netip"
"sync"
"time"
"github.com/go-gost/core/handler"
"github.com/go-gost/core/observer/stats"
ctxvalue "github.com/go-gost/x/ctx"
xnet "github.com/go-gost/x/internal/net"
xstats "github.com/go-gost/x/observer/stats"
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
xrecorder "github.com/go-gost/x/recorder"
"github.com/rs/xid"
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
"go.uber.org/atomic"
)
const (
// tcpConnectTimeout is the default timeout for TCP handshakes.
tcpConnectTimeout = 5 * time.Second
// tcpWaitTimeout implements a TCP half-close timeout.
tcpWaitTimeout = 60 * time.Second
// udpSessionTimeout is the default timeout for UDP sessions.
udpSessionTimeout = 60 * time.Second
)
var _ adapter.TransportHandler = (*transportHandler)(nil)
type transportHandler struct {
// Unbuffered TCP/UDP queues.
tcpQueue chan adapter.TCPConn
udpQueue chan adapter.UDPConn
// UDP session timeout.
udpTimeout *atomic.Duration
procOnce sync.Once
procCancel context.CancelFunc
opts *handler.Options
}
func newTransportHandler(opts *handler.Options) *transportHandler {
return &transportHandler{
tcpQueue: make(chan adapter.TCPConn),
udpQueue: make(chan adapter.UDPConn),
udpTimeout: atomic.NewDuration(udpSessionTimeout),
procCancel: func() { /* nop */ },
opts: opts,
}
}
func (h *transportHandler) HandleTCP(conn adapter.TCPConn) {
h.tcpQueue <- conn
}
func (h *transportHandler) HandleUDP(conn adapter.UDPConn) {
h.udpQueue <- conn
}
func (h *transportHandler) process(ctx context.Context) {
for {
select {
case conn := <-h.tcpQueue:
go h.handleTCPConn(conn)
case conn := <-h.udpQueue:
go h.handleUDPConn(conn)
case <-ctx.Done():
return
}
}
}
// ProcessAsync can be safely called multiple times, but will only be effective once.
func (h *transportHandler) ProcessAsync() {
h.procOnce.Do(func() {
ctx, cancel := context.WithCancel(context.Background())
h.procCancel = cancel
go h.process(ctx)
})
}
// Close closes the Tunnel and releases its resources.
func (h *transportHandler) Close() {
h.procCancel()
}
func (h *transportHandler) SetUDPTimeout(timeout time.Duration) {
h.udpTimeout.Store(timeout)
}
func (h *transportHandler) handleTCPConn(originConn adapter.TCPConn) {
defer originConn.Close()
id := originConn.ID()
remoteIP, _ := netip.AddrFromSlice(id.RemoteAddress.AsSlice())
dstIP, _ := netip.AddrFromSlice(id.LocalAddress.AsSlice())
remoteAddr := netip.AddrPortFrom(remoteIP, id.RemotePort)
dstAddr := netip.AddrPortFrom(dstIP, id.LocalPort)
start := time.Now()
sid := xid.New().String()
ctx := ctxvalue.ContextWithSid(context.Background(), ctxvalue.Sid(sid))
ro := &xrecorder.HandlerRecorderObject{
Service: h.opts.Service,
Network: "tcp",
RemoteAddr: remoteAddr.String(),
Dst: dstAddr.String(),
Time: start,
SID: sid,
}
log := h.opts.Logger.WithFields(map[string]any{"network": "tcp", "sid": ro.SID})
log.Debugf("%s <> %s", remoteAddr.String(), dstAddr.String())
var err error
var conn net.Conn = originConn
pStats := xstats.Stats{}
conn = stats_wrapper.WrapConn(conn, &pStats)
defer func() {
if err != nil {
ro.Err = err.Error()
}
ro.InputBytes = pStats.Get(stats.KindInputBytes)
ro.OutputBytes = pStats.Get(stats.KindOutputBytes)
ro.Duration = time.Since(start)
log.WithFields(map[string]any{
"duration": time.Since(start),
"inputBytes": ro.InputBytes,
"outputBytes": ro.OutputBytes,
}).Infof("%s >< %s", remoteAddr.String(), dstAddr.String())
}()
cc, err := h.opts.Router.Dial(ctx, "tcp", dstAddr.String())
if err != nil {
log.Errorf("dial %s: %v", dstAddr.String(), err)
return
}
defer cc.Close()
ro.Src = cc.LocalAddr().String()
t := time.Now()
log.Infof("%s <-> %s", remoteAddr, dstAddr)
xnet.Transport(conn, cc)
log.WithFields(map[string]any{
"duration": time.Since(t),
}).Infof("%s >-< %s", remoteAddr, dstAddr)
}
// TODO: Port Restricted NAT support.
func (t *transportHandler) handleUDPConn(uc adapter.UDPConn) {
defer uc.Close()
}