This commit is contained in:
qaq
2025-06-17 12:17:33 +08:00
parent 4a6b2c8e0f
commit 9fa968d07c
648 changed files with 64968 additions and 1621 deletions
+125
View File
@@ -0,0 +1,125 @@
package v4
import (
"context"
"errors"
"fmt"
"net"
"strconv"
"time"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/gosocks4"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("socks4", NewConnector)
registry.ConnectorRegistry().Register("socks4a", NewConnector)
}
type socks4Connector struct {
md metadata
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &socks4Connector{
options: options,
}
}
func (c *socks4Connector) Init(md md.Metadata) (err error) {
return c.parseMetadata(md)
}
func (c *socks4Connector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
switch network {
case "tcp", "tcp4", "tcp6":
if _, ok := conn.(net.PacketConn); ok {
err := fmt.Errorf("tcp over udp is unsupported")
log.Error(err)
return nil, err
}
default:
err := fmt.Errorf("network %s is unsupported", network)
log.Error(err)
return nil, err
}
var addr *gosocks4.Addr
if c.md.disable4a {
taddr, err := net.ResolveTCPAddr("tcp4", address)
if err != nil {
log.Error("resolve: ", err)
return nil, err
}
if len(taddr.IP) == 0 {
taddr.IP = net.IPv4zero
}
addr = &gosocks4.Addr{
Type: gosocks4.AddrIPv4,
Host: taddr.IP.String(),
Port: uint16(taddr.Port),
}
} else {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
p, _ := strconv.Atoi(port)
addr = &gosocks4.Addr{
Type: gosocks4.AddrDomain,
Host: host,
Port: uint16(p),
}
}
if c.md.connectTimeout > 0 {
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
defer conn.SetDeadline(time.Time{})
}
var userid []byte
if c.options.Auth != nil {
userid = []byte(c.options.Auth.Username())
}
req := gosocks4.NewRequest(gosocks4.CmdConnect, addr, userid)
log.Trace(req)
if err := req.Write(conn); err != nil {
log.Error(err)
return nil, err
}
reply, err := gosocks4.ReadReply(conn)
if err != nil {
log.Error(err)
return nil, err
}
log.Trace(reply)
if reply.Code != gosocks4.Granted {
err = errors.New("host unreachable")
log.Error(err)
return nil, err
}
return conn, nil
}
+25
View File
@@ -0,0 +1,25 @@
package v4
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
connectTimeout time.Duration
disable4a bool
}
func (c *socks4Connector) parseMetadata(md mdata.Metadata) (err error) {
const (
connectTimeout = "timeout"
disable4a = "disable4a"
)
c.md.connectTimeout = mdutil.GetDuration(md, connectTimeout)
c.md.disable4a = mdutil.GetBool(md, disable4a)
return
}
+132
View File
@@ -0,0 +1,132 @@
package v5
import (
"context"
"fmt"
"net"
"github.com/go-gost/core/connector"
"github.com/go-gost/core/logger"
"github.com/go-gost/gosocks5"
"github.com/go-gost/x/internal/net/udp"
"github.com/go-gost/x/internal/util/mux"
"github.com/go-gost/x/internal/util/socks"
)
// Bind implements connector.Binder.
func (c *socks5Connector) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
})
log.Debugf("bind on %s/%s", address, network)
options := connector.BindOptions{}
for _, opt := range opts {
opt(&options)
}
switch network {
case "tcp", "tcp4", "tcp6":
if options.Mux {
return c.muxBindTCP(ctx, conn, network, address, log)
}
return c.bindTCP(ctx, conn, network, address, log)
case "udp", "udp4", "udp6":
return c.bindUDP(ctx, conn, network, address, &options, log)
default:
err := fmt.Errorf("network %s is unsupported", network)
log.Error(err)
return nil, err
}
}
func (c *socks5Connector) bindTCP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Listener, error) {
laddr, err := c.bind(conn, gosocks5.CmdBind, network, address, log)
if err != nil {
return nil, err
}
return &tcpListener{
addr: laddr,
conn: conn,
logger: log,
}, nil
}
func (c *socks5Connector) muxBindTCP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Listener, error) {
laddr, err := c.bind(conn, socks.CmdMuxBind, network, address, log)
if err != nil {
return nil, err
}
session, err := mux.ServerSession(conn, c.md.muxCfg)
if err != nil {
return nil, err
}
return &tcpMuxListener{
addr: laddr,
session: session,
logger: log,
}, nil
}
func (c *socks5Connector) bindUDP(ctx context.Context, conn net.Conn, network, address string, opts *connector.BindOptions, log logger.Logger) (net.Listener, error) {
laddr, err := c.bind(conn, socks.CmdUDPTun, network, address, log)
if err != nil {
return nil, err
}
ln := udp.NewListener(socks.UDPTunClientPacketConn(conn),
&udp.ListenConfig{
Addr: laddr,
Backlog: opts.Backlog,
ReadQueueSize: opts.UDPDataQueueSize,
ReadBufferSize: opts.UDPDataBufferSize,
TTL: opts.UDPConnTTL,
Keepalive: true,
Logger: log,
})
return ln, nil
}
func (l *socks5Connector) bind(conn net.Conn, cmd uint8, network, address string, log logger.Logger) (net.Addr, error) {
addr := gosocks5.Addr{}
addr.ParseFrom(address)
req := gosocks5.NewRequest(cmd, &addr)
log.Trace(req)
if err := req.Write(conn); err != nil {
return nil, err
}
// first reply, bind status
reply, err := gosocks5.ReadReply(conn)
if err != nil {
return nil, err
}
log.Trace(reply)
if reply.Rep != gosocks5.Succeeded {
return nil, fmt.Errorf("bind on %s/%s failed", address, network)
}
var baddr net.Addr
switch network {
case "tcp", "tcp4", "tcp6":
baddr, err = net.ResolveTCPAddr(network, reply.Addr.String())
case "udp", "udp4", "udp6":
baddr, err = net.ResolveUDPAddr(network, reply.Addr.String())
default:
err = fmt.Errorf("unknown network %s", network)
}
if err != nil {
return nil, err
}
log.Debugf("bind on %s/%s OK", baddr, baddr.Network())
return baddr, nil
}
+129
View File
@@ -0,0 +1,129 @@
package v5
import (
"bytes"
"net"
"time"
"github.com/go-gost/core/common/bufpool"
"github.com/go-gost/core/logger"
"github.com/go-gost/gosocks5"
)
type bindConn struct {
net.Conn
localAddr net.Addr
remoteAddr net.Addr
}
func (c *bindConn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *bindConn) RemoteAddr() net.Addr {
return c.remoteAddr
}
type udpRelayConn struct {
udpConn *net.UDPConn
tcpConn net.Conn
taddr net.Addr
bufferSize int
logger logger.Logger
}
func (c *udpRelayConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
buf := bufpool.Get(c.bufferSize)
defer bufpool.Put(buf)
nn, err := c.udpConn.Read(buf)
if err != nil {
return
}
socksAddr := gosocks5.Addr{}
header := gosocks5.UDPHeader{
Addr: &socksAddr,
}
dgram := gosocks5.UDPDatagram{
Header: &header,
}
_, err = dgram.ReadFrom(bytes.NewReader(buf[:nn]))
if err != nil {
return
}
n = copy(b, dgram.Data)
addr, err = net.ResolveUDPAddr("udp", header.Addr.String())
return
}
func (c *udpRelayConn) Read(b []byte) (n int, err error) {
n, _, err = c.ReadFrom(b)
return
}
func (c *udpRelayConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
socksAddr := gosocks5.Addr{}
if err = socksAddr.ParseFrom(addr.String()); err != nil {
return
}
if socksAddr.Host == "" {
socksAddr.Type = gosocks5.AddrIPv4
socksAddr.Host = "127.0.0.1"
}
header := gosocks5.UDPHeader{
Addr: &socksAddr,
}
dgram := gosocks5.UDPDatagram{
Header: &header,
Data: b,
}
buf := bufpool.Get(c.bufferSize)
defer bufpool.Put(buf)
nn, err := dgram.WriteTo(bytes.NewBuffer(buf[:0]))
if err != nil {
return
}
if nn > int64(len(buf)) {
nn = int64(len(buf))
}
_, err = c.udpConn.Write(buf[:nn])
n = len(b)
return
}
func (c *udpRelayConn) Write(b []byte) (n int, err error) {
return c.WriteTo(b, c.taddr)
}
func (c *udpRelayConn) RemoteAddr() net.Addr {
return c.taddr
}
func (c *udpRelayConn) LocalAddr() net.Addr {
return c.udpConn.LocalAddr()
}
func (c *udpRelayConn) Close() error {
c.udpConn.Close()
return c.tcpConn.Close()
}
func (c *udpRelayConn) SetDeadline(t time.Time) error {
return c.udpConn.SetDeadline(t)
}
func (c *udpRelayConn) SetReadDeadline(t time.Time) error {
return c.udpConn.SetReadDeadline(t)
}
func (c *udpRelayConn) SetWriteDeadline(t time.Time) error {
return c.udpConn.SetWriteDeadline(t)
}
+234
View File
@@ -0,0 +1,234 @@
package v5
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"time"
"github.com/go-gost/core/connector"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/gosocks5"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/internal/util/socks"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("socks5", NewConnector)
registry.ConnectorRegistry().Register("socks", NewConnector)
}
type socks5Connector struct {
selector gosocks5.Selector
md metadata
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &socks5Connector{
options: options,
}
}
func (c *socks5Connector) Init(md md.Metadata) (err error) {
if err = c.parseMetadata(md); err != nil {
return
}
selector := &clientSelector{
methods: []uint8{
gosocks5.MethodNoAuth,
},
User: c.options.Auth,
TLSConfig: c.options.TLSConfig,
logger: c.options.Logger,
}
if selector.User != nil {
selector.methods = append(selector.methods, gosocks5.MethodUserPass)
}
if !c.md.noTLS {
selector.methods = append(selector.methods, socks.MethodTLS)
if selector.TLSConfig == nil {
selector.TLSConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
if selector.User != nil {
selector.methods = append(selector.methods, socks.MethodTLSAuth)
}
}
c.selector = selector
return
}
// Handshake implements connector.Handshaker.
func (c *socks5Connector) Handshake(ctx context.Context, conn net.Conn) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
})
if c.md.connectTimeout > 0 {
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
defer conn.SetDeadline(time.Time{})
}
cc := gosocks5.ClientConn(conn, c.selector)
if err := cc.Handleshake(); err != nil {
log.Error(err)
return nil, err
}
return cc, nil
}
func (c *socks5Connector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
if c.md.connectTimeout > 0 {
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
defer conn.SetDeadline(time.Time{})
}
var cOpts connector.ConnectOptions
for _, opt := range opts {
opt(&cOpts)
}
switch network {
case "udp", "udp4", "udp6":
return c.connectUDP(ctx, conn, network, address, log, &cOpts)
case "tcp", "tcp4", "tcp6":
if _, ok := conn.(net.PacketConn); ok {
err := fmt.Errorf("tcp over udp is unsupported")
log.Error(err)
return nil, err
}
default:
err := fmt.Errorf("network %s is unsupported", network)
log.Error(err)
return nil, err
}
addr := gosocks5.Addr{}
if err := addr.ParseFrom(address); err != nil {
log.Error(err)
return nil, err
}
if addr.Host == "" {
addr.Type = gosocks5.AddrIPv4
addr.Host = "127.0.0.1"
}
req := gosocks5.NewRequest(gosocks5.CmdConnect, &addr)
log.Trace(req)
if err := req.Write(conn); err != nil {
log.Error(err)
return nil, err
}
reply, err := gosocks5.ReadReply(conn)
if err != nil {
log.Error(err)
return nil, err
}
log.Trace(reply)
if reply.Rep != gosocks5.Succeeded {
err = errors.New("host unreachable")
log.Error(err)
return nil, err
}
return conn, nil
}
func (c *socks5Connector) connectUDP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger, opts *connector.ConnectOptions) (net.Conn, error) {
addr, err := net.ResolveUDPAddr(network, address)
if err != nil {
log.Error(err)
return nil, err
}
if c.md.relay == "udp" {
return c.relayUDP(ctx, conn, addr, log, opts)
}
req := gosocks5.NewRequest(socks.CmdUDPTun, nil)
log.Trace(req)
if err := req.Write(conn); err != nil {
log.Error(err)
return nil, err
}
reply, err := gosocks5.ReadReply(conn)
if err != nil {
log.Error(err)
return nil, err
}
log.Trace(reply)
if reply.Rep != gosocks5.Succeeded {
return nil, errors.New("get socks5 UDP tunnel failure")
}
return socks.UDPTunClientConn(conn, addr), nil
}
func (c *socks5Connector) relayUDP(ctx context.Context, conn net.Conn, addr net.Addr, log logger.Logger, opts *connector.ConnectOptions) (net.Conn, error) {
req := gosocks5.NewRequest(gosocks5.CmdUdp, nil)
log.Trace(req)
if err := req.Write(conn); err != nil {
log.Error(err)
return nil, err
}
reply, err := gosocks5.ReadReply(conn)
if err != nil {
log.Error(err)
return nil, err
}
log.Trace(reply)
if reply.Rep != gosocks5.Succeeded {
return nil, errors.New("get socks5 UDP tunnel failure")
}
log.Debugf("bind on: %v", reply.Addr)
cc, err := opts.Dialer.Dial(ctx, "udp", reply.Addr.String())
if err != nil {
c.options.Logger.Error(err)
return nil, err
}
log.Debugf("%s <- %s -> %s", cc.LocalAddr(), cc.RemoteAddr(), addr)
if c.md.udpTimeout > 0 {
cc.SetReadDeadline(time.Now().Add(c.md.udpTimeout))
}
return &udpRelayConn{
udpConn: cc.(*net.UDPConn),
tcpConn: conn,
taddr: addr,
bufferSize: c.md.udpBufferSize,
logger: log,
}, nil
}
+102
View File
@@ -0,0 +1,102 @@
package v5
import (
"fmt"
"net"
"github.com/go-gost/core/logger"
"github.com/go-gost/gosocks5"
"github.com/go-gost/x/internal/util/mux"
)
type tcpListener struct {
addr net.Addr
conn net.Conn
logger logger.Logger
}
func (p *tcpListener) Accept() (net.Conn, error) {
// second reply, peer connected
rep, err := gosocks5.ReadReply(p.conn)
if err != nil {
return nil, err
}
p.logger.Trace(rep)
if rep.Rep != gosocks5.Succeeded {
return nil, fmt.Errorf("peer connect failed")
}
raddr, err := net.ResolveTCPAddr("tcp", rep.Addr.String())
if err != nil {
return nil, err
}
return &bindConn{
Conn: p.conn,
localAddr: p.addr,
remoteAddr: raddr,
}, nil
}
func (p *tcpListener) Addr() net.Addr {
return p.addr
}
func (p *tcpListener) Close() error {
return p.conn.Close()
}
type tcpMuxListener struct {
addr net.Addr
session *mux.Session
logger logger.Logger
}
func (p *tcpMuxListener) Accept() (net.Conn, error) {
cc, err := p.session.Accept()
if err != nil {
return nil, err
}
conn, err := p.getPeerConn(cc)
if err != nil {
cc.Close()
return nil, err
}
return conn, nil
}
func (p *tcpMuxListener) getPeerConn(conn net.Conn) (net.Conn, error) {
// second reply, peer connected
rep, err := gosocks5.ReadReply(conn)
if err != nil {
return nil, err
}
p.logger.Trace(rep)
if rep.Rep != gosocks5.Succeeded {
err = fmt.Errorf("peer connect failed")
return nil, err
}
raddr, err := net.ResolveTCPAddr("tcp", rep.Addr.String())
if err != nil {
return nil, err
}
return &bindConn{
Conn: conn,
localAddr: p.addr,
remoteAddr: raddr,
}, nil
}
func (p *tcpMuxListener) Addr() net.Addr {
return p.addr
}
func (p *tcpMuxListener) Close() error {
return p.session.Close()
}
+44
View File
@@ -0,0 +1,44 @@
package v5
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/internal/util/mux"
)
const (
defaultUDPBufferSize = 4096
)
type metadata struct {
connectTimeout time.Duration
noTLS bool
relay string
udpBufferSize int
udpTimeout time.Duration
muxCfg *mux.Config
}
func (c *socks5Connector) parseMetadata(md mdata.Metadata) (err error) {
c.md.connectTimeout = mdutil.GetDuration(md, "timeout")
c.md.noTLS = mdutil.GetBool(md, "notls")
c.md.relay = mdutil.GetString(md, "relay")
c.md.udpBufferSize = mdutil.GetInt(md, "udp.bufferSize", "udpBufferSize")
if c.md.udpBufferSize <= 0 {
c.md.udpBufferSize = defaultUDPBufferSize
}
c.md.udpTimeout = mdutil.GetDuration(md, "udp.timeout")
c.md.muxCfg = &mux.Config{
Version: mdutil.GetInt(md, "mux.version"),
KeepAliveInterval: mdutil.GetDuration(md, "mux.keepaliveInterval"),
KeepAliveDisabled: mdutil.GetBool(md, "mux.keepaliveDisabled"),
KeepAliveTimeout: mdutil.GetDuration(md, "mux.keepaliveTimeout"),
MaxFrameSize: mdutil.GetInt(md, "mux.maxFrameSize"),
MaxReceiveBuffer: mdutil.GetInt(md, "mux.maxReceiveBuffer"),
MaxStreamBuffer: mdutil.GetInt(md, "mux.maxStreamBuffer"),
}
return
}
+77
View File
@@ -0,0 +1,77 @@
package v5
import (
"crypto/tls"
"net"
"net/url"
"github.com/go-gost/core/logger"
"github.com/go-gost/gosocks5"
"github.com/go-gost/x/internal/util/socks"
)
type clientSelector struct {
methods []uint8
User *url.Userinfo
TLSConfig *tls.Config
logger logger.Logger
}
func (s *clientSelector) Methods() []uint8 {
s.logger.Debug("methods: ", s.methods)
return s.methods
}
func (s *clientSelector) AddMethod(methods ...uint8) {
s.methods = append(s.methods, methods...)
}
func (s *clientSelector) Select(methods ...uint8) (method uint8) {
return
}
func (s *clientSelector) OnSelected(method uint8, conn net.Conn) (string, net.Conn, error) {
s.logger.Debug("method selected: ", method)
switch method {
case gosocks5.MethodNoAuth:
case socks.MethodTLS:
conn = tls.Client(conn, s.TLSConfig)
case gosocks5.MethodUserPass, socks.MethodTLSAuth:
if method == socks.MethodTLSAuth {
conn = tls.Client(conn, s.TLSConfig)
}
var username, password string
if s.User != nil {
username = s.User.Username()
password, _ = s.User.Password()
}
req := gosocks5.NewUserPassRequest(gosocks5.UserPassVer, username, password)
s.logger.Trace(req)
if err := req.Write(conn); err != nil {
s.logger.Error(err)
return "", nil, err
}
resp, err := gosocks5.ReadUserPassResponse(conn)
if err != nil {
s.logger.Error(err)
return "", nil, err
}
s.logger.Trace(resp)
if resp.Status != gosocks5.Succeeded {
return "", nil, gosocks5.ErrAuthFailure
}
case gosocks5.MethodNoAcceptable:
return "", nil, gosocks5.ErrBadMethod
default:
return "", nil, gosocks5.ErrBadFormat
}
return "", conn, nil
}