侧边栏壁纸
博主头像
BvBeJ的小站 博主等级

行动起来,活在当下

  • 累计撰写 38 篇文章
  • 累计创建 1 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录
Go

使用 Go 实现的简单 SOCKS5 代理示例

BvBeJ
2025-12-10 / 0 评论 / 0 点赞 / 2 阅读 / 0 字
package main

import (
	"encoding/binary"
	"fmt"
	"io"
	"log"
	"net"
	"sync/atomic"
	"time"
)

const (
	socksVersion5       = 0x05
	socksCmdConnect     = 0x01
	socksAddrTypeIPv4   = 0x01
	socksAddrTypeDomain = 0x03
	socksAddrTypeIPv6   = 0x04
)

// 带计数的 Reader
type countingReader struct {
	r io.Reader
	n *int64
}

func (cr *countingReader) Read(p []byte) (int, error) {
	n, err := cr.r.Read(p)
	if n > 0 {
		atomic.AddInt64(cr.n, int64(n))
	}
	return n, err
}

// 带计数的 Writer
type countingWriter struct {
	w io.Writer
	n *int64
}

func (cw *countingWriter) Write(p []byte) (int, error) {
	n, err := cw.w.Write(p)
	if n > 0 {
		atomic.AddInt64(cw.n, int64(n))
	}
	return n, err
}

func main() {
	addr := ":1080" // 监听端口
	l, err := net.Listen("tcp", addr)
	if err != nil {
		log.Fatalf("listen %s failed: %v", addr, err)
	}
	log.Printf("SOCKS5 proxy listening on %s", addr)

	for {
		conn, err := l.Accept()
		if err != nil {
			log.Printf("accept error: %v", err)
			continue
		}
		go handleConn(conn)
	}
}

func handleConn(c net.Conn) {
	defer c.Close()

	start := time.Now()
	clientAddr := c.RemoteAddr().String()

	var upBytes, downBytes int64 // 上行/下行字节数

	// SOCKS5 握手
	if err := socks5Handshake(c); err != nil {
		log.Printf("[%s] handshake error: %v", clientAddr, err)
		return
	}

	targetConn, targetAddr, err := socks5HandleRequest(c)
	if err != nil {
		log.Printf("[%s] request error: %v", clientAddr, err)
		return
	}
	defer targetConn.Close()

	log.Printf("[%s] connected to %s", clientAddr, targetAddr)

	// 使用计数器包装
	// 上行:client -> target
	clientReader := &countingReader{r: c, n: &upBytes}
	targetWriter := &countingWriter{w: targetConn, n: &upBytes}

	// 下行:target -> client
	targetReader := &countingReader{r: targetConn, n: &downBytes}
	clientWriter := &countingWriter{w: c, n: &downBytes}

	// 双向拷贝
	errCh := make(chan error, 2)

	go func() {
		_, err := io.Copy(targetWriter, clientReader)
		errCh <- err
	}()

	go func() {
		_, err := io.Copy(clientWriter, targetReader)
		errCh <- err
	}()

	// 等待任意一边结束
	<-errCh

	duration := time.Since(start)
	log.Printf("[%s] %s closed, up=%.2f KB, down=%.2f KB, duration=%s",
		clientAddr,
		targetAddr,
		float64(upBytes)/1024.0,
		float64(downBytes)/1024.0,
		duration,
	)
}

// 简单 SOCKS5 握手:只支持 NO AUTH (0x00)
func socks5Handshake(c net.Conn) error {
	buf := make([]byte, 258) // VER + NMETHODS + 255 METHODS
	// 读取 VER、NMETHODS
	if _, err := io.ReadFull(c, buf[:2]); err != nil {
		return fmt.Errorf("read head: %w", err)
	}
	if buf[0] != socksVersion5 {
		return fmt.Errorf("unsupported socks version: %d", buf[0])
	}
	nMethods := int(buf[1])
	if nMethods == 0 {
		return fmt.Errorf("no methods")
	}
	if _, err := io.ReadFull(c, buf[:nMethods]); err != nil {
		return fmt.Errorf("read methods: %w", err)
	}

	// 我们只接受 NO AUTH (0x00)
	// 直接回复服务端选择 0x00
	resp := []byte{socksVersion5, 0x00}
	if _, err := c.Write(resp); err != nil {
		return fmt.Errorf("write method selection: %w", err)
	}
	return nil
}

// 处理 SOCKS5 请求,只实现 CONNECT
func socks5HandleRequest(c net.Conn) (net.Conn, string, error) {
	header := make([]byte, 4)
	if _, err := io.ReadFull(c, header); err != nil {
		return nil, "", fmt.Errorf("read request header: %w", err)
	}

	ver, cmd, _, atyp := header[0], header[1], header[2], header[3]
	if ver != socksVersion5 {
		return nil, "", fmt.Errorf("invalid version: %d", ver)
	}
	if cmd != socksCmdConnect {
		replySocks5(c, 0x07, nil) // Command not supported
		return nil, "", fmt.Errorf("unsupported cmd: %d", cmd)
	}

	// 解析地址
	var host string
	switch atyp {
	case socksAddrTypeIPv4:
		addr := make([]byte, 4)
		if _, err := io.ReadFull(c, addr); err != nil {
			return nil, "", fmt.Errorf("read ipv4: %w", err)
		}
		host = net.IP(addr).String()
	case socksAddrTypeDomain:
		var l [1]byte
		if _, err := io.ReadFull(c, l[:]); err != nil {
			return nil, "", fmt.Errorf("read domain len: %w", err)
		}
		domain := make([]byte, l[0])
		if _, err := io.ReadFull(c, domain); err != nil {
			return nil, "", fmt.Errorf("read domain: %w", err)
		}
		host = string(domain)
	case socksAddrTypeIPv6:
		addr := make([]byte, 16)
		if _, err := io.ReadFull(c, addr); err != nil {
			return nil, "", fmt.Errorf("read ipv6: %w", err)
		}
		host = net.IP(addr).String()
	default:
		replySocks5(c, 0x08, nil) // Address type not supported
		return nil, "", fmt.Errorf("unsupported atyp: %d", atyp)
	}

	// 读取端口
	var portBuf [2]byte
	if _, err := io.ReadFull(c, portBuf[:]); err != nil {
		return nil, "", fmt.Errorf("read port: %w", err)
	}
	port := binary.BigEndian.Uint16(portBuf[:])
	target := fmt.Sprintf("%s:%d", host, port)

	// 连接目标
	targetConn, err := net.Dial("tcp", target)
	if err != nil {
		replySocks5(c, 0x05, nil) // Connection refused
		return nil, "", fmt.Errorf("connect target %s failed: %w", target, err)
	}

	// 成功:回复客户端
	// 这里 bind addr 使用 0.0.0.0:0
	bindAddr := &net.TCPAddr{IP: net.IPv4zero, Port: 0}
	if err := replySocks5(c, 0x00, bindAddr); err != nil {
		targetConn.Close()
		return nil, "", fmt.Errorf("reply failed: %w", err)
	}

	return targetConn, target, nil
}

// 回复 SOCKS5 请求
func replySocks5(c net.Conn, rep byte, bindAddr *net.TCPAddr) error {
	if bindAddr == nil {
		// 默认 0.0.0.0:0
		bindAddr = &net.TCPAddr{IP: net.IPv4zero, Port: 0}
	}
	buf := make([]byte, 10)
	buf[0] = socksVersion5
	buf[1] = rep
	buf[2] = 0x00 // RSV
	buf[3] = socksAddrTypeIPv4
	copy(buf[4:8], bindAddr.IP.To4())
	binary.BigEndian.PutUint16(buf[8:10], uint16(bindAddr.Port))

	_, err := c.Write(buf)
	return err
}
0

评论区