This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,11 +209,22 @@ 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 接受代理连接
|
||||||
@@ -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
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 // 新建代理连接请求
|
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
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