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

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
}