Files
GoTunnel/pkg/plugin/builtin/socks5.go
Flik e10736e05e
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 29s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 49s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 34s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 59s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 34s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 55s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 37s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m7s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 50s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 33s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 59s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 52s
update
2025-12-29 14:24:46 +08:00

263 lines
5.7 KiB
Go

package builtin
import (
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"github.com/gotunnel/pkg/plugin"
)
func init() {
Register(NewSOCKS5Plugin())
}
const (
socks5Version = 0x05
noAuth = 0x00
userPassAuth = 0x02
noAcceptable = 0xFF
userPassAuthVer = 0x01
authSuccess = 0x00
authFailure = 0x01
cmdConnect = 0x01
atypIPv4 = 0x01
atypDomain = 0x03
atypIPv6 = 0x04
)
// SOCKS5Plugin 将现有 SOCKS5 实现封装为 plugin
type SOCKS5Plugin struct {
config map[string]string
}
// NewSOCKS5Plugin 创建 SOCKS5 plugin
func NewSOCKS5Plugin() *SOCKS5Plugin {
return &SOCKS5Plugin{}
}
// Metadata 返回 plugin 信息
func (p *SOCKS5Plugin) Metadata() plugin.PluginMetadata {
return plugin.PluginMetadata{
Name: "socks5",
Version: "1.0.0",
Type: plugin.PluginTypeProxy,
Source: plugin.PluginSourceBuiltin,
RunAt: plugin.SideServer,
Description: "SOCKS5 proxy protocol handler",
Author: "GoTunnel",
RuleSchema: &plugin.RuleSchema{
NeedsLocalAddr: false,
},
ConfigSchema: []plugin.ConfigField{
{
Key: "auth",
Label: "认证方式",
Type: plugin.ConfigFieldSelect,
Default: "none",
Options: []string{"none", "password"},
},
{
Key: "username",
Label: "用户名",
Type: plugin.ConfigFieldString,
},
{
Key: "password",
Label: "密码",
Type: plugin.ConfigFieldPassword,
},
},
}
}
// Init 初始化 plugin
func (p *SOCKS5Plugin) Init(config map[string]string) error {
p.config = config
return nil
}
// HandleConn 处理 SOCKS5 连接
func (p *SOCKS5Plugin) HandleConn(conn net.Conn, dialer plugin.Dialer) error {
defer conn.Close()
// 握手阶段
if err := p.handshake(conn); err != nil {
return err
}
// 获取请求
target, err := p.readRequest(conn)
if err != nil {
return err
}
// 连接目标
remote, err := dialer.Dial("tcp", target)
if err != nil {
p.sendReply(conn, 0x05) // Connection refused
return err
}
defer remote.Close()
// 发送成功响应
if err := p.sendReply(conn, 0x00); err != nil {
return err
}
// 双向转发
go io.Copy(remote, conn)
io.Copy(conn, remote)
return nil
}
// Close 释放资源
func (p *SOCKS5Plugin) Close() error {
return nil
}
// handshake 处理握手
func (p *SOCKS5Plugin) handshake(conn net.Conn) error {
buf := make([]byte, 2)
if _, err := io.ReadFull(conn, buf); err != nil {
return err
}
if buf[0] != socks5Version {
return errors.New("unsupported SOCKS version")
}
nmethods := int(buf[1])
methods := make([]byte, nmethods)
if _, err := io.ReadFull(conn, methods); err != nil {
return err
}
// 检查是否需要密码认证
if p.config["auth"] == "password" {
// 检查客户端是否支持用户名密码认证
supported := false
for _, m := range methods {
if m == userPassAuth {
supported = true
break
}
}
if !supported {
conn.Write([]byte{socks5Version, noAcceptable})
return errors.New("client does not support password auth")
}
// 选择用户名密码认证
if _, err := conn.Write([]byte{socks5Version, userPassAuth}); err != nil {
return err
}
// 执行用户名密码认证
return p.authenticateUserPass(conn)
}
// 无认证
_, err := conn.Write([]byte{socks5Version, noAuth})
return err
}
// readRequest 读取请求
func (p *SOCKS5Plugin) readRequest(conn net.Conn) (string, error) {
buf := make([]byte, 4)
if _, err := io.ReadFull(conn, buf); err != nil {
return "", err
}
if buf[0] != socks5Version || buf[1] != cmdConnect {
return "", errors.New("unsupported command")
}
var host string
switch buf[3] {
case atypIPv4:
ip := make([]byte, 4)
if _, err := io.ReadFull(conn, ip); err != nil {
return "", err
}
host = net.IP(ip).String()
case atypDomain:
lenBuf := make([]byte, 1)
if _, err := io.ReadFull(conn, lenBuf); err != nil {
return "", err
}
domain := make([]byte, lenBuf[0])
if _, err := io.ReadFull(conn, domain); err != nil {
return "", err
}
host = string(domain)
case atypIPv6:
ip := make([]byte, 16)
if _, err := io.ReadFull(conn, ip); err != nil {
return "", err
}
host = net.IP(ip).String()
default:
return "", errors.New("unsupported address type")
}
portBuf := make([]byte, 2)
if _, err := io.ReadFull(conn, portBuf); err != nil {
return "", err
}
port := binary.BigEndian.Uint16(portBuf)
return fmt.Sprintf("%s:%d", host, port), nil
}
// authenticateUserPass 用户名密码认证
func (p *SOCKS5Plugin) authenticateUserPass(conn net.Conn) error {
// 读取认证版本
buf := make([]byte, 2)
if _, err := io.ReadFull(conn, buf); err != nil {
return err
}
if buf[0] != userPassAuthVer {
return errors.New("unsupported auth version")
}
// 读取用户名
ulen := int(buf[1])
username := make([]byte, ulen)
if _, err := io.ReadFull(conn, username); err != nil {
return err
}
// 读取密码长度和密码
plenBuf := make([]byte, 1)
if _, err := io.ReadFull(conn, plenBuf); err != nil {
return err
}
plen := int(plenBuf[0])
password := make([]byte, plen)
if _, err := io.ReadFull(conn, password); err != nil {
return err
}
// 验证用户名密码
expectedUser := p.config["username"]
expectedPass := p.config["password"]
if string(username) == expectedUser && string(password) == expectedPass {
conn.Write([]byte{userPassAuthVer, authSuccess})
return nil
}
conn.Write([]byte{userPassAuthVer, authFailure})
return errors.New("authentication failed")
}
// sendReply 发送响应
func (p *SOCKS5Plugin) sendReply(conn net.Conn, rep byte) error {
reply := []byte{socks5Version, rep, 0x00, atypIPv4, 0, 0, 0, 0, 0, 0}
_, err := conn.Write(reply)
return err
}