Compare commits

..
2 Commits
Author SHA1 Message Date
xjasonlyu a9f56af25f refactor proxy/internal
update proxies

refactor

fix sth

up
2024-08-28 19:01:18 -04:00
xjasonlyu 8ef439b8d8 Refactor(proxy): make proxies registerable 2024-08-28 19:01:01 -04:00
54 changed files with 818 additions and 1018 deletions
+3 -5
View File
@@ -1,8 +1,6 @@
.github/
.github
.gitignore
.golangci.yaml
# Other
docs/
build/
tests/
build/*
docs/*
-1
View File
@@ -17,6 +17,5 @@ jobs:
with:
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'
exempt-issue-labels: 'question,bug,enhancement,help wanted'
exempt-pr-labels: 'pending,WIP,help wanted'
days-before-stale: 60
days-before-close: 7
-4
View File
@@ -41,7 +41,6 @@ UNIX_ARCH_LIST = \
linux-ppc64 \
linux-ppc64le \
linux-s390x \
linux-loong64 \
openbsd-amd64 \
openbsd-amd64-v3 \
openbsd-arm64
@@ -130,9 +129,6 @@ linux-ppc64le:
linux-s390x:
GOARCH=s390x GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
linux-loong64:
GOARCH=loong64 GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
openbsd-amd64:
GOARCH=amd64 GOOS=openbsd $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
-1
View File
@@ -48,7 +48,6 @@ Welcome and feel free to ask any questions at [Discussions](https://github.com/x
- [google/gvisor](https://github.com/google/gvisor) - Application Kernel for Containers
- [wireguard-go](https://git.zx2c4.com/wireguard-go) - Go Implementation of WireGuard
- [wintun](https://git.zx2c4.com/wintun/) - Layer 3 TUN Driver for Windows
## License
-29
View File
@@ -1,29 +0,0 @@
// Package buffer provides a pool of []byte.
package buffer
import (
"github.com/xjasonlyu/tun2socks/v2/buffer/allocator"
)
const (
// MaxSegmentSize is the largest possible UDP datagram size.
MaxSegmentSize = (1 << 16) - 1
// RelayBufferSize is the default buffer size for TCP relays.
// io.Copy default buffer size is 32 KiB, but the maximum packet
// size of vmess/shadowsocks is about 16 KiB, so define a buffer
// of 20 KiB to reduce the memory of each TCP relay.
RelayBufferSize = 20 << 10
)
var _allocator = allocator.New()
// Get gets a []byte from default allocator with most appropriate cap.
func Get(size int) []byte {
return _allocator.Get(size)
}
// Put returns a []byte to default allocator for future use.
func Put(buf []byte) error {
return _allocator.Put(buf)
}
@@ -1,29 +1,30 @@
package allocator
package pool
import (
"errors"
"math/bits"
"github.com/xjasonlyu/tun2socks/v2/internal/pool"
"sync"
)
var _allocator = NewAllocator()
// Allocator for incoming frames, optimized to prevent overwriting
// after zeroing.
type Allocator struct {
buffers []*pool.Pool[[]byte]
buffers []sync.Pool
}
// New initiates a []byte allocator for frames less than 65536 bytes,
// the waste(memory fragmentation) of space allocation is guaranteed
// to be no more than 50%.
func New() *Allocator {
// NewAllocator initiates a []byte allocator for frames less than
// 65536 bytes, the waste(memory fragmentation) of space allocation
// is guaranteed to be no more than 50%.
func NewAllocator() *Allocator {
alloc := &Allocator{}
alloc.buffers = make([]*pool.Pool[[]byte], 17) // 1B -> 64K
alloc.buffers = make([]sync.Pool, 17) // 1B -> 64K
for k := range alloc.buffers {
i := k
alloc.buffers[k] = pool.New(func() []byte {
alloc.buffers[k].New = func() any {
return make([]byte, 1<<uint32(i))
})
}
}
return alloc
}
@@ -36,10 +37,10 @@ func (alloc *Allocator) Get(size int) []byte {
b := msb(size)
if size == 1<<b {
return alloc.buffers[b].Get()[:size]
return alloc.buffers[b].Get().([]byte)[:size]
}
return alloc.buffers[b+1].Get()[:size]
return alloc.buffers[b+1].Get().([]byte)[:size]
}
// Put returns a []byte to pool for future use,
@@ -50,6 +51,8 @@ func (alloc *Allocator) Put(buf []byte) error {
return errors.New("allocator Put() incorrect buffer size")
}
//lint:ignore SA6002 ignore temporarily
//nolint
alloc.buffers[b].Put(buf)
return nil
}
@@ -1,4 +1,4 @@
package allocator
package pool
import (
"math/rand"
@@ -8,7 +8,7 @@ import (
)
func TestAllocGet(t *testing.T) {
alloc := New()
alloc := NewAllocator()
assert.Nil(t, alloc.Get(0))
assert.Equal(t, 1, len(alloc.Get(1)))
assert.Equal(t, 2, len(alloc.Get(2)))
@@ -23,7 +23,7 @@ func TestAllocGet(t *testing.T) {
}
func TestAllocPut(t *testing.T) {
alloc := New()
alloc := NewAllocator()
assert.NotNil(t, alloc.Put(nil), "put nil misbehavior")
assert.NotNil(t, alloc.Put(make([]byte, 3)), "put elem:3 []bytes misbehavior")
assert.Nil(t, alloc.Put(make([]byte, 4)), "put elem:4 []bytes misbehavior")
@@ -33,7 +33,7 @@ func TestAllocPut(t *testing.T) {
}
func TestAllocPutThenGet(t *testing.T) {
alloc := New()
alloc := NewAllocator()
data := alloc.Get(4)
_ = alloc.Put(data)
newData := alloc.Get(4)
+17
View File
@@ -0,0 +1,17 @@
package pool
import (
"bytes"
"sync"
)
var bufferPool = sync.Pool{New: func() any { return &bytes.Buffer{} }}
func GetBuffer() *bytes.Buffer {
return bufferPool.Get().(*bytes.Buffer)
}
func PutBuffer(buf *bytes.Buffer) {
buf.Reset()
bufferPool.Put(buf)
}
+23
View File
@@ -0,0 +1,23 @@
// Package pool provides a pool of []byte.
package pool
const (
// MaxSegmentSize is the largest possible UDP datagram size.
MaxSegmentSize = (1 << 16) - 1
// RelayBufferSize is a buffer of 20 KiB to reduce the memory
// of each TCP relay as io.Copy default buffer size is 32 KiB,
// but the maximum packet size of vmess/shadowsocks is about
// 16 KiB, so define .
RelayBufferSize = 20 << 10
)
// Get gets a []byte from default allocator with most appropriate cap.
func Get(size int) []byte {
return _allocator.Get(size)
}
// Put returns a []byte to default allocator for future use.
func Put(buf []byte) error {
return _allocator.Put(buf)
}
+1
View File
@@ -12,6 +12,7 @@ import (
func open(fd int, mtu uint32, offset int) (device.Device, error) {
f := &FD{fd: fd, mtu: mtu}
ep, err := iobased.New(os.NewFile(uintptr(fd), f.Name()), mtu, offset)
if err != nil {
return nil, fmt.Errorf("create endpoint: %w", err)
+11 -11
View File
@@ -2,7 +2,7 @@ package core
import (
"fmt"
"net/netip"
"net"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
@@ -61,7 +61,7 @@ func withSpoofing(nicID tcpip.NICID, v bool) option.Option {
}
// withMulticastGroups adds a NIC to the given multicast groups.
func withMulticastGroups(nicID tcpip.NICID, multicastGroups []netip.Addr) option.Option {
func withMulticastGroups(nicID tcpip.NICID, multicastGroups []net.IP) option.Option {
return func(s *stack.Stack) error {
if len(multicastGroups) == 0 {
return nil
@@ -103,15 +103,15 @@ func withMulticastGroups(nicID tcpip.NICID, multicastGroups []netip.Addr) option
stack.AddressProperties{PEB: stack.CanBePrimaryEndpoint},
)
for _, multicastGroup := range multicastGroups {
var err tcpip.Error
switch {
case multicastGroup.Is4():
err = s.JoinGroup(ipv4.ProtocolNumber, nicID, tcpip.AddrFrom4(multicastGroup.As4()))
case multicastGroup.Is6():
err = s.JoinGroup(ipv6.ProtocolNumber, nicID, tcpip.AddrFrom16(multicastGroup.As16()))
}
if err != nil {
return fmt.Errorf("join multicast group: %s", err)
if ip := multicastGroup.To4(); ip != nil {
if err := s.JoinGroup(ipv4.ProtocolNumber, nicID, tcpip.AddrFrom4Slice(ip)); err != nil {
return fmt.Errorf("join multicast group: %s", err)
}
} else {
ip := multicastGroup.To16()
if err := s.JoinGroup(ipv6.ProtocolNumber, nicID, tcpip.AddrFrom16Slice(ip)); err != nil {
return fmt.Errorf("join multicast group: %s", err)
}
}
}
return nil
+2 -2
View File
@@ -1,7 +1,7 @@
package core
import (
"net/netip"
"net"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
@@ -26,7 +26,7 @@ type Config struct {
// MulticastGroups is used by internal stack to add
// nic to given groups.
MulticastGroups []netip.Addr
MulticastGroups []net.IP
// Options are supplement options to apply settings
// for the internal stack.
+7 -7
View File
@@ -3,7 +3,6 @@ package engine
import (
"errors"
"net"
"net/netip"
"os/exec"
"sync"
"time"
@@ -17,6 +16,7 @@ import (
"github.com/xjasonlyu/tun2socks/v2/core/device"
"github.com/xjasonlyu/tun2socks/v2/core/option"
"github.com/xjasonlyu/tun2socks/v2/dialer"
"github.com/xjasonlyu/tun2socks/v2/engine/mirror"
"github.com/xjasonlyu/tun2socks/v2/log"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/restapi"
@@ -130,7 +130,7 @@ func general(k *Key) error {
if k.UDPTimeout < time.Second {
return errors.New("invalid udp timeout value")
}
tunnel.T().SetUDPTimeout(k.UDPTimeout)
tunnel.SetUDPTimeout(k.UDPTimeout)
}
return nil
}
@@ -189,16 +189,16 @@ func netstack(k *Key) (err error) {
}
}()
if _defaultProxy, err = parseProxy(k.Proxy); err != nil {
if _defaultProxy, err = proxy.ParseFromURL(k.Proxy); err != nil {
return
}
tunnel.T().SetDialer(_defaultProxy)
proxy.DefaultProxy = _defaultProxy
if _defaultDevice, err = parseDevice(k.Device, uint32(k.MTU)); err != nil {
return
}
var multicastGroups []netip.Addr
var multicastGroups []net.IP
if multicastGroups, err = parseMulticastGroups(k.MulticastGroups); err != nil {
return err
}
@@ -226,7 +226,7 @@ func netstack(k *Key) (err error) {
if _defaultStack, err = core.CreateStack(&core.Config{
LinkEndpoint: _defaultDevice,
TransportHandler: tunnel.T(),
TransportHandler: &mirror.Tunnel{},
MulticastGroups: multicastGroups,
Options: opts,
}); err != nil {
@@ -236,7 +236,7 @@ func netstack(k *Key) (err error) {
log.Infof(
"[STACK] %s://%s <-> %s://%s",
_defaultDevice.Type(), _defaultDevice.Name(),
_defaultProxy.Proto(), _defaultProxy.Addr(),
_defaultProxy.Protocol(), _defaultProxy.Address(),
)
return nil
}
+18
View File
@@ -0,0 +1,18 @@
package mirror
import (
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
"github.com/xjasonlyu/tun2socks/v2/tunnel"
)
var _ adapter.TransportHandler = (*Tunnel)(nil)
type Tunnel struct{}
func (*Tunnel) HandleTCP(conn adapter.TCPConn) {
tunnel.TCPIn() <- conn
}
func (*Tunnel) HandleUDP(conn adapter.UDPConn) {
tunnel.UDPIn() <- conn
}
+11 -132
View File
@@ -1,21 +1,14 @@
package engine
import (
"encoding/base64"
"fmt"
"net"
"net/netip"
"net/url"
"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"
)
func parseRestAPI(s string) (*url.URL, error) {
@@ -68,137 +61,23 @@ func parseDevice(s string, mtu uint32) (device.Device, error) {
}
func parseFD(u *url.URL, mtu uint32) (device.Device, error) {
offset := 0
// fd offset in ios
// https://stackoverflow.com/questions/69260852/ios-network-extension-packet-parsing/69487795#69487795
if runtime.GOOS == "ios" {
offset = 4
}
return fdbased.Open(u.Host, mtu, offset)
return fdbased.Open(u.Host, mtu, 0)
}
func parseProxy(s string) (proxy.Proxy, error) {
if !strings.Contains(s, "://") {
s = fmt.Sprintf("%s://%s", proto.Socks5 /* default protocol */, 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 {
func parseMulticastGroups(s string) (multicastGroups []net.IP, _ error) {
ipStrings := strings.Split(s, ",")
for _, ipString := range ipStrings {
if strings.TrimSpace(ipString) == "" {
continue
}
key := data[0]
value := data[1]
switch key {
case "obfs":
obfsMode = value
case "obfs-host":
obfsHost = value
ip := net.ParseIP(ipString)
if ip == nil {
return nil, fmt.Errorf("invalid IP format: %s", ipString)
}
}
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)
}
func parseMulticastGroups(s string) (multicastGroups []netip.Addr, _ error) {
for _, ip := range strings.Split(s, ",") {
if ip = strings.TrimSpace(ip); ip == "" {
continue
if !ip.IsMulticast() {
return nil, fmt.Errorf("invalid multicast IP address: %s", ipString)
}
addr, err := netip.ParseAddr(ip)
if err != nil {
return nil, err
}
if !addr.IsMulticast() {
return nil, fmt.Errorf("invalid multicast IP: %s", addr)
}
multicastGroups = append(multicastGroups, addr)
multicastGroups = append(multicastGroups, ip)
}
return
}
+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"
)
+10 -8
View File
@@ -1,6 +1,8 @@
module github.com/xjasonlyu/tun2socks/v2
go 1.23.1
go 1.22.0
toolchain go1.22.5
require (
github.com/docker/go-units v0.5.0
@@ -14,22 +16,22 @@ require (
github.com/gorilla/websocket v1.5.3
github.com/stretchr/testify v1.9.0
go.uber.org/atomic v1.11.0
go.uber.org/automaxprocs v1.6.0
go.uber.org/automaxprocs v1.5.3
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.31.0
golang.org/x/sys v0.28.0
golang.org/x/time v0.8.0
golang.org/x/crypto v0.25.0
golang.org/x/sys v0.22.0
golang.org/x/time v0.5.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
gopkg.in/yaml.v3 v3.0.1
gvisor.dev/gvisor v0.0.0-20241108012608-5901531b85ba
gvisor.dev/gvisor v0.0.0-20240713103206-39d6c232e61d
)
require (
github.com/ajg/form v1.5.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.31.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
)
+14 -14
View File
@@ -12,8 +12,8 @@ github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-gost/relay v0.5.0 h1:JG1tgy/KWiVXS0ukuVXvbM0kbYuJTWxYpJ5JwzsCf/c=
github.com/go-gost/relay v0.5.0/go.mod h1:lcX+23LCQ3khIeASBo+tJ/WbwXFO32/N5YN6ucuYTG8=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -34,22 +34,22 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
@@ -59,5 +59,5 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20241108012608-5901531b85ba h1:ZXzGZ6Md+Vspe9x45C6QlpCdpwYC6nHFtWIfZzTqB0k=
gvisor.dev/gvisor v0.0.0-20241108012608-5901531b85ba/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM=
gvisor.dev/gvisor v0.0.0-20240713103206-39d6c232e61d h1:dFTIljP/5ReqgM7nMR4DauApFatUaSP8r9btX0sd8a8=
gvisor.dev/gvisor v0.0.0-20240713103206-39d6c232e61d/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU=
-38
View File
@@ -1,38 +0,0 @@
// Package pool provides internal pool utilities.
package pool
import (
"sync"
)
// A Pool is a generic wrapper around [sync.Pool] to provide strongly-typed
// object pooling.
//
// Note that SA6002 (ref: https://staticcheck.io/docs/checks/#SA6002) will
// not be detected, so all internal pool use must take care to only store
// pointer types.
type Pool[T any] struct {
pool sync.Pool
}
// New returns a new [Pool] for T, and will use fn to construct new Ts when
// the pool is empty.
func New[T any](fn func() T) *Pool[T] {
return &Pool[T]{
pool: sync.Pool{
New: func() any {
return fn()
},
},
}
}
// Get gets a T from the pool, or creates a new one if the pool is empty.
func (p *Pool[T]) Get() T {
return p.pool.Get().(T)
}
// Put returns x into the pool.
func (p *Pool[T]) Put(x T) {
p.pool.Put(x)
}
-85
View File
@@ -1,85 +0,0 @@
package pool
import (
"runtime/debug"
"sync"
"testing"
"github.com/stretchr/testify/require"
)
type pooledValue[T any] struct {
value T
}
func TestNew(t *testing.T) {
// Disable GC to avoid the victim cache during the test.
defer debug.SetGCPercent(debug.SetGCPercent(-1))
p := New(func() *pooledValue[string] {
return &pooledValue[string]{
value: "new",
}
})
// Probabilistically, 75% of sync.Pool.Put calls will succeed when -race
// is enabled (see ref below); attempt to make this quasi-deterministic by
// brute force (i.e., put significantly more objects in the pool than we
// will need for the test) in order to avoid testing without race enabled.
//
// ref: https://cs.opensource.google/go/go/+/refs/tags/go1.20.2:src/sync/pool.go;l=100-103
for i := 0; i < 1_000; i++ {
p.Put(&pooledValue[string]{
value: t.Name(),
})
}
// Ensure that we always get the expected value. Note that this must only
// run a fraction of the number of times that Put is called above.
for i := 0; i < 10; i++ {
func() {
x := p.Get()
defer p.Put(x)
require.Equal(t, t.Name(), x.value)
}()
}
// Depool all objects that might be in the pool to ensure that it's empty.
for i := 0; i < 1_000; i++ {
p.Get()
}
// Now that the pool is empty, it should use the value specified in the
// underlying sync.Pool.New func.
require.Equal(t, "new", p.Get().value)
}
func TestNew_Race(t *testing.T) {
p := New(func() *pooledValue[int] {
return &pooledValue[int]{
value: -1,
}
})
var wg sync.WaitGroup
defer wg.Wait()
// Run a number of goroutines that read and write pool object fields to
// tease out races.
for i := 0; i < 1_000; i++ {
i := i
wg.Add(1)
go func() {
defer wg.Done()
x := p.Get()
defer p.Put(x)
// Must both read and write the field.
if n := x.value; n >= -1 {
x.value = i
}
}()
}
}
+20 -22
View File
@@ -2,34 +2,26 @@ package metadata
import (
"net"
"net/netip"
"strconv"
)
// Metadata contains metadata of transport protocol sessions.
type Metadata struct {
Network Network `json:"network"`
SrcIP netip.Addr `json:"sourceIP"`
MidIP netip.Addr `json:"dialerIP"`
DstIP netip.Addr `json:"destinationIP"`
SrcPort uint16 `json:"sourcePort"`
MidPort uint16 `json:"dialerPort"`
DstPort uint16 `json:"destinationPort"`
}
func (m *Metadata) DestinationAddrPort() netip.AddrPort {
return netip.AddrPortFrom(m.DstIP, m.DstPort)
Network Network `json:"network"`
SrcIP net.IP `json:"sourceIP"`
MidIP net.IP `json:"dialerIP"`
DstIP net.IP `json:"destinationIP"`
SrcPort uint16 `json:"sourcePort"`
MidPort uint16 `json:"dialerPort"`
DstPort uint16 `json:"destinationPort"`
}
func (m *Metadata) DestinationAddress() string {
return m.DestinationAddrPort().String()
}
func (m *Metadata) SourceAddrPort() netip.AddrPort {
return netip.AddrPortFrom(m.SrcIP, m.SrcPort)
return net.JoinHostPort(m.DstIP.String(), strconv.FormatUint(uint64(m.DstPort), 10))
}
func (m *Metadata) SourceAddress() string {
return m.SourceAddrPort().String()
return net.JoinHostPort(m.SrcIP.String(), strconv.FormatUint(uint64(m.SrcPort), 10))
}
func (m *Metadata) Addr() net.Addr {
@@ -37,17 +29,23 @@ func (m *Metadata) Addr() net.Addr {
}
func (m *Metadata) TCPAddr() *net.TCPAddr {
if m.Network != TCP || !m.DstIP.IsValid() {
if m.Network != TCP || m.DstIP == nil {
return nil
}
return net.TCPAddrFromAddrPort(m.DestinationAddrPort())
return &net.TCPAddr{
IP: m.DstIP,
Port: int(m.DstPort),
}
}
func (m *Metadata) UDPAddr() *net.UDPAddr {
if m.Network != UDP || !m.DstIP.IsValid() {
if m.Network != UDP || m.DstIP == nil {
return nil
}
return net.UDPAddrFromAddrPort(m.DestinationAddrPort())
return &net.UDPAddr{
IP: m.DstIP,
Port: int(m.DstPort),
}
}
// Addr implements the net.Addr interface.
-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
}
+17 -14
View File
@@ -1,34 +1,33 @@
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"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/base"
)
var _ Proxy = (*Direct)(nil)
var _ proxy.Proxy = (*Direct)(nil)
type Direct struct {
*Base
}
const protocol = "direct"
func NewDirect() *Direct {
return &Direct{
Base: &Base{
proto: proto.Direct,
},
}
}
type Direct struct{ *base.Base }
func New() *Direct { return &Direct{base.New("", protocol)} }
func Parse(*url.URL) (proxy.Proxy, error) { return New(), 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)
internal.SetKeepAlive(c)
return c, nil
}
@@ -55,3 +54,7 @@ func (pc *directPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
}
return pc.PacketConn.WriteTo(b, udpAddr)
}
func init() {
proxy.RegisterProtocol(protocol, Parse)
}
+25 -12
View File
@@ -1,4 +1,4 @@
package proxy
package http
import (
"bufio"
@@ -13,36 +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"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/base"
)
var _ proxy.Proxy = (*HTTP)(nil)
const protocol = "http"
type HTTP struct {
*Base
*base.Base
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,
},
Base: base.New(addr, protocol),
user: user,
pass: pass,
}, nil
}
func Parse(proxyURL *url.URL) (proxy.Proxy, error) {
address, username := proxyURL.Host, proxyURL.User.Username()
password, _ := proxyURL.User.Password()
return New(address, username, password)
}
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.Address())
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", h.Addr(), err)
return nil, fmt.Errorf("connect to %s: %w", h.Address(), err)
}
setKeepAlive(c)
internal.SetKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
internal.SafeConnClose(c, err)
}(c)
err = h.shakeHand(metadata, c)
@@ -98,3 +107,7 @@ func basicAuth(username, password string) string {
auth := username + ":" + password
return base64.StdEncoding.EncodeToString([]byte(auth))
}
func init() {
proxy.RegisterProtocol(protocol, Parse)
}
+44
View File
@@ -0,0 +1,44 @@
package base
import (
"context"
"errors"
"fmt"
"net"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy"
)
var _ proxy.Proxy = (*Base)(nil)
type Base struct {
address, protocol string
}
func New(address, protocol string) *Base {
return &Base{
address: address,
protocol: protocol,
}
}
func (b *Base) Address() string {
return b.address
}
func (b *Base) Protocol() string {
return b.protocol
}
func (b *Base) String() string {
return fmt.Sprintf("%s://%s", b.protocol, b.address)
}
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
}
+31
View File
@@ -0,0 +1,31 @@
package internal
import (
"net"
"time"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/transport/socks5"
)
const tcpKeepAlivePeriod = 30 * time.Second
// SetKeepAlive sets the tcp keepalive option for the tcp connection.
func SetKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
tcp.SetKeepAlive(true)
tcp.SetKeepAlivePeriod(tcpKeepAlivePeriod)
}
}
// SafeConnClose closes the given tcp connection safely.
func SafeConnClose(c net.Conn, err error) {
if c != nil && err != nil {
c.Close()
}
}
// SerializeSocksAddr serializes *metadata.Metadata to socks5.Addr.
func SerializeSocksAddr(m *M.Metadata) socks5.Addr {
return socks5.SerializeAddr("", m.DstIP, m.DstPort)
}
-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)
}
}
+72
View File
@@ -0,0 +1,72 @@
package proxy
import (
"errors"
"fmt"
"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) {
if proxyURL == nil {
return nil, errors.New("proxy: nil url")
}
if proxyURL.Scheme == "" {
return nil, errors.New("proxy: protocol not specified")
}
p := pick(proxyURL.Scheme)
if p.parse == nil {
return nil, fmt.Errorf("%w: %s", ErrProtocol, proxyURL.Scheme)
}
return p.parse(proxyURL)
}
// ParseFromURL parses url string that holds the proxy info into Proxy.
func ParseFromURL(proxy string) (Proxy, error) {
proxyURL, err := url.Parse(proxy)
if err != nil {
return nil, err
}
return Parse(proxyURL)
}
+23 -22
View File
@@ -7,44 +7,45 @@ import (
"time"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
)
const (
tcpConnectTimeout = 5 * time.Second
TCPConnectTimeout = 5 * time.Second
)
var _defaultDialer Dialer = &Base{}
// DefaultProxy is the default [Proxy] and is used by [Dial], [DialContext], and [DialUDP].
var DefaultProxy Proxy = nil
type Dialer interface {
type Proxy interface {
// Address returns the address of the proxy.
Address() string
// Protocol returns the protocol of the proxy.
Protocol() string
// String returns the string representation of the proxy.
String() string
// DialContext is used to dial TCP networks with context.
DialContext(context.Context, *M.Metadata) (net.Conn, error)
// DialUDP is used to to dial/listen UDP networks.
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.
// Dial uses the DefaultProxy to dial TCP.
func Dial(metadata *M.Metadata) (net.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpConnectTimeout)
ctx, cancel := context.WithTimeout(context.Background(), TCPConnectTimeout)
defer cancel()
return _defaultDialer.DialContext(ctx, metadata)
return DialContext(ctx, metadata)
}
// DialContext uses default Dialer to dial TCP with context.
// DialContext uses the DefaultProxy to dial TCP with context.
func DialContext(ctx context.Context, metadata *M.Metadata) (net.Conn, error) {
return _defaultDialer.DialContext(ctx, metadata)
return DefaultProxy.DialContext(ctx, metadata)
}
// DialUDP uses default Dialer to dial UDP.
// DialUDP uses the DefaultProxy to dial UDP.
func DialUDP(metadata *M.Metadata) (net.PacketConn, error) {
return _defaultDialer.DialUDP(metadata)
return DefaultProxy.DialUDP(metadata)
}
+17 -19
View File
@@ -1,36 +1,30 @@
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"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/base"
)
var _ Proxy = (*Reject)(nil)
var _ proxy.Proxy = (*Reject)(nil)
type Reject struct {
*Base
}
const protocol = "reject"
func NewReject() *Reject {
return &Reject{
Base: &Base{
proto: proto.Reject,
},
}
}
type Reject struct{ *base.Base }
func (r *Reject) DialContext(context.Context, *M.Metadata) (net.Conn, error) {
return &nopConn{}, nil
}
func New() *Reject { return &Reject{base.New("", protocol)} }
func (r *Reject) DialUDP(*M.Metadata) (net.PacketConn, error) {
return &nopPacketConn{}, nil
}
func Parse(*url.URL) (proxy.Proxy, error) { return New(), nil }
func (r *Reject) DialContext(context.Context, *M.Metadata) (net.Conn, error) { return &nopConn{}, nil }
func (r *Reject) DialUDP(*M.Metadata) (net.PacketConn, error) { return &nopPacketConn{}, nil }
type nopConn struct{}
@@ -52,3 +46,7 @@ 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 init() {
proxy.RegisterProtocol(protocol, Parse)
}
+39 -18
View File
@@ -1,4 +1,4 @@
package proxy
package relay
import (
"bytes"
@@ -9,20 +9,26 @@ 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/common/pool"
"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"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/base"
)
var _ Proxy = (*Relay)(nil)
var _ proxy.Proxy = (*Relay)(nil)
const protocol = "relay"
type Relay struct {
*Base
*base.Base
user string
pass string
@@ -30,24 +36,35 @@ type Relay struct {
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,
},
Base: base.New(addr, protocol),
user: user,
pass: pass,
noDelay: noDelay,
}, nil
}
func Parse(proxyURL *url.URL) (proxy.Proxy, error) {
address, username := proxyURL.Host, proxyURL.User.Username()
password, _ := proxyURL.User.Password()
opts := struct {
NoDelay bool
}{}
if err := schema.NewDecoder().Decode(&opts, proxyURL.Query()); err != nil {
return nil, err
}
return New(address, username, password, opts.NoDelay)
}
func (rl *Relay) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) {
return rl.dialContext(ctx, metadata)
}
func (rl *Relay) DialUDP(metadata *M.Metadata) (net.PacketConn, error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpConnectTimeout)
ctx, cancel := context.WithTimeout(context.Background(), proxy.TCPConnectTimeout)
defer cancel()
return rl.dialContext(ctx, metadata)
@@ -56,14 +73,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.Address())
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", rl.Addr(), err)
return nil, fmt.Errorf("connect to %s: %w", rl.Address(), err)
}
setKeepAlive(c)
internal.SetKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
internal.SafeConnClose(c, err)
}(c)
req := relay.Request{
@@ -169,8 +186,8 @@ func (rc *relayConn) Read(b []byte) (n int, err error) {
return io.ReadFull(rc.Conn, b[:dLen])
}
buf := buffer.Get(dLen)
defer buffer.Put(buf)
buf := pool.Get(dLen)
defer pool.Put(buf)
_, err = io.ReadFull(rc.Conn, buf)
n = copy(b, buf)
@@ -243,10 +260,14 @@ func serializeRelayAddr(m *M.Metadata) *relay.AddrFeature {
Host: m.DstIP.String(),
Port: m.DstPort,
}
if m.DstIP.Is4() {
if m.DstIP.To4() != nil {
af.AType = relay.AddrIPv4
} else {
af.AType = relay.AddrIPv6
}
return af
}
func init() {
proxy.RegisterProtocol(protocol, Parse)
}
@@ -1,23 +1,30 @@
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"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/base"
"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)
const protocol = "ss"
type Shadowsocks struct {
*Base
*base.Base
cipher core.Cipher
@@ -25,44 +32,82 @@ type Shadowsocks struct {
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,
},
Base: base.New(addr, protocol),
cipher: cipher,
obfsMode: obfsMode,
obfsHost: obfsHost,
}, nil
}
func (ss *Shadowsocks) 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)
func Parse(proxyURL *url.URL) (proxy.Proxy, error) {
var (
address = proxyURL.Host
method, password string
obfsMode, obfsHost string
)
if ss := proxyURL.User.String(); ss == "" {
method = "dummy" // none cipher mode
} else if pass, set := proxyURL.User.Password(); set {
method = proxyURL.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]
}
}
setKeepAlive(c)
rawQuery, _ := url.QueryUnescape(proxyURL.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 (ss *Shadowsocks) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) {
c, err = dialer.DialContext(ctx, "tcp", ss.Address())
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", ss.Address(), err)
}
internal.SetKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
internal.SafeConnClose(c, err)
}(c)
switch ss.obfsMode {
case "tls":
c = obfs.NewTLSObfs(c, ss.obfsHost)
case "http":
_, port, _ := net.SplitHostPort(ss.addr)
_, port, _ := net.SplitHostPort(ss.Address())
c = obfs.NewHTTPObfs(c, ss.obfsHost, port)
}
c = ss.cipher.StreamConn(c)
_, err = c.Write(serializeSocksAddr(metadata))
_, err = c.Write(internal.SerializeSocksAddr(metadata))
return
}
@@ -72,9 +117,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.Address())
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.Address(), err)
}
pc = ss.cipher.PacketConn(pc)
@@ -90,7 +135,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(internal.SerializeSocksAddr(ma.Metadata()), b)
} else {
packet, err = socks5.EncodeUDPPacket(socks5.ParseAddr(addr), b)
}
@@ -120,3 +165,7 @@ func (pc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
copy(b, b[len(addr):])
return n - len(addr), udpAddr, err
}
func init() {
proxy.RegisterProtocol(protocol, 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
}
+56
View File
@@ -0,0 +1,56 @@
package socks4
import (
"context"
"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"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/base"
"github.com/xjasonlyu/tun2socks/v2/transport/socks4"
)
var _ proxy.Proxy = (*Socks4)(nil)
const protocol = "socks4"
type Socks4 struct {
*base.Base
userID string
}
func New(addr, userID string) (*Socks4, error) {
return &Socks4{
Base: base.New(addr, protocol),
userID: userID,
}, nil
}
func Parse(proxyURL *url.URL) (proxy.Proxy, error) {
address, userID := proxyURL.Host, proxyURL.User.Username()
return New(address, userID)
}
func (ss *Socks4) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) {
c, err = dialer.DialContext(ctx, "tcp", ss.Address())
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", ss.Address(), err)
}
internal.SetKeepAlive(c)
defer func(c net.Conn) {
internal.SafeConnClose(c, err)
}(c)
err = socks4.ClientHandshake(c, metadata.DestinationAddress(), socks4.CmdConnect, ss.userID)
return
}
func init() {
proxy.RegisterProtocol(protocol, Parse)
}
+36 -23
View File
@@ -1,4 +1,4 @@
package proxy
package socks5
import (
"context"
@@ -6,17 +6,22 @@ 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"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/base"
"github.com/xjasonlyu/tun2socks/v2/transport/socks5"
)
var _ Proxy = (*Socks5)(nil)
var _ proxy.Proxy = (*Socks5)(nil)
const protocol = "socks5"
type Socks5 struct {
*Base
*base.Base
user string
pass string
@@ -25,32 +30,40 @@ type Socks5 struct {
unix bool
}
func NewSocks5(addr, user, pass string) (*Socks5, error) {
func New(addr, user, pass string) (*Socks5, error) {
return &Socks5{
Base: &Base{
addr: addr,
proto: proto.Socks5,
},
Base: base.New(addr, protocol),
user: user,
pass: pass,
unix: len(addr) > 0 && addr[0] == '/',
}, nil
}
func Parse(proxyURL *url.URL) (proxy.Proxy, error) {
address, username := proxyURL.Host, proxyURL.User.Username()
password, _ := proxyURL.User.Password()
// Socks5 over UDS
if address == "" {
address = proxyURL.Path
}
return New(address, username, password)
}
func (ss *Socks5) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) {
network := "tcp"
if ss.unix {
network = "unix"
}
c, err = dialer.DialContext(ctx, network, ss.Addr())
c, err = dialer.DialContext(ctx, network, ss.Address())
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", ss.Addr(), err)
return nil, fmt.Errorf("connect to %s: %w", ss.Address(), err)
}
setKeepAlive(c)
internal.SetKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
internal.SafeConnClose(c, err)
}(c)
var user *socks5.User
@@ -61,7 +74,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, internal.SerializeSocksAddr(metadata), socks5.CmdConnect, user)
return
}
@@ -70,15 +83,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 := context.WithTimeout(context.Background(), proxy.TCPConnectTimeout)
defer cancel()
c, err := dialer.DialContext(ctx, "tcp", ss.Addr())
c, err := dialer.DialContext(ctx, "tcp", ss.Address())
if err != nil {
err = fmt.Errorf("connect to %s: %w", ss.Addr(), err)
err = fmt.Errorf("connect to %s: %w", ss.Address(), err)
return
}
setKeepAlive(c)
internal.SetKeepAlive(c)
defer func() {
if err != nil && c != nil {
@@ -128,9 +141,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.Address())
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.Address(), err)
}
bindAddr.IP = udpAddr.IP
}
@@ -148,7 +161,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(internal.SerializeSocksAddr(ma.Metadata()), b)
} else {
packet, err = socks5.EncodeUDPPacket(socks5.ParseAddr(addr), b)
}
@@ -185,6 +198,6 @@ func (pc *socksPacketConn) Close() error {
return pc.PacketConn.Close()
}
func serializeSocksAddr(m *M.Metadata) socks5.Addr {
return socks5.SerializeAddr("", m.DstIP, m.DstPort)
func init() {
proxy.RegisterProtocol(protocol, Parse)
}
+45
View File
@@ -0,0 +1,45 @@
package proxy
import (
"errors"
"net/url"
"strings"
)
// URL is the universal representation of the proxy configuration.
type URL url.URL
func (u *URL) Protocol() string {
return u.Scheme
}
func (u *URL) Address() string {
return u.Host
}
func (u *URL) String() string {
return (&url.URL{
Scheme: u.Scheme,
Host: u.Host,
Path: strings.TrimRight(u.Path, "/"),
}).String()
}
func ParseURL(rawURL string) (*URL, error) {
proxyURL, err := url.Parse(rawURL)
if err != nil {
return nil, err
}
if proxyURL.Scheme == "" {
return nil, errors.New("proxy: protocol not specified")
}
return (*URL)(proxyURL), nil
}
func MustParseURL(rawURL string) *URL {
u, err := ParseURL(rawURL)
if err != nil {
panic(err)
}
return u
}
+31
View File
@@ -0,0 +1,31 @@
package proxy
import (
"testing"
"github.com/stretchr/testify/suite"
)
type URLTestSuite struct {
suite.Suite
}
func (s *URLTestSuite) TestAddress() {
tests := []struct {
u *URL
expected string
}{
{
MustParseURL("http://example.com/"),
"http://example.com",
},
}
for _, tt := range tests {
s.Assert().Equal(tt.expected, tt.u.String())
}
}
func TestURLTestSuite(t *testing.T) {
suite.Run(t, new(URLTestSuite))
}
-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()
}
}
@@ -1,23 +0,0 @@
package bufferpool
import (
"bytes"
"github.com/xjasonlyu/tun2socks/v2/internal/pool"
)
const _size = 1024 // by default, create 1 KiB buffers
var _pool = pool.New(func() *bytes.Buffer {
return bytes.NewBuffer(make([]byte, 0, _size))
})
func Get() *bytes.Buffer {
buf := _pool.Get()
buf.Reset()
return buf
}
func Put(b *bytes.Buffer) {
_pool.Put(b)
}
+3 -3
View File
@@ -6,7 +6,7 @@ import (
"io"
"net"
"github.com/xjasonlyu/tun2socks/v2/buffer"
"github.com/xjasonlyu/tun2socks/v2/common/pool"
)
// ErrShortPacket means that the packet is too short for a valid encrypted packet.
@@ -70,8 +70,8 @@ func NewPacketConn(c net.PacketConn, ciph Cipher) *PacketConn {
// WriteTo encrypts b and write to addr using the embedded PacketConn.
func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
buf := buffer.Get(maxPacketSize)
defer buffer.Put(buf)
buf := pool.Get(maxPacketSize)
defer pool.Put(buf)
buf, err := Pack(buf, b, c)
if err != nil {
return 0, err
+9 -9
View File
@@ -7,7 +7,7 @@ import (
"io"
"net"
"github.com/xjasonlyu/tun2socks/v2/buffer"
"github.com/xjasonlyu/tun2socks/v2/common/pool"
)
const (
@@ -29,8 +29,8 @@ func NewWriter(w io.Writer, aead cipher.AEAD) *Writer { return &Writer{Writer: w
// Write encrypts p and writes to the embedded io.Writer.
func (w *Writer) Write(p []byte) (n int, err error) {
buf := buffer.Get(bufSize)
defer buffer.Put(buf)
buf := pool.Get(bufSize)
defer pool.Put(buf)
nonce := w.nonce[:w.NonceSize()]
tag := w.Overhead()
off := 2 + tag
@@ -65,8 +65,8 @@ func (w *Writer) Write(p []byte) (n int, err error) {
// writes to the embedded io.Writer. Returns number of bytes read from r and
// any error encountered.
func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) {
buf := buffer.Get(bufSize)
defer buffer.Put(buf)
buf := pool.Get(bufSize)
defer pool.Put(buf)
nonce := w.nonce[:w.NonceSize()]
tag := w.Overhead()
off := 2 + tag
@@ -142,7 +142,7 @@ func (r *Reader) Read(p []byte) (int, error) {
if len(p) >= payloadSizeMask+r.Overhead() {
return r.read(p)
}
b := buffer.Get(bufSize)
b := pool.Get(bufSize)
n, err := r.read(b)
if err != nil {
return 0, err
@@ -154,7 +154,7 @@ func (r *Reader) Read(p []byte) (int, error) {
n := copy(p, r.buf[r.off:])
r.off += n
if r.off == len(r.buf) {
buffer.Put(r.buf[:cap(r.buf)])
pool.Put(r.buf[:cap(r.buf)])
r.buf = nil
}
return n, nil
@@ -165,7 +165,7 @@ func (r *Reader) Read(p []byte) (int, error) {
// bytes written to w and any error encountered.
func (r *Reader) WriteTo(w io.Writer) (n int64, err error) {
if r.buf == nil {
r.buf = buffer.Get(bufSize)
r.buf = pool.Get(bufSize)
r.off = len(r.buf)
}
@@ -176,7 +176,7 @@ func (r *Reader) WriteTo(w io.Writer) (n int64, err error) {
n += int64(nw)
if ew != nil {
if r.off == len(r.buf) {
buffer.Put(r.buf[:cap(r.buf)])
pool.Put(r.buf[:cap(r.buf)])
r.buf = nil
}
err = ew
+3 -3
View File
@@ -6,7 +6,7 @@ import (
"io"
"net"
"github.com/xjasonlyu/tun2socks/v2/buffer"
"github.com/xjasonlyu/tun2socks/v2/common/pool"
)
// ErrShortPacket means the packet is too short to be a valid encrypted packet.
@@ -55,8 +55,8 @@ func NewPacketConn(c net.PacketConn, ciph Cipher) *PacketConn {
const maxPacketSize = 64 * 1024
func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
buf := buffer.Get(maxPacketSize)
defer buffer.Put(buf)
buf := pool.Get(maxPacketSize)
defer pool.Put(buf)
buf, err := Pack(buf, b, c.Cipher)
if err != nil {
return 0, err
+6 -6
View File
@@ -10,7 +10,7 @@ import (
"net"
"net/http"
"github.com/xjasonlyu/tun2socks/v2/buffer"
"github.com/xjasonlyu/tun2socks/v2/common/pool"
)
// HTTPObfs is shadowsocks http simple-obfs implementation
@@ -29,22 +29,22 @@ func (ho *HTTPObfs) Read(b []byte) (int, error) {
n := copy(b, ho.buf[ho.offset:])
ho.offset += n
if ho.offset == len(ho.buf) {
buffer.Put(ho.buf)
pool.Put(ho.buf)
ho.buf = nil
}
return n, nil
}
if ho.firstResponse {
buf := buffer.Get(buffer.RelayBufferSize)
buf := pool.Get(pool.RelayBufferSize)
n, err := ho.Conn.Read(buf)
if err != nil {
buffer.Put(buf)
pool.Put(buf)
return 0, err
}
idx := bytes.Index(buf[:n], []byte("\r\n\r\n"))
if idx == -1 {
buffer.Put(buf)
pool.Put(buf)
return 0, io.EOF
}
ho.firstResponse = false
@@ -54,7 +54,7 @@ func (ho *HTTPObfs) Read(b []byte) (int, error) {
ho.buf = buf[:idx+4+length]
ho.offset = idx + 4 + n
} else {
buffer.Put(buf)
pool.Put(buf)
}
return n, nil
}
+7 -8
View File
@@ -1,14 +1,14 @@
package obfs
import (
"bytes"
"crypto/rand"
"encoding/binary"
"io"
"net"
"time"
"github.com/xjasonlyu/tun2socks/v2/buffer"
"github.com/xjasonlyu/tun2socks/v2/transport/internal/bufferpool"
"github.com/xjasonlyu/tun2socks/v2/common/pool"
)
const (
@@ -25,12 +25,12 @@ type TLSObfs struct {
}
func (to *TLSObfs) read(b []byte, discardN int) (int, error) {
buf := buffer.Get(discardN)
buf := pool.Get(discardN)
_, err := io.ReadFull(to.Conn, buf)
if err != nil {
return 0, err
}
buffer.Put(buf)
pool.Put(buf)
sizeBuf := make([]byte, 2)
_, err = io.ReadFull(to.Conn, sizeBuf)
@@ -99,8 +99,8 @@ func (to *TLSObfs) write(b []byte) (int, error) {
return len(b), err
}
buf := bufferpool.Get()
defer bufferpool.Put(buf)
buf := pool.GetBuffer()
defer pool.PutBuffer(buf)
buf.Write([]byte{0x17, 0x03, 0x03})
binary.Write(buf, binary.BigEndian, uint16(len(b)))
buf.Write(b)
@@ -124,8 +124,7 @@ func makeClientHelloMsg(data []byte, server string) []byte {
rand.Read(random)
rand.Read(sessionID)
buf := bufferpool.Get()
defer bufferpool.Put(buf)
buf := &bytes.Buffer{}
// handshake, TLS 1.0 version, length
buf.WriteByte(22)
+19 -25
View File
@@ -2,14 +2,12 @@
package socks4
import (
"bytes"
"encoding/binary"
"errors"
"io"
"net"
"net/netip"
"strconv"
"github.com/xjasonlyu/tun2socks/v2/transport/internal/bufferpool"
)
const Version = 0x04
@@ -33,7 +31,6 @@ const (
var (
errVersionMismatched = errors.New("version code mismatched")
errIPv6NotSupported = errors.New("IPv6 not supported")
errCmdNotSupported = errors.New("command not supported")
ErrRequestRejected = errors.New("request rejected or failed")
ErrRequestIdentdFailed = errors.New("request rejected because SOCKS server cannot connect to identd on the client")
@@ -42,10 +39,6 @@ var (
)
func ClientHandshake(rw io.ReadWriter, addr string, command Command, userID string) (err error) {
if command == CmdBind {
return errCmdNotSupported
}
var (
host string
port uint16
@@ -54,27 +47,24 @@ func ClientHandshake(rw io.ReadWriter, addr string, command Command, userID stri
return err
}
ip, _ := netip.ParseAddr(host)
switch {
case !ip.IsValid(): /* HOST */
ip = netip.AddrFrom4([4]byte{0, 0, 0, 1})
case ip.Is4In6(): /* IPv4-mapped IPv6 */
ip = netip.AddrFrom4(ip.As4())
case ip.Is4(): /* IPv4 */
case ip.Is6(): /* IPv6 */
ip := net.ParseIP(host)
if ip == nil /* HOST */ {
ip = net.IPv4(0, 0, 0, 1)
} else if ip.To4() == nil /* IPv6 */ {
return errIPv6NotSupported
}
req := bufferpool.Get()
defer bufferpool.Put(req)
dstIP := /* [4]byte */ ip.To4()
req := &bytes.Buffer{}
req.WriteByte(Version)
req.WriteByte(command)
_ = binary.Write(req, binary.BigEndian, port)
req.Write(ip.AsSlice())
binary.Write(req, binary.BigEndian, port)
req.Write(dstIP)
req.WriteString(userID)
req.WriteByte(0x00) /* NULL */
req.WriteByte(0) /* NULL */
if isReservedIP(ip) /* SOCKS4A */ {
if isReservedIP(dstIP) /* SOCKS4A */ {
req.WriteString(host)
req.WriteByte(0) /* NULL */
}
@@ -113,9 +103,13 @@ func ClientHandshake(rw io.ReadWriter, addr string, command Command, userID stri
// Internet Assigned Numbers Authority -- such an address is inadmissible
// as a destination IP address and thus should never occur if the client
// can resolve the domain name.)
func isReservedIP(ip netip.Addr) bool {
prefix := netip.PrefixFrom(netip.IPv4Unspecified(), 24)
return !ip.IsUnspecified() && prefix.Contains(ip)
func isReservedIP(ip net.IP) bool {
subnet := net.IPNet{
IP: net.IPv4zero,
Mask: net.IPv4Mask(0xff, 0xff, 0xff, 0x00),
}
return !ip.IsUnspecified() && subnet.Contains(ip)
}
func splitHostPort(addr string) (string, uint16, error) {
-84
View File
@@ -1,84 +0,0 @@
package socks4
import (
"net/netip"
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsReservedIP(t *testing.T) {
reservedIPs := []string{
"0.0.0.1",
"0.0.0.2",
"0.0.0.50",
"0.0.0.100",
"0.0.0.255",
}
for _, ip := range reservedIPs {
assert.True(t, isReservedIP(netip.MustParseAddr(ip)))
}
unReservedIPs := []string{
"0.0.0.0",
"0.0.1.0",
"1.1.1.1",
"10.0.0.0",
"255.255.255.255",
}
for _, ip := range unReservedIPs {
assert.False(t, isReservedIP(netip.MustParseAddr(ip)))
}
}
func TestSplitHostPort(t *testing.T) {
tests := []struct {
addr string
host string
port uint16
}{
{
"1.1.1.1:80",
"1.1.1.1",
80,
},
{
"1.1.1.1:0",
"1.1.1.1",
0,
},
{
"0.0.0.0:0",
"0.0.0.0",
0,
},
{
"[::1]:443",
"::1",
443,
},
{
"example.com:80",
"example.com",
80,
},
}
for _, tt := range tests {
host, port, err := splitHostPort(tt.addr)
assert.NoError(t, err)
assert.Equal(t, tt.host, host)
assert.Equal(t, tt.port, port)
}
addrs := []string{
"1.1.1.1:-80",
"1.1.1.1:abcd",
"::1:80",
"[::1]",
"example.com",
}
for _, addr := range addrs {
_, _, err := splitHostPort(addr)
assert.Error(t, err)
}
}
+17 -26
View File
@@ -8,10 +8,7 @@ import (
"fmt"
"io"
"net"
"net/netip"
"strconv"
"github.com/xjasonlyu/tun2socks/v2/transport/internal/bufferpool"
)
// AuthMethod is the authentication method as defined in RFC 1928 section 3.
@@ -208,9 +205,10 @@ func ClientHandshake(rw io.ReadWriter, addr Addr, command Command, user *User) (
return nil, errors.New("auth username/password too long")
}
authMsgLen := 1 + 1 + uLen + 1 + pLen
// password protocol version
authMsg := bufferpool.Get()
defer bufferpool.Put(authMsg)
authMsg := bytes.NewBuffer(make([]byte, 0, authMsgLen))
authMsg.WriteByte(0x01 /* VER */)
authMsg.WriteByte(byte(uLen) /* ULEN */)
authMsg.WriteString(user.Username /* UNAME */)
@@ -234,15 +232,7 @@ func ClientHandshake(rw io.ReadWriter, addr Addr, command Command, user *User) (
}
// VER, CMD, RSV, ADDR
req := bufferpool.Get()
defer bufferpool.Put(req)
req.Grow(3 + MaxAddrLen)
req.WriteByte(Version)
req.WriteByte(byte(command))
req.WriteByte(0x00 /* RSV */)
req.Write(addr)
if _, err := rw.Write(req.Bytes()); err != nil {
if _, err := rw.Write(bytes.Join([][]byte{{Version, byte(command), 0x00 /* RSV */}, addr}, nil)); err != nil {
return nil, err
}
@@ -318,7 +308,7 @@ func SplitAddr(b []byte) Addr {
// SerializeAddr serializes destination address and port to Addr.
// If a domain name is provided, AtypDomainName would be used first.
func SerializeAddr(domainName string, dstIP netip.Addr, dstPort uint16) Addr {
func SerializeAddr(domainName string, dstIP net.IP, dstPort uint16) Addr {
var (
buf [][]byte
port [2]byte
@@ -328,10 +318,10 @@ func SerializeAddr(domainName string, dstIP netip.Addr, dstPort uint16) Addr {
if domainName != "" /* Domain Name */ {
length := len(domainName)
buf = [][]byte{{AtypDomainName, uint8(length)}, []byte(domainName), port[:]}
} else if dstIP.Is4() /* IPv4 */ {
buf = [][]byte{{AtypIPv4}, dstIP.AsSlice(), port[:]}
} else if dstIP.To4() != nil /* IPv4 */ {
buf = [][]byte{{AtypIPv4}, dstIP.To4(), port[:]}
} else /* IPv6 */ {
buf = [][]byte{{AtypIPv6}, dstIP.AsSlice(), port[:]}
buf = [][]byte{{AtypIPv6}, dstIP.To16(), port[:]}
}
return bytes.Join(buf, nil)
}
@@ -339,13 +329,14 @@ func SerializeAddr(domainName string, dstIP netip.Addr, dstPort uint16) Addr {
// ParseAddr parses a socks addr from net.Addr.
// This is a fast path of ParseAddrString(addr.String())
func ParseAddr(addr net.Addr) Addr {
if v, ok := addr.(interface {
AddrPort() netip.AddrPort
}); ok {
ap := v.AddrPort()
return SerializeAddr("", ap.Addr(), ap.Port())
switch v := addr.(type) {
case *net.TCPAddr:
return SerializeAddr("", v.IP, uint16(v.Port))
case *net.UDPAddr:
return SerializeAddr("", v.IP, uint16(v.Port))
default:
return ParseAddrString(addr.String())
}
return ParseAddrString(addr.String())
}
// ParseAddrString parses the address in string s to Addr. Returns nil if failed.
@@ -360,10 +351,10 @@ func ParseAddrString(s string) Addr {
return nil
}
if ip, _ := netip.ParseAddr(host); ip.IsValid() {
if ip := net.ParseIP(host); ip != nil {
return SerializeAddr("", ip, uint16(dstPort))
}
return SerializeAddr(host, netip.Addr{}, uint16(dstPort))
return SerializeAddr(host, nil, uint16(dstPort))
}
// DecodeUDPPacket split `packet` to addr payload, and this function is mutable with `packet`
+16 -27
View File
@@ -2,37 +2,26 @@ package tunnel
import (
"net"
"net/netip"
"gvisor.dev/gvisor/pkg/tcpip"
"strconv"
)
// parseNetAddr parses net.Addr to IP and port.
func parseNetAddr(addr net.Addr) (netip.Addr, uint16) {
if addr == nil {
return netip.Addr{}, 0
// parseAddr parses net.Addr to IP and port.
func parseAddr(addr net.Addr) (net.IP, uint16) {
switch v := addr.(type) {
case *net.TCPAddr:
return v.IP, uint16(v.Port)
case *net.UDPAddr:
return v.IP, uint16(v.Port)
case nil:
return nil, 0
default:
return parseAddrString(addr.String())
}
if v, ok := addr.(interface {
AddrPort() netip.AddrPort
}); ok {
ap := v.AddrPort()
return ap.Addr(), ap.Port()
}
return parseAddrString(addr.String())
}
// parseAddrString parses address string to IP and port.
// It doesn't do any name resolution.
func parseAddrString(s string) (netip.Addr, uint16) {
ap, err := netip.ParseAddrPort(s)
if err != nil {
return netip.Addr{}, 0
}
return ap.Addr(), ap.Port()
}
// parseTCPIPAddress parses tcpip.Address to netip.Addr.
func parseTCPIPAddress(addr tcpip.Address) netip.Addr {
ip, _ := netip.AddrFromSlice(addr.AsSlice())
return ip
func parseAddrString(addr string) (net.IP, uint16) {
host, port, _ := net.SplitHostPort(addr)
portInt, _ := strconv.ParseUint(port, 10, 16)
return net.ParseIP(host), uint16(portInt)
}
-37
View File
@@ -1,37 +0,0 @@
package tunnel
import (
"sync"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/tunnel/statistic"
)
var (
_globalMu sync.RWMutex
_globalT *Tunnel
)
func init() {
ReplaceGlobal(New(&proxy.Base{}, statistic.DefaultManager))
T().ProcessAsync()
}
// T returns the global Tunnel, which can be reconfigured with
// ReplaceGlobal. It's safe for concurrent use.
func T() *Tunnel {
_globalMu.RLock()
t := _globalT
_globalMu.RUnlock()
return t
}
// ReplaceGlobal replaces the global Tunnel, and returns a function
// to restore the original values. It's safe for concurrent use.
func ReplaceGlobal(t *Tunnel) func() {
_globalMu.Lock()
prev := _globalT
_globalT = t
_globalMu.Unlock()
return func() { ReplaceGlobal(prev) }
}
+1
View File
@@ -18,6 +18,7 @@ func init() {
uploadTotal: atomic.NewInt64(0),
downloadTotal: atomic.NewInt64(0),
}
go DefaultManager.handle()
}
+10
View File
@@ -50,6 +50,11 @@ func NewTCPTracker(conn net.Conn, metadata *M.Metadata, manager *Manager) net.Co
return tt
}
// DefaultTCPTracker returns a new net.Conn(*tcpTacker) with default manager.
func DefaultTCPTracker(conn net.Conn, metadata *M.Metadata) net.Conn {
return NewTCPTracker(conn, metadata, DefaultManager)
}
func (tt *tcpTracker) ID() string {
return tt.UUID.String()
}
@@ -115,6 +120,11 @@ func NewUDPTracker(conn net.PacketConn, metadata *M.Metadata, manager *Manager)
return ut
}
// DefaultUDPTracker returns a new net.PacketConn(*udpTacker) with default manager.
func DefaultUDPTracker(conn net.PacketConn, metadata *M.Metadata) net.PacketConn {
return NewUDPTracker(conn, metadata, DefaultManager)
}
func (ut *udpTracker) ID() string {
return ut.UUID.String()
}
+16 -14
View File
@@ -1,49 +1,51 @@
package tunnel
import (
"context"
"io"
"net"
"sync"
"time"
"github.com/xjasonlyu/tun2socks/v2/buffer"
"github.com/xjasonlyu/tun2socks/v2/common/pool"
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
"github.com/xjasonlyu/tun2socks/v2/log"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/tunnel/statistic"
)
func (t *Tunnel) handleTCPConn(originConn adapter.TCPConn) {
const (
// tcpWaitTimeout implements a TCP half-close timeout.
tcpWaitTimeout = 60 * time.Second
)
func handleTCPConn(originConn adapter.TCPConn) {
defer originConn.Close()
id := originConn.ID()
metadata := &M.Metadata{
Network: M.TCP,
SrcIP: parseTCPIPAddress(id.RemoteAddress),
SrcIP: net.IP(id.RemoteAddress.AsSlice()),
SrcPort: id.RemotePort,
DstIP: parseTCPIPAddress(id.LocalAddress),
DstIP: net.IP(id.LocalAddress.AsSlice()),
DstPort: id.LocalPort,
}
ctx, cancel := context.WithTimeout(context.Background(), tcpConnectTimeout)
defer cancel()
remoteConn, err := t.Dialer().DialContext(ctx, metadata)
remoteConn, err := proxy.Dial(metadata)
if err != nil {
log.Warnf("[TCP] dial %s: %v", metadata.DestinationAddress(), err)
return
}
metadata.MidIP, metadata.MidPort = parseNetAddr(remoteConn.LocalAddr())
metadata.MidIP, metadata.MidPort = parseAddr(remoteConn.LocalAddr())
remoteConn = statistic.NewTCPTracker(remoteConn, metadata, t.manager)
remoteConn = statistic.DefaultTCPTracker(remoteConn, metadata)
defer remoteConn.Close()
log.Infof("[TCP] %s <-> %s", metadata.SourceAddress(), metadata.DestinationAddress())
pipe(originConn, remoteConn)
}
// pipe copies data to & from provided net.Conn(s) bidirectionally.
// pipe copies copy data to & from provided net.Conn(s) bidirectionally.
func pipe(origin, remote net.Conn) {
wg := sync.WaitGroup{}
wg.Add(2)
@@ -56,11 +58,11 @@ func pipe(origin, remote net.Conn) {
func unidirectionalStream(dst, src net.Conn, dir string, wg *sync.WaitGroup) {
defer wg.Done()
buf := buffer.Get(buffer.RelayBufferSize)
buf := pool.Get(pool.RelayBufferSize)
if _, err := io.CopyBuffer(dst, src, buf); err != nil {
log.Debugf("[TCP] copy data for %s: %v", dir, err)
}
buffer.Put(buf)
pool.Put(buf)
// Do the upload/download side TCP half-close.
if cr, ok := src.(interface{ CloseRead() error }); ok {
cr.CloseRead()
+15 -95
View File
@@ -1,116 +1,36 @@
package tunnel
import (
"context"
"sync"
"time"
"go.uber.org/atomic"
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/tunnel/statistic"
)
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
// Unbuffered TCP/UDP queues.
var (
_tcpQueue = make(chan adapter.TCPConn)
_udpQueue = make(chan adapter.UDPConn)
)
var _ adapter.TransportHandler = (*Tunnel)(nil)
type Tunnel struct {
// Unbuffered TCP/UDP queues.
tcpQueue chan adapter.TCPConn
udpQueue chan adapter.UDPConn
// UDP session timeout.
udpTimeout *atomic.Duration
// Internal proxy.Dialer for Tunnel.
dialerMu sync.RWMutex
dialer proxy.Dialer
// Where the Tunnel statistics are sent to.
manager *statistic.Manager
procOnce sync.Once
procCancel context.CancelFunc
}
func New(dialer proxy.Dialer, manager *statistic.Manager) *Tunnel {
return &Tunnel{
tcpQueue: make(chan adapter.TCPConn),
udpQueue: make(chan adapter.UDPConn),
udpTimeout: atomic.NewDuration(udpSessionTimeout),
dialer: dialer,
manager: manager,
procCancel: func() { /* nop */ },
}
func init() {
go process()
}
// TCPIn return fan-in TCP queue.
func (t *Tunnel) TCPIn() chan<- adapter.TCPConn {
return t.tcpQueue
func TCPIn() chan<- adapter.TCPConn {
return _tcpQueue
}
// UDPIn return fan-in UDP queue.
func (t *Tunnel) UDPIn() chan<- adapter.UDPConn {
return t.udpQueue
func UDPIn() chan<- adapter.UDPConn {
return _udpQueue
}
func (t *Tunnel) HandleTCP(conn adapter.TCPConn) {
t.TCPIn() <- conn
}
func (t *Tunnel) HandleUDP(conn adapter.UDPConn) {
t.UDPIn() <- conn
}
func (t *Tunnel) process(ctx context.Context) {
func process() {
for {
select {
case conn := <-t.tcpQueue:
go t.handleTCPConn(conn)
case conn := <-t.udpQueue:
go t.handleUDPConn(conn)
case <-ctx.Done():
return
case conn := <-_tcpQueue:
go handleTCPConn(conn)
case conn := <-_udpQueue:
go handleUDPConn(conn)
}
}
}
// ProcessAsync can be safely called multiple times, but will only be effective once.
func (t *Tunnel) ProcessAsync() {
t.procOnce.Do(func() {
ctx, cancel := context.WithCancel(context.Background())
t.procCancel = cancel
go t.process(ctx)
})
}
// Close closes the Tunnel and releases its resources.
func (t *Tunnel) Close() {
t.procCancel()
}
func (t *Tunnel) Dialer() proxy.Dialer {
t.dialerMu.RLock()
d := t.dialer
t.dialerMu.RUnlock()
return d
}
func (t *Tunnel) SetDialer(dialer proxy.Dialer) {
t.dialerMu.Lock()
t.dialer = dialer
t.dialerMu.Unlock()
}
func (t *Tunnel) SetUDPTimeout(timeout time.Duration) {
t.udpTimeout.Store(timeout)
}
+23 -15
View File
@@ -6,34 +6,42 @@ import (
"sync"
"time"
"github.com/xjasonlyu/tun2socks/v2/buffer"
"github.com/xjasonlyu/tun2socks/v2/common/pool"
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
"github.com/xjasonlyu/tun2socks/v2/log"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/tunnel/statistic"
)
// _udpSessionTimeout is the default timeout for each UDP session.
var _udpSessionTimeout = 60 * time.Second
func SetUDPTimeout(t time.Duration) {
_udpSessionTimeout = t
}
// TODO: Port Restricted NAT support.
func (t *Tunnel) handleUDPConn(uc adapter.UDPConn) {
func handleUDPConn(uc adapter.UDPConn) {
defer uc.Close()
id := uc.ID()
metadata := &M.Metadata{
Network: M.UDP,
SrcIP: parseTCPIPAddress(id.RemoteAddress),
SrcIP: net.IP(id.RemoteAddress.AsSlice()),
SrcPort: id.RemotePort,
DstIP: parseTCPIPAddress(id.LocalAddress),
DstIP: net.IP(id.LocalAddress.AsSlice()),
DstPort: id.LocalPort,
}
pc, err := t.Dialer().DialUDP(metadata)
pc, err := proxy.DialUDP(metadata)
if err != nil {
log.Warnf("[UDP] dial %s: %v", metadata.DestinationAddress(), err)
return
}
metadata.MidIP, metadata.MidPort = parseNetAddr(pc.LocalAddr())
metadata.MidIP, metadata.MidPort = parseAddr(pc.LocalAddr())
pc = statistic.NewUDPTracker(pc, metadata, t.manager)
pc = statistic.DefaultUDPTracker(pc, metadata)
defer pc.Close()
var remote net.Addr
@@ -45,29 +53,29 @@ func (t *Tunnel) handleUDPConn(uc adapter.UDPConn) {
pc = newSymmetricNATPacketConn(pc, metadata)
log.Infof("[UDP] %s <-> %s", metadata.SourceAddress(), metadata.DestinationAddress())
pipePacket(uc, pc, remote, t.udpTimeout.Load())
pipePacket(uc, pc, remote)
}
func pipePacket(origin, remote net.PacketConn, to net.Addr, timeout time.Duration) {
func pipePacket(origin, remote net.PacketConn, to net.Addr) {
wg := sync.WaitGroup{}
wg.Add(2)
go unidirectionalPacketStream(remote, origin, to, "origin->remote", &wg, timeout)
go unidirectionalPacketStream(origin, remote, nil, "remote->origin", &wg, timeout)
go unidirectionalPacketStream(remote, origin, to, "origin->remote", &wg)
go unidirectionalPacketStream(origin, remote, nil, "remote->origin", &wg)
wg.Wait()
}
func unidirectionalPacketStream(dst, src net.PacketConn, to net.Addr, dir string, wg *sync.WaitGroup, timeout time.Duration) {
func unidirectionalPacketStream(dst, src net.PacketConn, to net.Addr, dir string, wg *sync.WaitGroup) {
defer wg.Done()
if err := copyPacketData(dst, src, to, timeout); err != nil {
if err := copyPacketData(dst, src, to, _udpSessionTimeout); err != nil {
log.Debugf("[UDP] copy data for %s: %v", dir, err)
}
}
func copyPacketData(dst, src net.PacketConn, to net.Addr, timeout time.Duration) error {
buf := buffer.Get(buffer.MaxSegmentSize)
defer buffer.Put(buf)
buf := pool.Get(pool.MaxSegmentSize)
defer pool.Put(buf)
for {
src.SetReadDeadline(time.Now().Add(timeout))