Refactor(proxy): registrable proxy protocol (#517)

This commit is contained in:
Jason Lyu
2026-01-10 02:07:46 -05:00
committed by GitHub
parent 27e493b195
commit b9aee255ab
21 changed files with 375 additions and 399 deletions
+2 -6
View File
@@ -193,7 +193,7 @@ func netstack(k *Key) (err error) {
if _defaultProxy, err = parseProxy(k.Proxy); err != nil {
return err
}
tunnel.T().SetDialer(_defaultProxy)
tunnel.T().SetProxy(_defaultProxy)
if _defaultDevice, err = parseDevice(k.Device, uint32(k.MTU)); err != nil {
return err
@@ -234,10 +234,6 @@ func netstack(k *Key) (err error) {
return err
}
log.Infof(
"[STACK] %s://%s <-> %s://%s",
_defaultDevice.Type(), _defaultDevice.Name(),
_defaultProxy.Proto(), _defaultProxy.Addr(),
)
log.Infof("[STACK] %s <-> %s", k.Device, k.Proxy)
return nil
}
+8 -104
View File
@@ -1,7 +1,6 @@
package engine
import (
"encoding/base64"
"fmt"
"net"
"net/netip"
@@ -9,13 +8,15 @@ import (
"runtime"
"strings"
"github.com/gorilla/schema"
"github.com/xjasonlyu/tun2socks/v2/core/device"
"github.com/xjasonlyu/tun2socks/v2/core/device/fdbased"
"github.com/xjasonlyu/tun2socks/v2/core/device/tun"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
)
const (
defaultDeviceType = "tun"
defaultProxyType = "socks5"
)
func parseRestAPI(s string) (*url.URL, error) {
@@ -47,7 +48,7 @@ func parseRestAPI(s string) (*url.URL, error) {
func parseDevice(s string, mtu uint32) (device.Device, error) {
if !strings.Contains(s, "://") {
s = fmt.Sprintf("%s://%s", tun.Driver /* default driver */, s)
s = fmt.Sprintf("%s://%s", defaultDeviceType, s)
}
u, err := url.Parse(s)
@@ -79,111 +80,14 @@ func parseFD(u *url.URL, mtu uint32) (device.Device, error) {
func parseProxy(s string) (proxy.Proxy, error) {
if !strings.Contains(s, "://") {
s = fmt.Sprintf("%s://%s", proto.Socks5 /* default protocol */, s)
s = fmt.Sprintf("%s://%s", defaultProxyType, s)
}
u, err := url.Parse(s)
if err != nil {
return nil, err
}
protocol := strings.ToLower(u.Scheme)
switch protocol {
case proto.Direct.String():
return proxy.NewDirect(), nil
case proto.Reject.String():
return proxy.NewReject(), nil
case proto.HTTP.String():
return parseHTTP(u)
case proto.Socks4.String():
return parseSocks4(u)
case proto.Socks5.String():
return parseSocks5(u)
case proto.Shadowsocks.String():
return parseShadowsocks(u)
case proto.Relay.String():
return parseRelay(u)
default:
return nil, fmt.Errorf("unsupported protocol: %s", protocol)
}
}
func parseHTTP(u *url.URL) (proxy.Proxy, error) {
address, username := u.Host, u.User.Username()
password, _ := u.User.Password()
return proxy.NewHTTP(address, username, password)
}
func parseSocks4(u *url.URL) (proxy.Proxy, error) {
address, userID := u.Host, u.User.Username()
return proxy.NewSocks4(address, userID)
}
func parseSocks5(u *url.URL) (proxy.Proxy, error) {
address, username := u.Host, u.User.Username()
password, _ := u.User.Password()
// Socks5 over UDS
if address == "" {
address = u.Path
}
return proxy.NewSocks5(address, username, password)
}
func parseShadowsocks(u *url.URL) (proxy.Proxy, error) {
var (
address = u.Host
method, password string
obfsMode, obfsHost string
)
if ss := u.User.String(); ss == "" {
method = "dummy" // none cipher mode
} else if pass, set := u.User.Password(); set {
method = u.User.Username()
password = pass
} else {
data, _ := base64.RawURLEncoding.DecodeString(ss)
userInfo := strings.SplitN(string(data), ":", 2)
if len(userInfo) == 2 {
method = userInfo[0]
password = userInfo[1]
}
}
rawQuery, _ := url.QueryUnescape(u.RawQuery)
for _, s := range strings.Split(rawQuery, ";") {
data := strings.SplitN(s, "=", 2)
if len(data) != 2 {
continue
}
key := data[0]
value := data[1]
switch key {
case "obfs":
obfsMode = value
case "obfs-host":
obfsHost = value
}
}
return proxy.NewShadowsocks(address, method, password, obfsMode, obfsHost)
}
func parseRelay(u *url.URL) (proxy.Proxy, error) {
address, username := u.Host, u.User.Username()
password, _ := u.User.Password()
opts := struct {
NoDelay bool
}{}
if err := schema.NewDecoder().Decode(&opts, u.Query()); err != nil {
return nil, err
}
return proxy.NewRelay(address, username, password, opts.NoDelay)
return proxy.Parse(u)
}
func parseMulticastGroups(s string) (multicastGroups []netip.Addr, _ error) {
+11
View File
@@ -0,0 +1,11 @@
package engine
import (
_ "github.com/xjasonlyu/tun2socks/v2/proxy/direct"
_ "github.com/xjasonlyu/tun2socks/v2/proxy/http"
_ "github.com/xjasonlyu/tun2socks/v2/proxy/reject"
_ "github.com/xjasonlyu/tun2socks/v2/proxy/relay"
_ "github.com/xjasonlyu/tun2socks/v2/proxy/shadowsocks"
_ "github.com/xjasonlyu/tun2socks/v2/proxy/socks4"
_ "github.com/xjasonlyu/tun2socks/v2/proxy/socks5"
)
-33
View File
@@ -1,33 +0,0 @@
package proxy
import (
"context"
"errors"
"net"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
)
var _ Proxy = (*Base)(nil)
type Base struct {
addr string
proto proto.Proto
}
func (b *Base) Addr() string {
return b.addr
}
func (b *Base) Proto() proto.Proto {
return b.proto
}
func (b *Base) DialContext(context.Context, *M.Metadata) (net.Conn, error) {
return nil, errors.ErrUnsupported
}
func (b *Base) DialUDP(*M.Metadata) (net.PacketConn, error) {
return nil, errors.ErrUnsupported
}
+14 -14
View File
@@ -1,34 +1,28 @@
package proxy
package direct
import (
"context"
"net"
"net/url"
"github.com/xjasonlyu/tun2socks/v2/dialer"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/utils"
)
var _ Proxy = (*Direct)(nil)
var _ proxy.Proxy = (*Direct)(nil)
type Direct struct {
*Base
}
type Direct struct{}
func NewDirect() *Direct {
return &Direct{
Base: &Base{
proto: proto.Direct,
},
}
}
func New() (*Direct, error) { return &Direct{}, nil }
func (d *Direct) DialContext(ctx context.Context, metadata *M.Metadata) (net.Conn, error) {
c, err := dialer.DialContext(ctx, "tcp", metadata.DestinationAddress())
if err != nil {
return nil, err
}
setKeepAlive(c)
utils.SetKeepAlive(c)
return c, nil
}
@@ -55,3 +49,9 @@ func (pc *directPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
}
return pc.PacketConn.WriteTo(b, udpAddr)
}
func Parse(*url.URL) (proxy.Proxy, error) { return New() }
func init() {
proxy.RegisterProtocol("direct", Parse)
}
+26 -13
View File
@@ -1,4 +1,4 @@
package proxy
package http
import (
"bufio"
@@ -13,42 +13,45 @@ import (
"github.com/xjasonlyu/tun2socks/v2/dialer"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/utils"
)
type HTTP struct {
*Base
var _ proxy.Proxy = (*HTTP)(nil)
type HTTP struct {
addr string
user string
pass string
}
func NewHTTP(addr, user, pass string) (*HTTP, error) {
func New(addr, user, pass string) (*HTTP, error) {
return &HTTP{
Base: &Base{
addr: addr,
proto: proto.HTTP,
},
addr: addr,
user: user,
pass: pass,
}, nil
}
func (h *HTTP) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) {
c, err = dialer.DialContext(ctx, "tcp", h.Addr())
c, err = dialer.DialContext(ctx, "tcp", h.addr)
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", h.Addr(), err)
return nil, fmt.Errorf("connect to %s: %w", h.addr, err)
}
setKeepAlive(c)
utils.SetKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
utils.SafeConnClose(c, err)
}(c)
err = h.shakeHand(metadata, c)
return c, err
}
func (h *HTTP) DialUDP(*M.Metadata) (net.PacketConn, error) {
return nil, errors.ErrUnsupported
}
func (h *HTTP) shakeHand(metadata *M.Metadata, rw io.ReadWriter) error {
addr := metadata.DestinationAddress()
req := &http.Request{
@@ -98,3 +101,13 @@ func basicAuth(username, password string) string {
auth := username + ":" + password
return base64.StdEncoding.EncodeToString([]byte(auth))
}
func Parse(u *url.URL) (proxy.Proxy, error) {
address, username := u.Host, u.User.Username()
password, _ := u.User.Password()
return New(address, username, password)
}
func init() {
proxy.RegisterProtocol("http", Parse)
}
+40
View File
@@ -0,0 +1,40 @@
package utils
import (
"context"
"net"
"time"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/transport/socks5"
)
const (
tcpConnectTimeout = 5 * time.Second
tcpKeepAlivePeriod = 30 * time.Second
)
// SetKeepAlive sets tcp keepalive option for tcp connection.
func SetKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
tcp.SetKeepAlive(true)
tcp.SetKeepAlivePeriod(tcpKeepAlivePeriod)
}
}
// SafeConnClose closes tcp connection safely.
func SafeConnClose(c net.Conn, err error) {
if c != nil && err != nil {
c.Close()
}
}
// SerializeSocksAddr serializes metadata to SOCKSv5 address.
func SerializeSocksAddr(m *M.Metadata) socks5.Addr {
return socks5.SerializeAddr("", m.DstIP, m.DstPort)
}
// WithTCPConnectTimeout returns a derived context with the default TCP connect timeout.
func WithTCPConnectTimeout(ctx context.Context) (context.Context, context.CancelFunc) {
return context.WithTimeout(ctx, tcpConnectTimeout)
}
-36
View File
@@ -1,36 +0,0 @@
package proto
import "fmt"
const (
Direct Proto = iota
Reject
HTTP
Socks4
Socks5
Shadowsocks
Relay
)
type Proto uint8
func (proto Proto) String() string {
switch proto {
case Direct:
return "direct"
case Reject:
return "reject"
case HTTP:
return "http"
case Socks4:
return "socks4"
case Socks5:
return "socks5"
case Shadowsocks:
return "ss"
case Relay:
return "relay"
default:
return fmt.Sprintf("proto(%d)", proto)
}
}
+56
View File
@@ -0,0 +1,56 @@
package proxy
import (
"errors"
"net/url"
"sync"
"go.uber.org/atomic"
)
// ErrProtocol indicates that parsing encountered an unknown protocol.
var ErrProtocol = errors.New("proxy: unknown protocol")
// A protocol holds a proxy protocol's name and how to parse it.
type protocol struct {
name string
parse func(*url.URL) (Proxy, error)
}
// Protocols is the list of registered proxy protocols.
var (
protocolsMu sync.Mutex
atomicProtocols atomic.Value
)
// RegisterProtocol registers a proxy protocol for use by [Parse].
// Name is the name of the proxy protocol, like "http" or "socks5".
// [Parse] is the function that parses the proxy url.
func RegisterProtocol(name string, parse func(*url.URL) (Proxy, error)) {
protocolsMu.Lock()
formats, _ := atomicProtocols.Load().([]protocol)
atomicProtocols.Store(append(formats, protocol{name, parse}))
protocolsMu.Unlock()
}
// pick determines the protocol by the given name.
func pick(name string) protocol {
protocols, _ := atomicProtocols.Load().([]protocol)
for _, p := range protocols {
if p.name == name {
return p
}
}
return protocol{}
}
// Parse parses proxy *url.URL that holds the proxy info into Proxy.
// Protocol registration is typically done by an init function in the
// proxy-specific package.
func Parse(proxyURL *url.URL) (Proxy, error) {
p := pick(proxyURL.Scheme)
if p.parse == nil {
return nil, ErrProtocol
}
return p.parse(proxyURL)
}
+1 -37
View File
@@ -4,47 +4,11 @@ package proxy
import (
"context"
"net"
"time"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
)
const (
tcpConnectTimeout = 5 * time.Second
)
var _defaultDialer Dialer = &Base{}
type Dialer interface {
type Proxy interface {
DialContext(context.Context, *M.Metadata) (net.Conn, error)
DialUDP(*M.Metadata) (net.PacketConn, error)
}
type Proxy interface {
Dialer
Addr() string
Proto() proto.Proto
}
// SetDialer sets default Dialer.
func SetDialer(d Dialer) {
_defaultDialer = d
}
// Dial uses default Dialer to dial TCP.
func Dial(metadata *M.Metadata) (net.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpConnectTimeout)
defer cancel()
return _defaultDialer.DialContext(ctx, metadata)
}
// DialContext uses default Dialer to dial TCP with context.
func DialContext(ctx context.Context, metadata *M.Metadata) (net.Conn, error) {
return _defaultDialer.DialContext(ctx, metadata)
}
// DialUDP uses default Dialer to dial UDP.
func DialUDP(metadata *M.Metadata) (net.PacketConn, error) {
return _defaultDialer.DialUDP(metadata)
}
+12 -13
View File
@@ -1,28 +1,21 @@
package proxy
package reject
import (
"context"
"io"
"net"
"net/url"
"time"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
"github.com/xjasonlyu/tun2socks/v2/proxy"
)
var _ Proxy = (*Reject)(nil)
var _ proxy.Proxy = (*Reject)(nil)
type Reject struct {
*Base
}
type Reject struct{}
func NewReject() *Reject {
return &Reject{
Base: &Base{
proto: proto.Reject,
},
}
}
func New() (*Reject, error) { return &Reject{}, nil }
func (r *Reject) DialContext(context.Context, *M.Metadata) (net.Conn, error) {
return &nopConn{}, nil
@@ -52,3 +45,9 @@ func (npc *nopPacketConn) LocalAddr() net.Addr { ret
func (npc *nopPacketConn) SetDeadline(time.Time) error { return nil }
func (npc *nopPacketConn) SetReadDeadline(time.Time) error { return nil }
func (npc *nopPacketConn) SetWriteDeadline(time.Time) error { return nil }
func Parse(*url.URL) (proxy.Proxy, error) { return New() }
func init() {
proxy.RegisterProtocol("reject", Parse)
}
+31 -15
View File
@@ -1,4 +1,4 @@
package proxy
package relay
import (
"bytes"
@@ -9,33 +9,32 @@ import (
"io"
"math"
"net"
"net/url"
"sync"
"github.com/go-gost/relay"
"github.com/gorilla/schema"
"github.com/xjasonlyu/tun2socks/v2/buffer"
"github.com/xjasonlyu/tun2socks/v2/dialer"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/utils"
)
var _ Proxy = (*Relay)(nil)
var _ proxy.Proxy = (*Relay)(nil)
type Relay struct {
*Base
addr string
user string
pass string
noDelay bool
}
func NewRelay(addr, user, pass string, noDelay bool) (*Relay, error) {
func New(addr, user, pass string, noDelay bool) (*Relay, error) {
return &Relay{
Base: &Base{
addr: addr,
proto: proto.Relay,
},
addr: addr,
user: user,
pass: pass,
noDelay: noDelay,
@@ -47,7 +46,7 @@ func (rl *Relay) DialContext(ctx context.Context, metadata *M.Metadata) (c net.C
}
func (rl *Relay) DialUDP(metadata *M.Metadata) (net.PacketConn, error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpConnectTimeout)
ctx, cancel := utils.WithTCPConnectTimeout(context.Background())
defer cancel()
return rl.dialContext(ctx, metadata)
@@ -56,14 +55,14 @@ func (rl *Relay) DialUDP(metadata *M.Metadata) (net.PacketConn, error) {
func (rl *Relay) dialContext(ctx context.Context, metadata *M.Metadata) (rc *relayConn, err error) {
var c net.Conn
c, err = dialer.DialContext(ctx, "tcp", rl.Addr())
c, err = dialer.DialContext(ctx, "tcp", rl.addr)
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", rl.Addr(), err)
return nil, fmt.Errorf("connect to %s: %w", rl.addr, err)
}
setKeepAlive(c)
utils.SetKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
utils.SafeConnClose(c, err)
}(c)
req := relay.Request{
@@ -250,3 +249,20 @@ func serializeRelayAddr(m *M.Metadata) *relay.AddrFeature {
}
return af
}
func Parse(u *url.URL) (proxy.Proxy, error) {
address, username := u.Host, u.User.Username()
password, _ := u.User.Password()
opts := struct{ NoDelay bool }{}
if err := schema.NewDecoder().
Decode(&opts, u.Query()); err != nil {
return nil, err
}
return New(address, username, password, opts.NoDelay)
}
func init() {
proxy.RegisterProtocol("relay", Parse)
}
@@ -1,41 +1,41 @@
package proxy
package shadowsocks
import (
"context"
"encoding/base64"
"errors"
"fmt"
"net"
"net/url"
"strings"
"github.com/xjasonlyu/tun2socks/v2/dialer"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/utils"
"github.com/xjasonlyu/tun2socks/v2/transport/shadowsocks/core"
obfs "github.com/xjasonlyu/tun2socks/v2/transport/simple-obfs"
"github.com/xjasonlyu/tun2socks/v2/transport/socks5"
)
var _ Proxy = (*Shadowsocks)(nil)
var _ proxy.Proxy = (*Shadowsocks)(nil)
type Shadowsocks struct {
*Base
addr string
cipher core.Cipher
// simple-obfs plugin
obfsMode, obfsHost string
}
func NewShadowsocks(addr, method, password, obfsMode, obfsHost string) (*Shadowsocks, error) {
func New(addr, method, password, obfsMode, obfsHost string) (*Shadowsocks, error) {
cipher, err := core.PickCipher(method, nil, password)
if err != nil {
return nil, fmt.Errorf("ss initialize: %w", err)
}
return &Shadowsocks{
Base: &Base{
addr: addr,
proto: proto.Shadowsocks,
},
addr: addr,
cipher: cipher,
obfsMode: obfsMode,
obfsHost: obfsHost,
@@ -43,14 +43,14 @@ func NewShadowsocks(addr, method, password, obfsMode, obfsHost string) (*Shadows
}
func (ss *Shadowsocks) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) {
c, err = dialer.DialContext(ctx, "tcp", ss.Addr())
c, err = dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", ss.Addr(), err)
return nil, fmt.Errorf("connect to %s: %w", ss.addr, err)
}
setKeepAlive(c)
utils.SetKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
utils.SafeConnClose(c, err)
}(c)
switch ss.obfsMode {
@@ -62,7 +62,7 @@ func (ss *Shadowsocks) DialContext(ctx context.Context, metadata *M.Metadata) (c
}
c = ss.cipher.StreamConn(c)
_, err = c.Write(serializeSocksAddr(metadata))
_, err = c.Write(utils.SerializeSocksAddr(metadata))
return c, err
}
@@ -72,9 +72,9 @@ func (ss *Shadowsocks) DialUDP(*M.Metadata) (net.PacketConn, error) {
return nil, fmt.Errorf("listen packet: %w", err)
}
udpAddr, err := net.ResolveUDPAddr("udp", ss.Addr())
udpAddr, err := net.ResolveUDPAddr("udp", ss.addr)
if err != nil {
return nil, fmt.Errorf("resolve udp address %s: %w", ss.Addr(), err)
return nil, fmt.Errorf("resolve udp address %s: %w", ss.addr, err)
}
pc = ss.cipher.PacketConn(pc)
@@ -90,7 +90,7 @@ type ssPacketConn struct {
func (pc *ssPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
var packet []byte
if ma, ok := addr.(*M.Addr); ok {
packet, err = socks5.EncodeUDPPacket(serializeSocksAddr(ma.Metadata()), b)
packet, err = socks5.EncodeUDPPacket(utils.SerializeSocksAddr(ma.Metadata()), b)
} else {
packet, err = socks5.EncodeUDPPacket(socks5.ParseAddr(addr), b)
}
@@ -120,3 +120,48 @@ func (pc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
copy(b, b[len(addr):])
return n - len(addr), udpAddr, err
}
func Parse(u *url.URL) (proxy.Proxy, error) {
var (
address = u.Host
method, password string
obfsMode, obfsHost string
)
if ss := u.User.String(); ss == "" {
method = "dummy" // none cipher mode
} else if pass, set := u.User.Password(); set {
method = u.User.Username()
password = pass
} else {
data, _ := base64.RawURLEncoding.DecodeString(ss)
userInfo := strings.SplitN(string(data), ":", 2)
if len(userInfo) == 2 {
method = userInfo[0]
password = userInfo[1]
}
}
rawQuery, _ := url.QueryUnescape(u.RawQuery)
for _, s := range strings.Split(rawQuery, ";") {
data := strings.SplitN(s, "=", 2)
if len(data) != 2 {
continue
}
key := data[0]
value := data[1]
switch key {
case "obfs":
obfsMode = value
case "obfs-host":
obfsHost = value
}
}
return New(address, method, password, obfsMode, obfsHost)
}
func init() {
proxy.RegisterProtocol("ss", Parse)
}
-45
View File
@@ -1,45 +0,0 @@
package proxy
import (
"context"
"fmt"
"net"
"github.com/xjasonlyu/tun2socks/v2/dialer"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
"github.com/xjasonlyu/tun2socks/v2/transport/socks4"
)
var _ Proxy = (*Socks4)(nil)
type Socks4 struct {
*Base
userID string
}
func NewSocks4(addr, userID string) (*Socks4, error) {
return &Socks4{
Base: &Base{
addr: addr,
proto: proto.Socks4,
},
userID: userID,
}, nil
}
func (ss *Socks4) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) {
c, err = dialer.DialContext(ctx, "tcp", ss.Addr())
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", ss.Addr(), err)
}
setKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
err = socks4.ClientHandshake(c, metadata.DestinationAddress(), socks4.CmdConnect, ss.userID)
return c, err
}
+57
View File
@@ -0,0 +1,57 @@
package socks4
import (
"context"
"errors"
"fmt"
"net"
"net/url"
"github.com/xjasonlyu/tun2socks/v2/dialer"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/utils"
"github.com/xjasonlyu/tun2socks/v2/transport/socks4"
)
var _ proxy.Proxy = (*Socks4)(nil)
type Socks4 struct {
addr string
userID string
}
func New(addr, userID string) (*Socks4, error) {
return &Socks4{
addr: addr,
userID: userID,
}, nil
}
func (ss *Socks4) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) {
c, err = dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", ss.addr, err)
}
utils.SetKeepAlive(c)
defer func(c net.Conn) {
utils.SafeConnClose(c, err)
}(c)
err = socks4.ClientHandshake(c, metadata.DestinationAddress(), socks4.CmdConnect, ss.userID)
return c, err
}
func (ss *Socks4) DialUDP(*M.Metadata) (net.PacketConn, error) {
return nil, errors.ErrUnsupported
}
func Parse(u *url.URL) (proxy.Proxy, error) {
address, userID := u.Host, u.User.Username()
return New(address, userID)
}
func init() {
proxy.RegisterProtocol("socks4", Parse)
}
+33 -23
View File
@@ -1,4 +1,4 @@
package proxy
package socks5
import (
"context"
@@ -6,17 +6,19 @@ import (
"fmt"
"io"
"net"
"net/url"
"github.com/xjasonlyu/tun2socks/v2/dialer"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/utils"
"github.com/xjasonlyu/tun2socks/v2/transport/socks5"
)
var _ Proxy = (*Socks5)(nil)
var _ proxy.Proxy = (*Socks5)(nil)
type Socks5 struct {
*Base
addr string
user string
pass string
@@ -25,7 +27,7 @@ type Socks5 struct {
unix bool
}
func NewSocks5(addr, user, pass string) (*Socks5, error) {
func New(addr, user, pass string) (*Socks5, error) {
unix := len(addr) > 0 && addr[0] == '/'
// For support Linux abstract namespace
@@ -34,10 +36,7 @@ func NewSocks5(addr, user, pass string) (*Socks5, error) {
}
return &Socks5{
Base: &Base{
addr: addr,
proto: proto.Socks5,
},
addr: addr,
user: user,
pass: pass,
unix: unix,
@@ -50,14 +49,14 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *M.Metadata) (c net.
network = "unix"
}
c, err = dialer.DialContext(ctx, network, ss.Addr())
c, err = dialer.DialContext(ctx, network, ss.addr)
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", ss.Addr(), err)
return nil, fmt.Errorf("connect to %s: %w", ss.addr, err)
}
setKeepAlive(c)
utils.SetKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
utils.SafeConnClose(c, err)
}(c)
var user *socks5.User
@@ -68,7 +67,7 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *M.Metadata) (c net.
}
}
_, err = socks5.ClientHandshake(c, serializeSocksAddr(metadata), socks5.CmdConnect, user)
_, err = socks5.ClientHandshake(c, utils.SerializeSocksAddr(metadata), socks5.CmdConnect, user)
return c, err
}
@@ -77,15 +76,15 @@ func (ss *Socks5) DialUDP(*M.Metadata) (_ net.PacketConn, err error) {
return nil, fmt.Errorf("%w when unix domain socket is enabled", errors.ErrUnsupported)
}
ctx, cancel := context.WithTimeout(context.Background(), tcpConnectTimeout)
ctx, cancel := utils.WithTCPConnectTimeout(context.Background())
defer cancel()
c, err := dialer.DialContext(ctx, "tcp", ss.Addr())
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil {
err = fmt.Errorf("connect to %s: %w", ss.Addr(), err)
err = fmt.Errorf("connect to %s: %w", ss.addr, err)
return
}
setKeepAlive(c)
utils.SetKeepAlive(c)
defer func() {
if err != nil && c != nil {
@@ -135,9 +134,9 @@ func (ss *Socks5) DialUDP(*M.Metadata) (_ net.PacketConn, err error) {
}
if bindAddr.IP.IsUnspecified() { /* e.g. "0.0.0.0" or "::" */
udpAddr, err := net.ResolveUDPAddr("udp", ss.Addr())
udpAddr, err := net.ResolveUDPAddr("udp", ss.addr)
if err != nil {
return nil, fmt.Errorf("resolve udp address %s: %w", ss.Addr(), err)
return nil, fmt.Errorf("resolve udp address %s: %w", ss.addr, err)
}
bindAddr.IP = udpAddr.IP
}
@@ -155,7 +154,7 @@ type socksPacketConn struct {
func (pc *socksPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
var packet []byte
if ma, ok := addr.(*M.Addr); ok {
packet, err = socks5.EncodeUDPPacket(serializeSocksAddr(ma.Metadata()), b)
packet, err = socks5.EncodeUDPPacket(utils.SerializeSocksAddr(ma.Metadata()), b)
} else {
packet, err = socks5.EncodeUDPPacket(socks5.ParseAddr(addr), b)
}
@@ -192,6 +191,17 @@ func (pc *socksPacketConn) Close() error {
return pc.PacketConn.Close()
}
func serializeSocksAddr(m *M.Metadata) socks5.Addr {
return socks5.SerializeAddr("", m.DstIP, m.DstPort)
func Parse(u *url.URL) (proxy.Proxy, error) {
address, username := u.Host, u.User.Username()
password, _ := u.User.Password()
// Socks5 over UDS
if address == "" {
address = u.Path
}
return New(address, username, password)
}
func init() {
proxy.RegisterProtocol("socks5", Parse)
}
-25
View File
@@ -1,25 +0,0 @@
package proxy
import (
"net"
"time"
)
const (
tcpKeepAlivePeriod = 30 * time.Second
)
// setKeepAlive sets tcp keepalive option for tcp connection.
func setKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
tcp.SetKeepAlive(true)
tcp.SetKeepAlivePeriod(tcpKeepAlivePeriod)
}
}
// safeConnClose closes tcp connection safely.
func safeConnClose(c net.Conn, err error) {
if c != nil && err != nil {
c.Close()
}
}
+6 -2
View File
@@ -3,7 +3,7 @@ package tunnel
import (
"sync"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/proxy/reject"
"github.com/xjasonlyu/tun2socks/v2/tunnel/statistic"
)
@@ -13,7 +13,11 @@ var (
)
func init() {
ReplaceGlobal(New(&proxy.Base{}, statistic.DefaultManager))
t := New(
&reject.Reject{}, /* default: reject */
statistic.DefaultManager,
)
ReplaceGlobal(t)
T().ProcessAsync()
}
+1 -1
View File
@@ -29,7 +29,7 @@ func (t *Tunnel) handleTCPConn(originConn adapter.TCPConn) {
ctx, cancel := context.WithTimeout(context.Background(), tcpConnectTimeout)
defer cancel()
remoteConn, err := t.Dialer().DialContext(ctx, metadata)
remoteConn, err := t.Proxy().DialContext(ctx, metadata)
if err != nil {
log.Warnf("[TCP] dial %s: %v", metadata.DestinationAddress(), err)
return
+13 -13
View File
@@ -31,9 +31,9 @@ type Tunnel struct {
// UDP session timeout.
udpTimeout *atomic.Duration
// Internal proxy.Dialer for Tunnel.
dialerMu sync.RWMutex
dialer proxy.Dialer
// Internal proxy.Proxy for Tunnel.
proxyMu sync.RWMutex
proxy proxy.Proxy
// Where the Tunnel statistics are sent to.
manager *statistic.Manager
@@ -42,12 +42,12 @@ type Tunnel struct {
procCancel context.CancelFunc
}
func New(dialer proxy.Dialer, manager *statistic.Manager) *Tunnel {
func New(proxy proxy.Proxy, manager *statistic.Manager) *Tunnel {
return &Tunnel{
tcpQueue: make(chan adapter.TCPConn),
udpQueue: make(chan adapter.UDPConn),
udpTimeout: atomic.NewDuration(udpSessionTimeout),
dialer: dialer,
proxy: proxy,
manager: manager,
procCancel: func() { /* nop */ },
}
@@ -98,17 +98,17 @@ func (t *Tunnel) Close() {
t.procCancel()
}
func (t *Tunnel) Dialer() proxy.Dialer {
t.dialerMu.RLock()
d := t.dialer
t.dialerMu.RUnlock()
func (t *Tunnel) Proxy() proxy.Proxy {
t.proxyMu.RLock()
d := t.proxy
t.proxyMu.RUnlock()
return d
}
func (t *Tunnel) SetDialer(dialer proxy.Dialer) {
t.dialerMu.Lock()
t.dialer = dialer
t.dialerMu.Unlock()
func (t *Tunnel) SetProxy(proxy proxy.Proxy) {
t.proxyMu.Lock()
t.proxy = proxy
t.proxyMu.Unlock()
}
func (t *Tunnel) SetUDPTimeout(timeout time.Duration) {
+1 -1
View File
@@ -26,7 +26,7 @@ func (t *Tunnel) handleUDPConn(uc adapter.UDPConn) {
DstPort: id.LocalPort,
}
pc, err := t.Dialer().DialUDP(metadata)
pc, err := t.Proxy().DialUDP(metadata)
if err != nil {
log.Warnf("[UDP] dial %s: %v", metadata.DestinationAddress(), err)
return