mirror of
https://github.com/xjasonlyu/tun2socks.git
synced 2025-10-06 17:26:58 +08:00
Feature(proxy): support gost relay protocol (#310)
This commit is contained in:
@@ -7,6 +7,8 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/schema"
|
||||||
|
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/device/fdbased"
|
"github.com/xjasonlyu/tun2socks/v2/core/device/fdbased"
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/device/tun"
|
"github.com/xjasonlyu/tun2socks/v2/core/device/tun"
|
||||||
@@ -92,6 +94,8 @@ func parseProxy(s string) (proxy.Proxy, error) {
|
|||||||
return parseSocks5(u)
|
return parseSocks5(u)
|
||||||
case proto.Shadowsocks.String():
|
case proto.Shadowsocks.String():
|
||||||
return parseShadowsocks(u)
|
return parseShadowsocks(u)
|
||||||
|
case proto.Relay.String():
|
||||||
|
return parseRelay(u)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported protocol: %s", protocol)
|
return nil, fmt.Errorf("unsupported protocol: %s", protocol)
|
||||||
}
|
}
|
||||||
@@ -158,6 +162,20 @@ func parseShadowsocks(u *url.URL) (proxy.Proxy, error) {
|
|||||||
return proxy.NewShadowsocks(address, method, password, obfsMode, obfsHost)
|
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 []net.IP, _ error) {
|
func parseMulticastGroups(s string) (multicastGroups []net.IP, _ error) {
|
||||||
ipStrings := strings.Split(s, ",")
|
ipStrings := strings.Split(s, ",")
|
||||||
for _, ipString := range ipStrings {
|
for _, ipString := range ipStrings {
|
||||||
|
1
go.mod
1
go.mod
@@ -7,6 +7,7 @@ require (
|
|||||||
github.com/go-chi/chi/v5 v5.0.10
|
github.com/go-chi/chi/v5 v5.0.10
|
||||||
github.com/go-chi/cors v1.2.1
|
github.com/go-chi/cors v1.2.1
|
||||||
github.com/go-chi/render v1.0.3
|
github.com/go-chi/render v1.0.3
|
||||||
|
github.com/go-gost/relay v0.4.1-0.20230916134211-828f314ddfe7
|
||||||
github.com/google/uuid v1.3.1
|
github.com/google/uuid v1.3.1
|
||||||
github.com/gorilla/schema v1.2.0
|
github.com/gorilla/schema v1.2.0
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
|
2
go.sum
2
go.sum
@@ -12,6 +12,8 @@ github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
|||||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
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-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||||
|
github.com/go-gost/relay v0.4.1-0.20230916134211-828f314ddfe7 h1:qAG1OyjvdA5h221CfFSS3J359V3d2E7dJWyP29QoDSI=
|
||||||
|
github.com/go-gost/relay v0.4.1-0.20230916134211-828f314ddfe7/go.mod h1:lcX+23LCQ3khIeASBo+tJ/WbwXFO32/N5YN6ucuYTG8=
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
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/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||||
|
@@ -9,6 +9,7 @@ const (
|
|||||||
Socks4
|
Socks4
|
||||||
Socks5
|
Socks5
|
||||||
Shadowsocks
|
Shadowsocks
|
||||||
|
Relay
|
||||||
)
|
)
|
||||||
|
|
||||||
type Proto uint8
|
type Proto uint8
|
||||||
@@ -27,6 +28,8 @@ func (proto Proto) String() string {
|
|||||||
return "socks5"
|
return "socks5"
|
||||||
case Shadowsocks:
|
case Shadowsocks:
|
||||||
return "ss"
|
return "ss"
|
||||||
|
case Relay:
|
||||||
|
return "relay"
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("proto(%d)", proto)
|
return fmt.Sprintf("proto(%d)", proto)
|
||||||
}
|
}
|
||||||
|
252
proxy/relay.go
Normal file
252
proxy/relay.go
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/go-gost/relay"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Proxy = (*Relay)(nil)
|
||||||
|
|
||||||
|
type Relay struct {
|
||||||
|
*Base
|
||||||
|
|
||||||
|
user string
|
||||||
|
pass string
|
||||||
|
|
||||||
|
noDelay bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRelay(addr, user, pass string, noDelay bool) (*Relay, error) {
|
||||||
|
return &Relay{
|
||||||
|
Base: &Base{
|
||||||
|
addr: addr,
|
||||||
|
proto: proto.Relay,
|
||||||
|
},
|
||||||
|
user: user,
|
||||||
|
pass: pass,
|
||||||
|
noDelay: noDelay,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return rl.dialContext(ctx, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("connect to %s: %w", rl.Addr(), err)
|
||||||
|
}
|
||||||
|
setKeepAlive(c)
|
||||||
|
|
||||||
|
defer func(c net.Conn) {
|
||||||
|
safeConnClose(c, err)
|
||||||
|
}(c)
|
||||||
|
|
||||||
|
req := relay.Request{
|
||||||
|
Version: relay.Version1,
|
||||||
|
Cmd: relay.CmdConnect,
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata.Network == M.UDP {
|
||||||
|
req.Cmd |= relay.FUDP
|
||||||
|
req.Features = append(req.Features, &relay.NetworkFeature{
|
||||||
|
Network: relay.NetworkUDP,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if rl.user != "" {
|
||||||
|
req.Features = append(req.Features, &relay.UserAuthFeature{
|
||||||
|
Username: rl.user,
|
||||||
|
Password: rl.pass,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Features = append(req.Features, serializeRelayAddr(metadata))
|
||||||
|
|
||||||
|
if rl.noDelay {
|
||||||
|
if _, err = req.WriteTo(c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = readRelayResponse(c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch metadata.Network {
|
||||||
|
case M.TCP:
|
||||||
|
rc = newRelayConn(c, metadata.Addr(), rl.noDelay, false)
|
||||||
|
if !rl.noDelay {
|
||||||
|
if _, err = req.WriteTo(rc.wbuf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case M.UDP:
|
||||||
|
rc = newRelayConn(c, metadata.Addr(), rl.noDelay, true)
|
||||||
|
if !rl.noDelay {
|
||||||
|
if _, err = req.WriteTo(rc.wbuf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("network %s is unsupported", metadata.Network)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type relayConn struct {
|
||||||
|
net.Conn
|
||||||
|
udp bool
|
||||||
|
addr net.Addr
|
||||||
|
once sync.Once
|
||||||
|
wbuf *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRelayConn(c net.Conn, addr net.Addr, noDelay, udp bool) *relayConn {
|
||||||
|
rc := &relayConn{
|
||||||
|
Conn: c,
|
||||||
|
addr: addr,
|
||||||
|
udp: udp,
|
||||||
|
}
|
||||||
|
if !noDelay {
|
||||||
|
rc.wbuf = &bytes.Buffer{}
|
||||||
|
}
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *relayConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
|
n, err := rc.Read(b)
|
||||||
|
return n, rc.addr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *relayConn) Read(b []byte) (n int, err error) {
|
||||||
|
rc.once.Do(func() {
|
||||||
|
if rc.wbuf != nil {
|
||||||
|
err = readRelayResponse(rc.Conn)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !rc.udp {
|
||||||
|
return rc.Conn.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
var bb [2]byte
|
||||||
|
_, err = io.ReadFull(rc.Conn, bb[:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dLen := int(binary.BigEndian.Uint16(bb[:]))
|
||||||
|
if len(b) >= dLen {
|
||||||
|
return io.ReadFull(rc.Conn, b[:dLen])
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := pool.Get(dLen)
|
||||||
|
defer pool.Put(buf)
|
||||||
|
_, err = io.ReadFull(rc.Conn, buf)
|
||||||
|
n = copy(b, buf)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *relayConn) WriteTo(b []byte, _ net.Addr) (int, error) {
|
||||||
|
return rc.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *relayConn) Write(b []byte) (int, error) {
|
||||||
|
if rc.udp {
|
||||||
|
return rc.udpWrite(b)
|
||||||
|
}
|
||||||
|
return rc.tcpWrite(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *relayConn) tcpWrite(b []byte) (n int, err error) {
|
||||||
|
if rc.wbuf != nil && rc.wbuf.Len() > 0 {
|
||||||
|
n = len(b)
|
||||||
|
rc.wbuf.Write(b)
|
||||||
|
_, err = rc.Conn.Write(rc.wbuf.Bytes())
|
||||||
|
rc.wbuf.Reset()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return rc.Conn.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *relayConn) udpWrite(b []byte) (n int, err error) {
|
||||||
|
if len(b) > math.MaxUint16 {
|
||||||
|
err = errors.New("write: data maximum exceeded")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n = len(b)
|
||||||
|
if rc.wbuf != nil && rc.wbuf.Len() > 0 {
|
||||||
|
var bb [2]byte
|
||||||
|
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
|
||||||
|
rc.wbuf.Write(bb[:])
|
||||||
|
rc.wbuf.Write(b)
|
||||||
|
_, err = rc.wbuf.WriteTo(rc.Conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var bb [2]byte
|
||||||
|
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
|
||||||
|
_, err = rc.Conn.Write(bb[:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return rc.Conn.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readRelayResponse(r io.Reader) error {
|
||||||
|
resp := relay.Response{}
|
||||||
|
if _, err := resp.ReadFrom(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.Version != relay.Version1 {
|
||||||
|
return relay.ErrBadVersion
|
||||||
|
}
|
||||||
|
if resp.Status != relay.StatusOK {
|
||||||
|
return fmt.Errorf("status %d", resp.Status)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func serializeRelayAddr(m *M.Metadata) *relay.AddrFeature {
|
||||||
|
af := &relay.AddrFeature{
|
||||||
|
Host: m.DstIP.String(),
|
||||||
|
Port: m.DstPort,
|
||||||
|
}
|
||||||
|
if m.DstIP.To4() != nil {
|
||||||
|
af.AType = relay.AddrIPv4
|
||||||
|
} else {
|
||||||
|
af.AType = relay.AddrIPv6
|
||||||
|
}
|
||||||
|
return af
|
||||||
|
}
|
Reference in New Issue
Block a user