Go 语言的实战案例 SOCKS5 代理 | 青训营

news/2024/11/13 3:32:05/

Powered by:NEFU AB-IN

文章目录

  • Go 语言的实战案例 SOCKS5 代理 | 青训营
    • 引入
    • TCP echo server
    • auth 认证
    • 请求阶段
    • relay阶段

Go 语言的实战案例 SOCKS5 代理 | 青训营

  • GO语言工程实践课后作业:实现思路、代码以及路径记录

引入

代理是指在计算机网络中,代理服务器充当客户端和目标服务器之间的中介。它接收来自客户端的请求,然后将请求转发给目标服务器,再将目标服务器的响应返回给客户端。

用途

  • 匿名浏览:Socks5代理可以隐藏客户端的真实IP地址,使得客户端在访问目标服务器时更加匿名。可以代表客户端向其他服务器发送请求

    • 在网络爬虫中,使用代理服务器可以解决IP访问频率限制问题,因为它们可以隐藏真实的客户端IP地址并提供新的IP地址来进行请求,从而减轻对单个IP的访问频率限制。这样,爬虫就可以通过多个代理IP来轮流发送请求,避免被目标服务器封禁。
  • 数据加密:Socks5代理支持对数据流进行加密传输,提供更高的数据安全性。

  • 跨协议代理:Socks5代理不仅支持HTTP协议,还支持包括FTP、SMTP等在内的多种协议,实现了更广泛的应用场景。

  • 穿越防火墙:由于Socks5代理更通用,不受网络防火墙的限制,可以更容易地穿越网络防火墙,访问被封锁或限制的资源。

Socks5代理是一种代理协议,用于在网络上进行数据传输。Socks5代理服务器可以支持多种协议的代理,包括HTTP、FTP、SMTP等。与HTTP代理相比,Socks5代理更加灵活和通用。

主要区别如下:

  • HTTP代理:主要用于HTTP请求,支持浏览器的代理设置,透明代理
  • Socks5代理:支持HTTP以外的其他协议的代理,不支持浏览器的代理设置,需要在应用程序中设置代理,Socks5代理的主要特点是它可以在客户端和代理服务器之间建立一个 TCP 或 UDP 连接,并直接将数据转发给目标服务器,而不需要解析或修改数据包。这种特性使得SOCKS5代理适用于很多不同类型的网络应用,如 P2P 文件共享、实时视频传输等。

代理过程通常如下:

  1. 客户端向代理服务器发送连接请求,并指定目标服务器的地址。
  2. 代理服务器接收到请求后,解析目标服务器地址,并建立与目标服务器的连接。
  3. 代理服务器将客户端的请求转发给目标服务器。
  4. 目标服务器接收到请求后,处理请求并生成响应。
  5. 代理服务器将目标服务器的响应转发给客户端。
  6. 客户端接收到代理服务器返回的响应,并进行处理。

image.png
总的来说,代理服务器充当了客户端和目标服务器之间的中转站,使得客户端能间接与目标服务器进行通信,提供了更多的隐私保护和功能扩展。

TCP echo server

/** @Author: NEFU AB-IN* @Date: 2023-08-23 22:36:26* @FilePath: \GoTest\6\6.go* @LastEditTime: 2023-08-23 22:43:57*/
package mainimport ("bufio""log""net"
)// TCP echo serverfunc main() {server, err := net.Listen("tcp", "127.0.0.1:1080") // 监听端口,返回一个serverif err != nil {panic(err)}for {client, err := server.Accept() // 在死循环里,用这个方法接收一个请求if err != nil {log.Printf("Accept failed %v", err)continue}go process(client) // 处理这个链接 go关键字 启动子线程去处理// 如果接受连接时出现错误,服务器会记录错误信息,然后继续等待下一个客户端连接// 否则为每个客户端连接启动一个独立的process协程来处理客户端请求,这样可以并发处理多个客户端连接。}
}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}}
}

image.png

auth 认证

认证阶段:实现auth函数,更改process函数,里面用到auth函数

/** @Author: NEFU AB-IN* @Date: 2023-08-23 22:50:05* @FilePath: \GoTest\7\7.go* @LastEditTime: 2023-08-23 22:50:11*/
package mainimport ("bufio""fmt""io""log""net"
)
// 一些常量定义,用于表示Socks5协议中的一些数值,例如协议版本、命令类型和地址类型等。
const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04func 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)err := auth(reader, conn)if err != nil {log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)return}log.Println("auth success")
}func auth(reader *bufio.Reader, conn net.Conn) (err error) {// 第一步:浏览器向代理服务器发送报文,三个字段// +----+----------+----------+// |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)}if ver != socks5Ver {return fmt.Errorf("not supported ver:%v", ver)}methodSize, err := reader.ReadByte()if err != nil {return fmt.Errorf("read methodSize failed:%w", err)}method := make([]byte, methodSize) // 创建缓冲区_, err = io.ReadFull(reader, method) // 它会从r中连续读取数据,直到buf被填满,或者遇到了一个错误。如果读取完整的buf之前发生错误,函数会返回读取的字节数n和错误信息err;如果成功读取了整个buf,函数会返回buf的长度和nil。
// 这个函数在需要确保读取完整数据包时很有用,比如读取固定长度的消息、解码二进制数据等。if err != nil {return fmt.Errorf("read method failed:%w", err)}log.Println("ver", ver, "method", method)// +----+--------+// |VER | METHOD |// +----+--------+// | 1  |   1    |// +----+--------+_, err = conn.Write([]byte{socks5Ver, 0x00})if err != nil {return fmt.Errorf("write failed:%w", err)}return nil
}

请求阶段

请求阶段:实现connect函数,签名一致,在process中调用

package mainimport ("bufio""encoding/binary""errors""fmt""io""log""net"
)const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04func 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)err := auth(reader, conn)if err != nil {log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)return}err = connect(reader, conn)if err != nil {log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)return}
}func auth(reader *bufio.Reader, conn net.Conn) (err error) {// +----+----------+----------+// |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/PASSWORDver, err := reader.ReadByte()if err != nil {return fmt.Errorf("read ver failed:%w", err)}if ver != socks5Ver {return fmt.Errorf("not supported ver:%v", ver)}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)}// +----+--------+// |VER | METHOD |// +----+--------+// | 1  |   1    |// +----+--------+_, err = conn.Write([]byte{socks5Ver, 0x00})if err != nil {return fmt.Errorf("write failed:%w", err)}return nil
}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个字节// !!!! 可以看到前四个字段都是单字节,可以采用之前的方法一个个读出来,这里直接构造一个4字节的缓冲区,用readfull全读出来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]if ver != socks5Ver {return fmt.Errorf("not supported ver:%v", ver)}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: // 先不实现IPV6return 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])  // 用于将 2 字节的大端序字节序列解码为一个无符号 16 位整数(uint16)。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}) // 拼成byte数组if err != nil {return fmt.Errorf("write failed: %w", err)}return nil
}

relay阶段

relay阶段:最后一步,真正和服务器建立TCP连接,在 connect 函数中,增加功能

package mainimport ("bufio""context""encoding/binary""errors""fmt""io""log""net"
)const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04func 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)err := auth(reader, conn)if err != nil {log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)return}err = connect(reader, conn)if err != nil {log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)return}
}func auth(reader *bufio.Reader, conn net.Conn) (err error) {// +----+----------+----------+// |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/PASSWORDver, err := reader.ReadByte()if err != nil {return fmt.Errorf("read ver failed:%w", err)}if ver != socks5Ver {return fmt.Errorf("not supported ver:%v", ver)}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)}// +----+--------+// |VER | METHOD |// +----+--------+// | 1  |   1    |// +----+--------+_, err = conn.Write([]byte{socks5Ver, 0x00})if err != nil {return fmt.Errorf("write failed:%w", err)}return nil
}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个字节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]if ver != socks5Ver {return fmt.Errorf("not supported ver:%v", ver)}if cmd != cmdBind {return fmt.Errorf("not supported cmd:%v", cmd)}addr := ""switch atyp {case atypeIPV4:_, 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: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])// 建立浏览器和服务器的双向数据转换,使用 `net.Dial` 函数连接到目标服务器 `dest`,即客户端所请求的远程服务器。这样就建立了代理服务器与目标服务器之间的连接。dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))if err != nil {return fmt.Errorf("dial dst failed:%w", err)}defer dest.Close()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)}// 这段代码的主要功能是实现两个goroutine并行执行io.Copy操作,并在其中一个goroutine完成后取消另一个goroutine的执行。
//- 第一行创建一个新的上下文对象ctx,并使用context.Background()作为根上下文对象。这个上下文对象用于在不同goroutine之间传递取消信号。
//- 第二行定义了一个cancel函数,用于取消上下文对象ctx。defer关键字用于延迟执行cancel函数,在函数返回时自动调用。
//- 第四行开启了一个goroutine,该goroutine负责将读取到的数据从reader复制到dest,并在复制完成后调用cancel函数取消上下文对象的执行。这样做是为了在复制完成后,能正确地通知另一个goroutine停止执行。
//- 第六行开启了第二个goroutine,该goroutine负责将dest的数据复制到conn,并在复制完成后也调用cancel函数取消上下文对象的执行。
//- 第九行使用<-ctx.Done()语法,表示等待上下文对象ctx被取消。一旦ctx被取消,即ctx.Done()返回,程序会继续往下执行。ctx, cancel := context.WithCancel(context.Background())defer cancel()go func() {_, _ = io.Copy(dest, reader)cancel()}()go func() {_, _ = io.Copy(conn, dest)cancel()}()<-ctx.Done() // 阻塞等待,直到两个数据转发的Go协程都完成后,函数返回。执行时机,也就是cancel的时间。return nil
}

ctx, cancel := context.WithCancel(context.Background())解释

  • 上下文对象是 Go 语言中用于跨 goroutine 传递请求的值、截止时间以及取消信号的一种机制。它可以用来解决在并发环境下协调多个 goroutine 之间的操作和通信的问题。

  • 上下文对象通常用于以下几种情况:

    1. 传递请求的相关值:例如请求ID、用户信息等,在多个 goroutine 之间共享。
    2. 设置截止时间:对某些操作设置一个超时时间,用于控制操作的执行时间。
    3. 取消信号:当一个操作出现问题或者不再需要执行时,可以通过取消上下文对象来通知相关的 goroutine 停止执行。
  • context.WithCancel(context.Background()) 这句代码的意思是创建一个具有取消功能的上下文对象(WithCancel()函数接受一个 Context 并返回其子Context和取消函数cancel)。context.Background() 创建一个根上下文对象,而 context.WithCancel 则基于根上下文对象创建一个新的上下文对象,该上下文对象具有取消功能。返回的 ctx 和 cancel 是用于取消该上下文对象的函数。

  • 所以这句代码的作用就是创建一个新的上下文对象 ctx,并使用 context.WithCancel 函数将其绑定到一个取消函数 cancel 上。这样可以通过调用 cancel 函数来取消该上下文对象的执行。该上下文对象可以被传递给其他需要依赖上下文的 goroutine 来实现协作和取消的功能。

image.png


http://www.ppmy.cn/news/1059786.html

相关文章

【随笔】如何使用阿里云的OSS保存基础的服务器环境

使用阿里云OSS创建一个存储仓库&#xff1a;bucket 在Linux上下载并安装阿里云的ossutil工具 // 命令行&#xff0c;是linux环境 3. 安装ossutil。sudo -v ; curl https://gosspublic.alicdn.com/ossutil/install.sh | sudo bash 说明:安装过程中&#xff0c;需要使用解压工具…

iptables的使用规则

环境中为了安全要限制swagger的访问&#xff0c;最简单的方式是通过iptables防火墙设置规则限制。 在测试服务器中设置访问swagger-ui.html显示如下&#xff0c;区分大小写&#xff1a; iptables设置限制访问9783端口的swagger字段的请求&#xff1a; iptables -A INPUT -p t…

【图像分割】实现snake模型的活动轮廓模型以进行图像分割研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

opencv 案例实战02-停车场车牌识别SVM模型训练及验证

1. 整个识别的流程图&#xff1a; 2. 车牌定位中分割流程图&#xff1a; 三、车牌识别中字符分割流程图&#xff1a; 1.准备数据集 下载车牌相关字符样本用于训练和测试&#xff0c;本文使用14个汉字样本和34个数字跟字母样本&#xff0c;每个字符样本数为40&#xff0c;样本尺…

【Python】P0 本系列博文简介与大纲

Python 前言本系列博文适合谁本系列博文不适合谁本系列博文大纲 前言 本系列博文基于《Python Cookbook》一书&#xff0c;Python 3 版本&#xff1b;本系列博文的目标不是为了构建一个 Python 知识大全&#xff0c;而是为了那些需要快速将 Python 学以致用的相关人员&#xf…

【计算机网络】HTTPs 传输流程

HTTPS和HTTP的区别 1、HTTP协议传输的数据都是未加密的&#xff0c;是明文的&#xff0c;使用HTTP协议传输隐私信息非常不安 HTTPS协议是由SSLHTTP协议构建的可进行加密传输、身份认证的网络协议&#xff0c;要比http协议安全。 2、HTTPS协议需要到CA申请证书&#xff0c;一般…

Python“牵手”lazada商品列表数据,关键词搜索lazadaAPI接口数据,lazadaAPI接口申请指南

lazada平台API接口是为开发电商类应用程序而设计的一套完整的、跨浏览器、跨平台的接口规范&#xff0c;lazadaAPI接口是指通过编程的方式&#xff0c;让开发者能够通过HTTP协议直接访问lazada平台的数据&#xff0c;包括商品信息、店铺信息、物流信息等&#xff0c;从而实现la…

【给自己挖个坑】三维视频重建(NSR技术)-KIRI Engine

文章目录 以下是我和AI的对话通过手机拍摄物体的视频&#xff0c;再根据视频生成三维模型&#xff0c;这个可实现吗我想开发类似上面的手机应用程序&#xff0c;如何开发呢 看了以上回答&#xff0c;还是洗洗睡吧NSR技术的实现原理是什么呢有案例吗我是名Java工程师&#xff0c…