Files
GoTunnel/pkg/proxy/socks5.go
Flik 5a03d9e1f1 feat: remove unused plugin version comparison and types, refactor proxy server to support authentication
- Deleted version comparison logic from `pkg/plugin/sign/version.go`.
- Removed unused types and constants from `pkg/plugin/types.go`.
- Updated `pkg/protocol/message.go` to remove plugin-related message types.
- Enhanced `pkg/proxy/http.go` and `pkg/proxy/socks5.go` to include username/password authentication for HTTP and SOCKS5 proxies.
- Modified `pkg/proxy/server.go` to pass authentication parameters to server constructors.
- Added new API endpoint to generate installation commands with a token for clients.
- Created database functions to manage installation tokens in `internal/server/db/install_token.go`.
- Implemented the installation command generation logic in `internal/server/router/handler/install.go`.
- Updated web frontend to support installation command generation and display in `web/src/views/ClientsView.vue`.
2026-03-17 23:16:30 +08:00

194 lines
4.2 KiB
Go

package proxy
import (
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"github.com/gotunnel/pkg/relay"
)
const (
socks5Version = 0x05
noAuth = 0x00
userPassAuth = 0x02
cmdConnect = 0x01
atypIPv4 = 0x01
atypDomain = 0x03
atypIPv6 = 0x04
)
// SOCKS5Server SOCKS5 代理服务
type SOCKS5Server struct {
dialer Dialer
onStats func(in, out int64) // 流量统计回调
username string
password string
}
// Dialer 连接拨号器接口
type Dialer interface {
Dial(network, address string) (net.Conn, error)
}
// NewSOCKS5Server 创建 SOCKS5 服务
func NewSOCKS5Server(dialer Dialer, onStats func(in, out int64), username, password string) *SOCKS5Server {
return &SOCKS5Server{dialer: dialer, onStats: onStats, username: username, password: password}
}
// HandleConn 处理 SOCKS5 连接
func (s *SOCKS5Server) HandleConn(conn net.Conn) error {
defer conn.Close()
// 握手阶段
if err := s.handshake(conn); err != nil {
return err
}
// 获取请求
target, err := s.readRequest(conn)
if err != nil {
return err
}
// 连接目标
remote, err := s.dialer.Dial("tcp", target)
if err != nil {
s.sendReply(conn, 0x05) // Connection refused
return err
}
defer remote.Close()
// 发送成功响应
if err := s.sendReply(conn, 0x00); err != nil {
return err
}
// 双向转发 (带流量统计)
relay.RelayWithStats(conn, remote, s.onStats)
return nil
}
// handshake 处理握手
func (s *SOCKS5Server) 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 s.username != "" && s.password != "" {
_, err := conn.Write([]byte{socks5Version, userPassAuth})
if err != nil {
return err
}
return s.authenticate(conn)
}
// 无认证
_, err := conn.Write([]byte{socks5Version, noAuth})
return err
}
// authenticate 处理用户名密码认证
func (s *SOCKS5Server) authenticate(conn net.Conn) error {
buf := make([]byte, 2)
if _, err := io.ReadFull(conn, buf); err != nil {
return err
}
if buf[0] != 0x01 {
return errors.New("unsupported auth version")
}
ulen := int(buf[1])
username := make([]byte, ulen)
if _, err := io.ReadFull(conn, username); err != nil {
return err
}
plen := make([]byte, 1)
if _, err := io.ReadFull(conn, plen); err != nil {
return err
}
password := make([]byte, plen[0])
if _, err := io.ReadFull(conn, password); err != nil {
return err
}
if string(username) == s.username && string(password) == s.password {
conn.Write([]byte{0x01, 0x00}) // 认证成功
return nil
}
conn.Write([]byte{0x01, 0x01}) // 认证失败
return errors.New("authentication failed")
}
// readRequest 读取请求
func (s *SOCKS5Server) 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
}
// sendReply 发送响应
func (s *SOCKS5Server) sendReply(conn net.Conn, rep byte) error {
// VER REP RSV ATYP BND.ADDR BND.PORT
reply := []byte{socks5Version, rep, 0x00, atypIPv4, 0, 0, 0, 0, 0, 0}
_, err := conn.Write(reply)
return err
}