You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

218 lines
5.7 KiB

package proxy
import (
"fmt"
"io"
"net"
"net/http"
"time"
"github.com/azoic/wormhole-client/pkg/logger"
)
// SOCKS5Proxy SOCKS5代理客户端
type SOCKS5Proxy struct {
serverAddr string
username string
password string
timeout time.Duration
}
// NewSOCKS5Proxy 创建SOCKS5代理客户端
func NewSOCKS5Proxy(serverAddr, username, password string, timeout time.Duration) *SOCKS5Proxy {
return &SOCKS5Proxy{
serverAddr: serverAddr,
username: username,
password: password,
timeout: timeout,
}
}
// CreateHTTPProxy 创建HTTP代理服务器
func (p *SOCKS5Proxy) CreateHTTPProxy(localPort int) *http.Server {
proxyHandler := &httpProxyHandler{
socks5Proxy: p,
}
server := &http.Server{
Addr: fmt.Sprintf(":%d", localPort),
Handler: proxyHandler,
}
return server
}
// httpProxyHandler HTTP代理处理器
type httpProxyHandler struct {
socks5Proxy *SOCKS5Proxy
}
func (h *httpProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
logger.Info("Processing request: %s %s", r.Method, r.URL.String())
if r.Method == http.MethodConnect {
h.handleHTTPSProxy(w, r)
} else {
h.handleHTTPProxy(w, r)
}
}
// handleHTTPSProxy 处理HTTPS代理请求 (CONNECT方法)
func (h *httpProxyHandler) handleHTTPSProxy(w http.ResponseWriter, r *http.Request) {
destConn, err := h.socks5Proxy.DialTCP(r.Host)
if err != nil {
logger.Error("Failed to connect via SOCKS5: %v", err)
http.Error(w, "Bad Gateway", http.StatusBadGateway)
return
}
defer destConn.Close()
w.WriteHeader(http.StatusOK)
hijacker, ok := w.(http.Hijacker)
if !ok {
logger.Error("Hijacking not supported")
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
clientConn, _, err := hijacker.Hijack()
if err != nil {
logger.Error("Failed to hijack connection: %v", err)
return
}
defer clientConn.Close()
// 双向数据转发
go h.copyData(clientConn, destConn)
h.copyData(destConn, clientConn)
}
// handleHTTPProxy 处理HTTP代理请求
func (h *httpProxyHandler) handleHTTPProxy(w http.ResponseWriter, r *http.Request) {
// 通过SOCKS5连接到目标服务器
destConn, err := h.socks5Proxy.DialTCP(r.Host)
if err != nil {
logger.Error("Failed to connect via SOCKS5: %v", err)
http.Error(w, "Bad Gateway", http.StatusBadGateway)
return
}
defer destConn.Close()
// 发送HTTP请求
if err := r.Write(destConn); err != nil {
logger.Error("Failed to write request: %v", err)
http.Error(w, "Bad Gateway", http.StatusBadGateway)
return
}
// 读取响应并返回给客户端
if _, err := io.Copy(w, destConn); err != nil {
logger.Error("Failed to copy response: %v", err)
}
}
// DialTCP 通过SOCKS5连接到目标地址
func (p *SOCKS5Proxy) DialTCP(address string) (net.Conn, error) {
// 连接到SOCKS5代理服务器
conn, err := net.DialTimeout("tcp", p.serverAddr, p.timeout)
if err != nil {
return nil, fmt.Errorf("failed to connect to SOCKS5 server: %v", err)
}
// 执行SOCKS5握手
if err := p.performSOCKS5Handshake(conn, address); err != nil {
conn.Close()
return nil, fmt.Errorf("SOCKS5 handshake failed: %v", err)
}
logger.Debug("Successfully connected to %s via SOCKS5 proxy", address)
return conn, nil
}
// performSOCKS5Handshake 执行SOCKS5握手协议
func (p *SOCKS5Proxy) performSOCKS5Handshake(conn net.Conn, targetAddr string) error {
// 简化的SOCKS5握手实现
// 实际项目中应该完整实现SOCKS5协议
// 发送认证方法选择
authMethods := []byte{0x05, 0x01, 0x02} // 版本5,1个方法,用户名密码认证
if _, err := conn.Write(authMethods); err != nil {
return fmt.Errorf("failed to send auth methods: %v", err)
}
// 读取服务器响应
response := make([]byte, 2)
if _, err := conn.Read(response); err != nil {
return fmt.Errorf("failed to read auth response: %v", err)
}
if response[0] != 0x05 || response[1] != 0x02 {
return fmt.Errorf("unsupported authentication method")
}
// 发送用户名密码
authData := []byte{0x01} // 子协议版本
authData = append(authData, byte(len(p.username)))
authData = append(authData, []byte(p.username)...)
authData = append(authData, byte(len(p.password)))
authData = append(authData, []byte(p.password)...)
if _, err := conn.Write(authData); err != nil {
return fmt.Errorf("failed to send credentials: %v", err)
}
// 读取认证结果
authResult := make([]byte, 2)
if _, err := conn.Read(authResult); err != nil {
return fmt.Errorf("failed to read auth result: %v", err)
}
if authResult[1] != 0x00 {
return fmt.Errorf("authentication failed")
}
// 发送连接请求
host, portStr, err := net.SplitHostPort(targetAddr)
if err != nil {
return fmt.Errorf("invalid target address: %v", err)
}
// 简化的连接请求(实际实现应该支持域名解析)
connectReq := []byte{0x05, 0x01, 0x00, 0x03} // 版本,连接命令,保留字段,域名类型
connectReq = append(connectReq, byte(len(host)))
connectReq = append(connectReq, []byte(host)...)
// 添加端口
portNum := 80 // 默认HTTP端口
if portStr != "" {
// 简化处理:如果端口是443则用443,否则用80
if portStr == "443" {
portNum = 443
}
}
connectReq = append(connectReq, byte(portNum>>8), byte(portNum&0xFF))
if _, err := conn.Write(connectReq); err != nil {
return fmt.Errorf("failed to send connect request: %v", err)
}
// 读取连接响应
connectResp := make([]byte, 10) // 简化的响应读取
if _, err := conn.Read(connectResp); err != nil {
return fmt.Errorf("failed to read connect response: %v", err)
}
if connectResp[1] != 0x00 {
return fmt.Errorf("connection failed, status: %d", connectResp[1])
}
return nil
}
// copyData 数据复制
func (h *httpProxyHandler) copyData(dst, src net.Conn) {
defer dst.Close()
defer src.Close()
io.Copy(dst, src)
}