Go实战案例三 | 青训营笔记

73 阅读5分钟

这是我参与「第五届青训营」笔记创作活动的第5天!

案例三 SOCKS5代理

案例题目

Socks5概述

Socks5协议是一款广泛使用的代理协议,它在使用TCP/IP协议通讯的客户端和服务器之间扮演一个中介角色,使得内部网中的客户端变得能够访问Internet网中的服务器,或者使C/S(Client和Server)之间的通讯更加安全。SOCKS5 代理服务器通过将客户端发来的请求转发给真正的目标服务器, 模拟了一个客户端请求操作。在这里,客户端和SOCKS5代理服务器之间也是通过TCP/IP协议进行通讯,客户端将原本要发送给真正服务器的请求先发送给SOCKS5服务器,然后SOCKS5服务器再将请求转发给真正的服务器。

Socks5代理优点

  • 绕过互联网块

  • 没有程序,协议或流量限制

  • 更快,更可靠的连接

  • 错误减少,整体性能提升

  • 在P2P平台上表现更好

Socks5原理流程图(可分为4个阶段)

  1. 协商阶段
  2. 认证阶段
  3. 请求阶段
  4. relay阶段 image.png

思路分析

1. TCP-echo sever搭建

具体代码实现 主要是process函数的实现

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}) //写入(这里[]byte做强制类型转换)
		if err != nil {
			break
		}
	}
}

运行后用nc命令进行测试(我的电脑好像nc命令没法识别。。希望懂的友友给点建议)

2. 认证阶段auth函数

修改process函数删掉死循环

func process(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)
	err := auth(reader, conn)
	if err != nil {
		log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
		return
	}
	log.Println("auth success")
}

Socks5认证格式

	// +----+----------+----------+
	// |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 | METHOD |
	// +----+--------+
	// | 1  |   1    |
	// +----+--------+
	// VER: 协议版本,socks5为0x05 
        // NMETHOD: 服务器端匹配结果

认证方式:

  • 0x00: 无验证需求
  • 0x01: GSSAPI,通用安全服务应用程序接口
  • 0x02: USERNAME&PASSWORD,用户名密码验证
  • 0x03 - 0x07: IANA ASSIGNED,IANA分配
  • 0x80 - 0xfe: RESERVED FOR PRIVATE METHODS,私人方法保留
  • 0xff: NO ACCEPTABLE METHODS,无可接受方法

如果服务器返回是0xff,则表明服务端不支持客户端所有的认证方式,也就是认证失败。 而如果你不需要认证,则直接发送0x00即可

3. 请求阶段格式

// +----+-----+-------+------+----------+----------+
// |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个字节

对于CMD字段:

  • 0x01: CONNECT请求
  • 0x02: BIND请求
  • 0x03: UDP转发

对于ATYP字段

  • 0x01: IPv4地址,DST.ADDR部分4字节长度
  • 0x03: 域名,DST.ADDR部分第一个字节为域名长度,DST.ADDR剩余的内容为域名,没有\0结尾
  • 0x04: IPv6地址,16个字节长度

4. 返回阶段格式

// +----+-----+-------+------+----------+----------+
// |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

对于REP应答字段: 除了0x00其他都是出现错误

  • 0x00: 成功
  • 0x01: 普通SOCKS服务器连接失败
  • 0x02: 现有规则不允许连接
  • 0x03: 网络不可达
  • 0x04: 主机不可达
  • 0x05: 连接被拒
  • 0x06: TTL超时
  • 0x07: 不支持的命令
  • 0x08: 不支持的地址类型
  • 0x09 - 0xff: 未定义

对于ATYP字段

  • 0x01: IPv4地址,DST.ADDR部分4字节长度
  • 0x03: 域名,DST.ADDR部分第一个字节为域名长度,DST.ADDR剩余的内容为域名,没有\0结尾
  • 0x04: IPv6地址,16个字节长度

总结

SOCKS5这部分运用了许多计算机网络中的内容,包含了TCP三握手和建立连接等知识。与SOCKS5协议不同,HTTP代理是通过HTTP协议进行的,HTTP代理服务器软件了解通讯包的内部结构,在转发过程中还要对通讯进行某种程度的修改和转换。和HTTP代理协议不同,SOCKS5实际上是一个传输层的代理协议OCKS工作在比HTTP代理更低的层次,SOCKS使用握手协议来通知代理软件其客户端试图进行SOCKS连接,然后尽可能透明的进行操作。也就是说,SOCKS希望的是充当一个透明的代理中介,尽可能的不对请求数据进行过多的更改。而常规的代理(如HTTP)可能会解释和重写报头。

GO语言学习路线

完整的Go语言学习流程图如下,保持谦虚,保持努力! image.png