All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 30s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 1m26s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 49s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 1m1s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 54s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 39s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m32s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 51s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 38s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 1m0s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 58s
265 lines
5.9 KiB
Go
265 lines
5.9 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,
|
|
Description: "SOCKS5 proxy protocol handler (official plugin)",
|
|
Author: "GoTunnel",
|
|
Capabilities: []string{
|
|
"dial", "read", "write", "close",
|
|
},
|
|
ConfigSchema: []plugin.ConfigField{
|
|
{
|
|
Key: "auth",
|
|
Label: "认证方式",
|
|
Type: plugin.ConfigFieldSelect,
|
|
Default: "none",
|
|
Options: []string{"none", "password"},
|
|
Description: "SOCKS5 认证方式",
|
|
},
|
|
{
|
|
Key: "username",
|
|
Label: "用户名",
|
|
Type: plugin.ConfigFieldString,
|
|
Description: "认证用户名(仅 password 认证时需要)",
|
|
},
|
|
{
|
|
Key: "password",
|
|
Label: "密码",
|
|
Type: plugin.ConfigFieldPassword,
|
|
Description: "认证密码(仅 password 认证时需要)",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|