Go语言实战案例&SOCKS5详解 | 豆包MarsCode AI 刷题

148 阅读9分钟

本节课主要利用三个小项目,来巩固go语言的语法并熟悉一些重要的包的使用

01-guessing-game

一个非常简单的猜数字的小游戏,主要用来巩固和熟悉Go语言中的输入输出,和math/read 包的使用

知识点:

  1. 随机数生成

在Go 1.20以前,使用rand.Seed用于设置全局随机数生成器的种子,以便于每次运行程序时获得不同的随机数序列,但从Go 1.20版本起,默认随机数生成器已不再需要显示调用rand.Seed

  • 如果需要一个特定的随机数序列(例如,为了可重复性或测试),可以通过rand.Newrand.NewSource(seed)创建一个局部随机数生成器,而不是调用全局的rand.Seed。这种方法不会影响到全局的随机数生成器,且更加灵活
  • 如果不需要特定的序列,可以直接使用全局的rand包函数,例如rand.Int()rand.Float64()等,而无需调用Seed
  1. bufio包

bufio包是一个Go语言中用于缓冲I/O操作的标准包。他通过将数据缓存到内存来减少系统调用的次数,从而提高文件、网络连接等I/O操作的效率。bufio包特别适用于需要频繁读取或写入小块数据的场景

常用类型:

bufio.Reader 提供了带缓冲的读取功能,适用于从 io.Reader 接口实现的数据源(如文件、网络连接)读取数据。常用方法包括:

  • ReadByte:读取一个字节。
  • ReadLine:读取一行数据。
  • ReadString:读取直到特定字符的字符串,例如读取到 '\n'。
  • Peek:查看缓冲区的下一个字节,不移动读取位置。
reader := bufio.NewReader(os.Stdin)
input, _ := reader.ReadString('\n')  // 读取一行输入直到换行符
fmt.Println("Input:", input)

bufio.Writer 提供了带缓冲的写入功能,可以写入到 io.Writer 接口实现的数据目标(如文件、网络连接)。常用方法包括:

  • WriteByte:写入一个字节。
  • WriteString:写入字符串。
  • Flush:将缓冲区中的数据写入到底层的输出目标中。
writer := bufio.NewWriter(os.Stdout)
writer.WriteString("Hello, World!") // 写入字符串到缓冲区
writer.Flush()                      // 强制将缓冲区数据写入输出

bufio.Scanner 是一个简单的逐行或逐词读取文本的工具,非常适合逐行读取文件或输入内容。常用方法包括:

  • Scan:读取下一个标记(默认是行)。
  • Text:获取最近一次读取到的文本。
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
    line := scanner.Text()
    fmt.Println("Line:", line)
}
  1. fmt包

fmt 包是 Go 语言标准库中的一个格式化 I/O 包,用于处理字符串格式化和标准输入输出。fmt 包提供了丰富的函数,支持格式化输出、字符串拼接、数据打印等操作,尤其适合处理各种数据类型的格式化输出。

具体内容请见标准文档

02-simpledict

一个调用外部API的小翻译词典,主要用来介绍Go语言中的HTTP服务,json序列化和反序列化

知识点:

  1. json与Go struct
type DictRequest struct {
	TransType string `json:"trans_type"`
	Source    string `json:"source"`
	UserID    string `json:"user_id"`
}
  1. 序列化与反序列化
  • 序列化
type DictRequest struct {
		TransType string `json:"trans_type"`
		Source    string `json:"source"`
		UserID    string `json:"user_id"`
}

func main() {
		request := DictRequest{TransType: "en2zh", Source: word}
		buf, err := json.Marshal(request)
}
  • 反序列化
// 假设读取到一个[]byte类型的 bodytext

type DictResponse struct {
		// ...
		// ...
}

var dictResponse DictResponse
err = json.Unmarshal(bodyText, &dictResponse)
  1. 获取命令行参数

在命令行调用时通过./myapp arg1 arg2go run main.go arg1 arg2来传递参数

可以直接使用os.Args来使用参数

03-proxy

实现一个SOCKS5代理服务的基本过程,涉及到net包的使用,利用context包实现goroutine的同步,SOCKS5协议

知识点:

net包

主要提供了函数来创建网络连接,相较于http包面向应用层的HTTP协议,net包主要面向连接层,提供了较为底层的网络服务,主要用于TCP与UDP的实现,具体请见标准文档

context包实现协程同步

...

// 创建一个可以取消的上下文
ctx, cancel := context.WithCancel(context.Background())
// 防御性coding
defer cancel()

// 向服务器转发client的请求
go func() {
		_, _ = io.Copy(dest, reader)  // Copy是封闭循环 只有当一方关闭连接才退出
		cancel()
}()

// 向client递交服务器的response
go func() {
		_, _ = io.Copy(conn, dest)  // Copy是封闭循环 只有当一方关闭连接才退出
		cancel()
}()

<-ctx.Done()

...

调用context.WithCancel(context.Background())生成一个新的context.Context和一个cancel函数

在合适的地方调用<-ctx.Done()来监听取消信号

当调用cancel函数时,ctx.Done()会被触发,收到信号并停止

SOCKS5协议

连接过程

SOCKS5代理建立连接流程包含三个主要阶段:

  1. 客户端与代理服务器建立连接
    • 客户端发送连接请求到SOCKS5代理服务器
    • 代理服务器接受客户端的请求后,等待客户端指定认证方法
  2. 协商认证方法
    • 客户端发送支持的认证方法列表,可能包含无验证、用户名/密码验证等
    • 代理服务器根据配置选择一种认证方法,返回给客户端。
    • 如果需要认证,客户端会发送对应认证信息,如user/passwd
    • 如果认证成功,代理服务器会继续连接请求,否则返回错误并中断连接
  3. 连接目标服务器
    • 一旦通过认证,客户端发送请求包括目标IP、端口和请求类型
    • 代理服务器根据请求类型建立到目标服务器的连接
    • 代理服务器和客户端进行数据传输时,SOCKS5代理仅作为中转,目标服务器对客户端不可见,达到隐藏真实IP的效果

socks5协议的主要请求类型

SOCKS5的支持的请求类型主要有三种:

  • CONNECT请求:用于建立TCP连接,适合HTTP、HTTPS等应用协议
  • BIND请求:用于被动的TCP连接,一般用于FTP等协议(少见)
  • UDP ASSOCIATE请求:用于UDP数据包传输,适合DNS查询,VoIP等

socks5数据包格式

SOCKS5数据包分为两种:协商请求

  1. 协商数据包: 用于身份验证流程,包括客户端发起的认证方法列表,以及代理返回的认证方法

客户端的协商数据包:

字节偏移量字段名称字节长度说明
0VER1协议版本号(0x05表示SOCKS5)
1NMETHODS1支持的认证方法数量
2METHODS1~255客户端支持的认证方法列表

VER:协议版本号,固定为0x05表示SOCKS5

NMETHODS:支持认证方法数量,为后续METHODS字段的长度

METHODS:列表中包括客户端支持的认证方法,每种方法用1字节表示:

  1. 0x00:不需要认证
  2. 0x01:GSSAPI认证
  3. 0x02:用户名密码认证
  4. 其他值为私有方法

服务器的响应协商数据包:

字节偏移量字段名称字节长度说明
0VER1协议版本号(0x05表示SOCKS5)
1METHOD1服务器选择的认证方法

VER:协议版本号,固定为0x05表示SOCKS5

METHOD:服务器选择的认证方法,与客户端提供的方法之一相匹配,如果服务器不支持任何方式,METHOD会返回0xFF,表示认证失败

  1. 请求数据包: 包括目标的IP地址、端口号、请求类型。根据socks5协议,目标地址可以是IPv4、IPv6或域名

客户端发送的请求数据包:

字节偏移量字段名称字节长度说明
0VER1协议版本号(0x05)
1CMD1请求类型
2RSV1保留字段,必须为0
3ATYP1地址类型(IPv4、IPv6或域名)
4DST.ADDR可变目标地址,根据ATYP字段的不同而变化
DST.PORT2目标端口,网络字节序(大段序)

VER:协议版本号

CMD:请求类型

  • 0x01:CONNECT请求,用于TCP连接
  • 0x02:BIND请求,用于监听连接(很少使用)
  • 0x03:UDP ASSOCIATE请求,用于UDP连接

RSV:保留字段,固定为0x00

ATYP:目标地址类型,指定目标地址的格式:

  • 0x01:IPv4地址,4字节
  • 0x03:域名,后跟1字节长度字段,和实际域名字符串。
  • 0x04:IPv6地址,16字节

DST.ADDR:目标地址,格式由ATYP决定

DST.PORT:目标端口,占用2字节

代理服务器的响应请求数据包

字节偏移量字段名称字节长度说明
0VER1协议版本号(0x05)
1REP1应答字段,表示请求结果
2RSV1保留字段,必须为0
3ATYP1地址类型(与请求数据包格式相同)
4DST.ADDR可变服务器绑定的地址
DST.PORT2服务器绑定的端口

VER:协议版本号,固定为 0x05。

REP:应答代码,表示请求的处理结果:

  • 0x00:成功。
  • 0x01:通用SOCKS服务器故障。
  • x02:连接不允许。
  • 0x03:网络不可达。
  • 0x04:主机不可达。
  • 0x05:连接被拒绝。
  • 0x06:TTL超时。
  • 0x07:不支持的命令。
  • 0x08:不支持的地址类型。
  • 其他:保留。

RSV:保留字段,固定为 0x00。

ATYP:地址类型,与请求数据包中的ATYP字段含义相同。

BND.ADDR:代理服务器绑定的地址,通常是代理服务器的IP地址或域名。

BND.PORT:代理服务器绑定的端口。

SOCKS5与其他代理的区别

  • 与HTTP代理:HTTP代理只支持HTTP/HTTPS流量,而socks5支持多种协议(TCP/UDP),适用范围更广。
  • 与SOCKS4代理:SOCKS4不支持认证和UDP流量,功能较为简单
  • 与VPN:VPN对全部网络流量进行加密,socks5本身不加密数据,需要和加密协议配合使用,socks5仅代理特定应用的流量,适合轻量代理

欢迎读者批评指正!!!