This commit is contained in:
@@ -108,20 +108,24 @@ func (c *Client) handleSession() {
|
||||
|
||||
// handleStream 处理流
|
||||
func (c *Client) handleStream(stream net.Conn) {
|
||||
defer stream.Close()
|
||||
|
||||
msg, err := protocol.ReadMessage(stream)
|
||||
if err != nil {
|
||||
stream.Close()
|
||||
return
|
||||
}
|
||||
|
||||
switch msg.Type {
|
||||
case protocol.MsgTypeProxyConfig:
|
||||
defer stream.Close()
|
||||
c.handleProxyConfig(msg)
|
||||
case protocol.MsgTypeNewProxy:
|
||||
defer stream.Close()
|
||||
c.handleNewProxy(stream, msg)
|
||||
case protocol.MsgTypeHeartbeat:
|
||||
defer stream.Close()
|
||||
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}
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/gotunnel/internal/server/db"
|
||||
"github.com/gotunnel/pkg/protocol"
|
||||
"github.com/gotunnel/pkg/proxy"
|
||||
"github.com/gotunnel/pkg/relay"
|
||||
"github.com/gotunnel/pkg/utils"
|
||||
"github.com/hashicorp/yamux"
|
||||
@@ -208,12 +209,23 @@ func (s *Server) startProxyListeners(cs *ClientSession) {
|
||||
cs.Listeners[rule.RemotePort] = ln
|
||||
cs.mu.Unlock()
|
||||
|
||||
log.Printf("[Server] Proxy %s: :%d -> %s:%d",
|
||||
rule.Name, rule.RemotePort, rule.LocalIP, rule.LocalPort)
|
||||
ruleType := rule.Type
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// acceptProxyConns 接受代理连接
|
||||
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 处理代理连接
|
||||
func (s *Server) handleProxyConn(cs *ClientSession, conn net.Conn, rule protocol.ProxyRule) {
|
||||
defer conn.Close()
|
||||
|
||||
71
pkg/crypto/tls.go
Normal file
71
pkg/crypto/tls.go
Normal 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
|
||||
}
|
||||
@@ -17,6 +17,8 @@ const (
|
||||
MsgTypeNewProxy uint8 = 6 // 新建代理连接请求
|
||||
MsgTypeProxyReady uint8 = 7 // 代理就绪
|
||||
MsgTypeError uint8 = 8 // 错误消息
|
||||
MsgTypeProxyConnect uint8 = 9 // 代理连接请求 (SOCKS5/HTTP)
|
||||
MsgTypeProxyResult uint8 = 10 // 代理连接结果
|
||||
)
|
||||
|
||||
// Message 基础消息结构
|
||||
@@ -40,9 +42,10 @@ type AuthResponse struct {
|
||||
// ProxyRule 代理规则
|
||||
type ProxyRule struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
LocalIP string `json:"local_ip" yaml:"local_ip"`
|
||||
LocalPort int `json:"local_port" yaml:"local_port"`
|
||||
RemotePort int `json:"remote_port" yaml:"remote_port"`
|
||||
Type string `json:"type" yaml:"type"` // tcp, socks5, http
|
||||
LocalIP string `json:"local_ip" yaml:"local_ip"` // tcp 模式使用
|
||||
LocalPort int `json:"local_port" yaml:"local_port"` // tcp 模式使用
|
||||
RemotePort int `json:"remote_port" yaml:"remote_port"` // 服务端监听端口
|
||||
}
|
||||
|
||||
// ProxyConfig 代理配置下发
|
||||
@@ -61,6 +64,17 @@ type ErrorMessage struct {
|
||||
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
|
||||
func WriteMessage(w io.Writer, msg *Message) error {
|
||||
header := make([]byte, 5)
|
||||
|
||||
65
pkg/proxy/dialer.go
Normal file
65
pkg/proxy/dialer.go
Normal 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
88
pkg/proxy/http.go
Normal 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
62
pkg/proxy/server.go
Normal 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
145
pkg/proxy/socks5.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user