引言
在网络通信中,代理服务器扮演着重要角色。SOCKS5代理是一种常用的网络代理协议,它可以在应用层与传输层之间转发TCP/UDP流量,实现用户在局域网或公网之间的匿名访问和数据传输。本文将介绍如何使用Go语言实现一个简单的SOCKS5代理服务器。
技术栈
本项目将使用以下技术栈进行实现:
- Go语言:Go语言提供了强大的标准库和并发模型,适合构建高性能的网络应用程序。
- 网络编程:使用Go语言标准库中的
net
包进行网络通信和处理。 - 协议解析:通过解析SOCKS5代理协议实现代理服务器与客户端之间的交互。
- 配置管理:使用Go语言的配置库,实现代理服务器的配置文件管理。
项目实现步骤
【原理】
- 了解一下 socks5 协议的工作原理。正常浏览器访问一个网站,如果不经过代理服务器的话,就是先和对方的网站建立 TCP 连接,然后三次握手,握手完之后发起 HTTP 请求,然后服务返回 HTTP 响应。如果设置代理服务器之后,流程会变得复杂一些。
- 首先是浏览器和 socks5 代理建立 TCP 连接,代理再和真正的服务器建立 TCP 连接。
这里可以分成四个阶段
1. 握手阶段
- 浏览器与Socks5代理服务器建立TCP连接。
- 浏览器发送一个初始请求给代理服务器,请求建立Socks5代理连接。
- 代理服务器回复客户端,表示准备好建立代理连接。
- 握手阶段完成。
创建TCP ehco server,代码如下
package main
import (
"bufio"
"log"
"net"
)
func main() {
// 监听地址和端口
server, err := net.Listen("tcp", "127.0.0.1:1080")
if err != nil {
panic(err)
}
for {
// 接受客户端连接
client, err := server.Accept()
if err != nil {
log.Printf("Accept failed %v", err)
continue
}
// 启动协程处理客户端连接
go process(client)
}
}
func process(conn net.Conn) {
// 处理完连接后关闭连接
defer conn.Close()
reader := bufio.NewReader(conn)
for {
// 读取客户端发送的数据
b, err := reader.ReadByte()
if err != nil {
break
}
// 将读取到的数据写入到客户端连接中,实现中继功能
_, err = conn.Write([]byte{b})
if err != nil {
break
}
}
}
- 在终端输入nc命令:
nc 127.0.0.1 1080
当我们输入hello 代理服务器会返回输入的值
2. 认证阶段
- 如果在握手阶段,Socks5代理服务器要求进行身份验证,则浏览器发送包含认证方法的请求给代理服务器。
- 代理服务器从浏览器发送的认证方法列表中选择一种方法,并回复浏览器确认的方法。
- 如果方法需要进行用户名和密码认证,则浏览器发送用户名和密码给代理服务器。
- 代理服务器验证身份信息,如果认证成功,则认证阶段完成。
func auth(reader *bufio.Reader, conn net.Conn) (err error) {
// 读取协议版本(VER)
// +----+----------+----------+
// |VER | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+
// VER: 协议版本,socks5为0x05
// NMETHODS: 支持认证的方法数量
// METHODS: 对应NMETHODS,NMETHODS的值为多少,METHODS就有多少个字节。RFC预定义了一些值的含义,内容如下:
// X’00’ NO AUTHENTICATION REQUIRED
// X’02’ USERNAME/PASSWORD
ver, err := reader.ReadByte()
if err != nil {
return fmt.Errorf("read ver failed:%w", err)
}
// 检查协议版本是否为 SOCKS5
if ver != socks5Ver {
return fmt.Errorf("not supported ver:%v", ver)
}
// 读取支持的认证方法数(NMETHODS)
methodSize, err := reader.ReadByte()
if err != nil {
return fmt.Errorf("read methodSize failed:%w", err)
}
// 读取每个认证方法的字节
method := make([]byte, methodSize)
_, err = io.ReadFull(reader, method)
if err != nil {
return fmt.Errorf("read method failed:%w", err)
}
log.Println("ver", ver, "method", method)
// 回复客户端的协议版本和认证方法(VER, METHOD)
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
_, err = conn.Write([]byte{socks5Ver, 0x00})
if err != nil {
return fmt.Errorf("write failed:%w", err)
}
return nil
}
3. 请求阶段
- 浏览器发送一个请求给代理服务器,该请求指定要访问的目标网站的地址和端口号。
- 代理服务器接收到请求后,与目标网站建立TCP连接。
- 代理服务器向目标网站发送请求,并将目标网站的响应返回给浏览器。
- 请求阶段完成后,浏览器将通过代理服务器获得目标网站返回的HTTP响应。
func connect(reader *bufio.Reader, conn net.Conn) (err error) {
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// VER 版本号,socks5的值为0x05
// CMD 0x01表示CONNECT请求
// RSV 保留字段,值为0x00
// ATYP 目标地址类型,DST.ADDR的数据对应这个字段的类型。
// 0x01表示IPv4地址,DST.ADDR为4个字节
// 0x03表示域名,DST.ADDR是一个可变长度的域名
// DST.ADDR 一个可变长度的值
// DST.PORT 目标端口,固定2个字节
// 读取协议头部字段,包括协议版本(VER)、命令(CMD)、目标地址类型(ATYP)
buf := make([]byte, 4)
_, err = io.ReadFull(reader, buf)
if err != nil {
return fmt.Errorf("read header failed:%w", err)
}
ver, cmd, atyp := buf[0], buf[1], buf[3]
// 检查协议版本是否为 SOCKS5
if ver != socks5Ver {
return fmt.Errorf("not supported ver:%v", ver)
}
// 检查命令是否为 CONNECT 请求
if cmd != cmdBind {
return fmt.Errorf("not supported cmd:%v", cmd)
}
addr := ""
switch atyp {
case atypeIPV4:
// 读取 IPv4 地址
_, err = io.ReadFull(reader, buf)
if err != nil {
return fmt.Errorf("read atyp failed:%w", err)
}
addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])
case atypeHOST:
// 读取域名地址
hostSize, err := reader.ReadByte()
if err != nil {
return fmt.Errorf("read hostSize failed:%w", err)
}
host := make([]byte, hostSize)
_, err = io.ReadFull(reader, host)
if err != nil {
return fmt.Errorf("read host failed:%w", err)
}
addr = string(host)
case atypeIPV6:
// 返回不支持 IPv6的信息
return errors.New("IPv6: no supported yet")
default:
return errors.New("invalid atyp")
}
// 读取目标端口
_, err = io.ReadFull(reader, buf[:2])
if err != nil {
return fmt.Errorf("read port failed:%w", err)
}
port := binary.BigEndian.Uint16(buf[:2])
log.Println("dial", addr, port) //只是输出日志
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// VER socks版本,这里为0x05
// REP Relay field,内容取值如下 X’00’ succeeded
// RSV 保留字段
// ATYPE 地址类型
// BND.ADDR 服务绑定的地址
// BND.PORT 服务绑定的端口DST.PORT
// 回复客户端,表示连接成功
_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
if err != nil {
return fmt.Errorf("write failed: %w", err)
}
return nil
}
4. 中继(relay)阶段
- 一旦请求阶段成功,代理服务器开始在浏览器和目标网站之间中继数据。
- 客户端发送的数据被代理服务器转发给目标网站,目标网站的响应被代理服务器转发给浏览器。
- 中继阶段持续进行,直到浏览器或目标网站关闭连接或代理服务器检测到连接超时。
SwitchyOmega插件
- 浏览器安装SwitchyOmega插件
- 新建情景模式
- 代理协议
SOCKS5
代理服务器127.0.0.1
代理端口1080
- 运行编写的代码
- 连接方式选择刚刚新建的情景模式,即可调用代理服务器
预期效果
通过完成该项目,读者可以了解SOCKS5代理协议的基本原理和实现方式,掌握Go语言网络编程的基础知识,同时也可以通过该项目的代码和实现思路,进行二次开发和扩展,实现更加复杂的代理功能。
完整代码
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END