mirror of
https://github.com/xjasonlyu/tun2socks.git
synced 2024-12-30 02:37:01 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9f56af25f | ||
|
|
8ef439b8d8 |
+3
-5
@@ -1,8 +1,6 @@
|
||||
.github/
|
||||
.github
|
||||
.gitignore
|
||||
.golangci.yaml
|
||||
|
||||
# Other
|
||||
docs/
|
||||
build/
|
||||
tests/
|
||||
build/*
|
||||
docs/*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)-$@
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
@@ -18,6 +18,7 @@ func init() {
|
||||
uploadTotal: atomic.NewInt64(0),
|
||||
downloadTotal: atomic.NewInt64(0),
|
||||
}
|
||||
|
||||
go DefaultManager.handle()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user