1
All checks were successful
Build Multi-Platform Binaries / build (push) Successful in 12m12s

This commit is contained in:
Flik
2025-12-25 23:09:49 +08:00
parent db5bd942d3
commit f1038a132b
8 changed files with 517 additions and 8 deletions

View File

@@ -108,20 +108,24 @@ func (c *Client) handleSession() {
// handleStream 处理流 // handleStream 处理流
func (c *Client) handleStream(stream net.Conn) { func (c *Client) handleStream(stream net.Conn) {
defer stream.Close()
msg, err := protocol.ReadMessage(stream) msg, err := protocol.ReadMessage(stream)
if err != nil { if err != nil {
stream.Close()
return return
} }
switch msg.Type { switch msg.Type {
case protocol.MsgTypeProxyConfig: case protocol.MsgTypeProxyConfig:
defer stream.Close()
c.handleProxyConfig(msg) c.handleProxyConfig(msg)
case protocol.MsgTypeNewProxy: case protocol.MsgTypeNewProxy:
defer stream.Close()
c.handleNewProxy(stream, msg) c.handleNewProxy(stream, msg)
case protocol.MsgTypeHeartbeat: case protocol.MsgTypeHeartbeat:
defer stream.Close()
c.handleHeartbeat(stream) c.handleHeartbeat(stream)
case protocol.MsgTypeProxyConnect:
c.handleProxyConnect(stream, msg)
} }
} }
@@ -175,3 +179,37 @@ func (c *Client) handleHeartbeat(stream net.Conn) {
msg := &protocol.Message{Type: protocol.MsgTypeHeartbeatAck} msg := &protocol.Message{Type: protocol.MsgTypeHeartbeatAck}
protocol.WriteMessage(stream, msg) protocol.WriteMessage(stream, msg)
} }
// handleProxyConnect 处理代理连接请求 (SOCKS5/HTTP)
func (c *Client) handleProxyConnect(stream net.Conn, msg *protocol.Message) {
defer stream.Close()
var req protocol.ProxyConnectRequest
if err := msg.ParsePayload(&req); err != nil {
c.sendProxyResult(stream, false, "invalid request")
return
}
// 连接目标地址
targetConn, err := net.DialTimeout("tcp", req.Target, 10*time.Second)
if err != nil {
c.sendProxyResult(stream, false, err.Error())
return
}
defer targetConn.Close()
// 发送成功响应
if err := c.sendProxyResult(stream, true, ""); err != nil {
return
}
// 双向转发数据
relay.Relay(stream, targetConn)
}
// sendProxyResult 发送代理连接结果
func (c *Client) sendProxyResult(stream net.Conn, success bool, message string) error {
result := protocol.ProxyConnectResult{Success: success, Message: message}
msg, _ := protocol.NewMessage(protocol.MsgTypeProxyResult, result)
return protocol.WriteMessage(stream, msg)
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/gotunnel/internal/server/db" "github.com/gotunnel/internal/server/db"
"github.com/gotunnel/pkg/protocol" "github.com/gotunnel/pkg/protocol"
"github.com/gotunnel/pkg/proxy"
"github.com/gotunnel/pkg/relay" "github.com/gotunnel/pkg/relay"
"github.com/gotunnel/pkg/utils" "github.com/gotunnel/pkg/utils"
"github.com/hashicorp/yamux" "github.com/hashicorp/yamux"
@@ -208,12 +209,23 @@ func (s *Server) startProxyListeners(cs *ClientSession) {
cs.Listeners[rule.RemotePort] = ln cs.Listeners[rule.RemotePort] = ln
cs.mu.Unlock() cs.mu.Unlock()
log.Printf("[Server] Proxy %s: :%d -> %s:%d", ruleType := rule.Type
rule.Name, rule.RemotePort, rule.LocalIP, rule.LocalPort) if ruleType == "" {
ruleType = "tcp"
}
switch ruleType {
case "socks5", "http":
log.Printf("[Server] %s proxy %s on :%d",
ruleType, rule.Name, rule.RemotePort)
go s.acceptProxyServerConns(cs, ln, rule)
default:
log.Printf("[Server] TCP proxy %s: :%d -> %s:%d",
rule.Name, rule.RemotePort, rule.LocalIP, rule.LocalPort)
go s.acceptProxyConns(cs, ln, rule) go s.acceptProxyConns(cs, ln, rule)
} }
} }
}
// acceptProxyConns 接受代理连接 // acceptProxyConns 接受代理连接
func (s *Server) acceptProxyConns(cs *ClientSession, ln net.Listener, rule protocol.ProxyRule) { func (s *Server) acceptProxyConns(cs *ClientSession, ln net.Listener, rule protocol.ProxyRule) {
@@ -226,6 +238,20 @@ func (s *Server) acceptProxyConns(cs *ClientSession, ln net.Listener, rule proto
} }
} }
// acceptProxyServerConns 接受 SOCKS5/HTTP 代理连接
func (s *Server) acceptProxyServerConns(cs *ClientSession, ln net.Listener, rule protocol.ProxyRule) {
dialer := proxy.NewTunnelDialer(cs.Session)
proxyServer := proxy.NewServer(rule.Type, dialer)
for {
conn, err := ln.Accept()
if err != nil {
return
}
go proxyServer.HandleConn(conn)
}
}
// handleProxyConn 处理代理连接 // handleProxyConn 处理代理连接
func (s *Server) handleProxyConn(cs *ClientSession, conn net.Conn, rule protocol.ProxyRule) { func (s *Server) handleProxyConn(cs *ClientSession, conn net.Conn, rule protocol.ProxyRule) {
defer conn.Close() defer conn.Close()

71
pkg/crypto/tls.go Normal file
View File

@@ -0,0 +1,71 @@
package crypto
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"os"
"time"
)
// GenerateSelfSignedCert 生成自签名证书
func GenerateSelfSignedCert(certFile, keyFile string) error {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return err
}
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return err
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"GoTunnel"},
CommonName: "GoTunnel Server",
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0), // 10年有效期
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
DNSNames: []string{"localhost"},
}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return err
}
// 写入证书文件
certOut, err := os.Create(certFile)
if err != nil {
return err
}
defer certOut.Close()
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certDER})
// 写入私钥文件
keyOut, err := os.Create(keyFile)
if err != nil {
return err
}
defer keyOut.Close()
privBytes, err := x509.MarshalECPrivateKey(priv)
if err != nil {
return err
}
pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: privBytes})
return nil
}

View File

@@ -17,6 +17,8 @@ const (
MsgTypeNewProxy uint8 = 6 // 新建代理连接请求 MsgTypeNewProxy uint8 = 6 // 新建代理连接请求
MsgTypeProxyReady uint8 = 7 // 代理就绪 MsgTypeProxyReady uint8 = 7 // 代理就绪
MsgTypeError uint8 = 8 // 错误消息 MsgTypeError uint8 = 8 // 错误消息
MsgTypeProxyConnect uint8 = 9 // 代理连接请求 (SOCKS5/HTTP)
MsgTypeProxyResult uint8 = 10 // 代理连接结果
) )
// Message 基础消息结构 // Message 基础消息结构
@@ -40,9 +42,10 @@ type AuthResponse struct {
// ProxyRule 代理规则 // ProxyRule 代理规则
type ProxyRule struct { type ProxyRule struct {
Name string `json:"name" yaml:"name"` Name string `json:"name" yaml:"name"`
LocalIP string `json:"local_ip" yaml:"local_ip"` Type string `json:"type" yaml:"type"` // tcp, socks5, http
LocalPort int `json:"local_port" yaml:"local_port"` LocalIP string `json:"local_ip" yaml:"local_ip"` // tcp 模式使用
RemotePort int `json:"remote_port" yaml:"remote_port"` LocalPort int `json:"local_port" yaml:"local_port"` // tcp 模式使用
RemotePort int `json:"remote_port" yaml:"remote_port"` // 服务端监听端口
} }
// ProxyConfig 代理配置下发 // ProxyConfig 代理配置下发
@@ -61,6 +64,17 @@ type ErrorMessage struct {
Message string `json:"message"` Message string `json:"message"`
} }
// ProxyConnectRequest 代理连接请求
type ProxyConnectRequest struct {
Target string `json:"target"` // 目标地址 host:port
}
// ProxyConnectResult 代理连接结果
type ProxyConnectResult struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
}
// WriteMessage 写入消息到 writer // WriteMessage 写入消息到 writer
func WriteMessage(w io.Writer, msg *Message) error { func WriteMessage(w io.Writer, msg *Message) error {
header := make([]byte, 5) header := make([]byte, 5)

65
pkg/proxy/dialer.go Normal file
View File

@@ -0,0 +1,65 @@
package proxy
import (
"errors"
"net"
"github.com/gotunnel/pkg/protocol"
"github.com/hashicorp/yamux"
)
// TunnelDialer 通过隧道连接的拨号器
type TunnelDialer struct {
session *yamux.Session
}
// NewTunnelDialer 创建隧道拨号器
func NewTunnelDialer(session *yamux.Session) *TunnelDialer {
return &TunnelDialer{session: session}
}
// Dial 通过隧道建立连接
func (d *TunnelDialer) Dial(network, address string) (net.Conn, error) {
stream, err := d.session.Open()
if err != nil {
return nil, err
}
// 发送代理连接请求
req := protocol.ProxyConnectRequest{Target: address}
msg, err := protocol.NewMessage(protocol.MsgTypeProxyConnect, req)
if err != nil {
stream.Close()
return nil, err
}
if err := protocol.WriteMessage(stream, msg); err != nil {
stream.Close()
return nil, err
}
// 读取连接结果
respMsg, err := protocol.ReadMessage(stream)
if err != nil {
stream.Close()
return nil, err
}
if respMsg.Type != protocol.MsgTypeProxyResult {
stream.Close()
return nil, errors.New("unexpected response type")
}
var result protocol.ProxyConnectResult
if err := respMsg.ParsePayload(&result); err != nil {
stream.Close()
return nil, err
}
if !result.Success {
stream.Close()
return nil, errors.New(result.Message)
}
return stream, nil
}

88
pkg/proxy/http.go Normal file
View File

@@ -0,0 +1,88 @@
package proxy
import (
"bufio"
"io"
"net"
"net/http"
"strings"
)
// HTTPServer HTTP 代理服务
type HTTPServer struct {
dialer Dialer
}
// NewHTTPServer 创建 HTTP 代理服务
func NewHTTPServer(dialer Dialer) *HTTPServer {
return &HTTPServer{dialer: dialer}
}
// HandleConn 处理 HTTP 代理连接
func (h *HTTPServer) HandleConn(conn net.Conn) error {
defer conn.Close()
reader := bufio.NewReader(conn)
req, err := http.ReadRequest(reader)
if err != nil {
return err
}
if req.Method == http.MethodConnect {
return h.handleConnect(conn, req)
}
return h.handleHTTP(conn, req, reader)
}
// handleConnect 处理 CONNECT 方法 (HTTPS)
func (h *HTTPServer) handleConnect(conn net.Conn, req *http.Request) error {
target := req.Host
if !strings.Contains(target, ":") {
target = target + ":443"
}
remote, err := h.dialer.Dial("tcp", target)
if err != nil {
conn.Write([]byte("HTTP/1.1 502 Bad Gateway\r\n\r\n"))
return err
}
defer remote.Close()
conn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n"))
go io.Copy(remote, conn)
io.Copy(conn, remote)
return nil
}
// handleHTTP 处理普通 HTTP 请求
func (h *HTTPServer) handleHTTP(conn net.Conn, req *http.Request, reader *bufio.Reader) error {
target := req.Host
if !strings.Contains(target, ":") {
target = target + ":80"
}
remote, err := h.dialer.Dial("tcp", target)
if err != nil {
conn.Write([]byte("HTTP/1.1 502 Bad Gateway\r\n\r\n"))
return err
}
defer remote.Close()
// 修改请求路径为相对路径
req.URL.Scheme = ""
req.URL.Host = ""
req.RequestURI = req.URL.Path
if req.URL.RawQuery != "" {
req.RequestURI += "?" + req.URL.RawQuery
}
// 发送请求到目标
if err := req.Write(remote); err != nil {
return err
}
// 转发响应
_, err = io.Copy(conn, remote)
return err
}

62
pkg/proxy/server.go Normal file
View File

@@ -0,0 +1,62 @@
package proxy
import (
"log"
"net"
)
// Server 代理服务器
type Server struct {
socks5 *SOCKS5Server
http *HTTPServer
listener net.Listener
typ string
}
// NewServer 创建代理服务器
func NewServer(typ string, dialer Dialer) *Server {
return &Server{
socks5: NewSOCKS5Server(dialer),
http: NewHTTPServer(dialer),
typ: typ,
}
}
// Run 启动代理服务
func (s *Server) Run(addr string) error {
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
s.listener = ln
log.Printf("[Proxy] %s listening on %s", s.typ, addr)
for {
conn, err := ln.Accept()
if err != nil {
return err
}
go s.HandleConn(conn)
}
}
func (s *Server) HandleConn(conn net.Conn) {
var err error
switch s.typ {
case "socks5":
err = s.socks5.HandleConn(conn)
case "http":
err = s.http.HandleConn(conn)
}
if err != nil {
log.Printf("[Proxy] Error: %v", err)
}
}
// Close 关闭服务
func (s *Server) Close() error {
if s.listener != nil {
return s.listener.Close()
}
return nil
}

145
pkg/proxy/socks5.go Normal file
View File

@@ -0,0 +1,145 @@
package proxy
import (
"encoding/binary"
"errors"
"fmt"
"io"
"net"
)
const (
socks5Version = 0x05
noAuth = 0x00
cmdConnect = 0x01
atypIPv4 = 0x01
atypDomain = 0x03
atypIPv6 = 0x04
)
// SOCKS5Server SOCKS5 代理服务
type SOCKS5Server struct {
dialer Dialer
}
// Dialer 连接拨号器接口
type Dialer interface {
Dial(network, address string) (net.Conn, error)
}
// NewSOCKS5Server 创建 SOCKS5 服务
func NewSOCKS5Server(dialer Dialer) *SOCKS5Server {
return &SOCKS5Server{dialer: dialer}
}
// 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
}
// 双向转发
go io.Copy(remote, conn)
io.Copy(conn, remote)
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
}
// 响应:使用无认证
_, err := conn.Write([]byte{socks5Version, noAuth})
return err
}
// 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
}