Golang搭建SOCKS5代理 | 青训营笔记

737 阅读7分钟

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

前言

这里主要对前面的做一个补充,回头看了一下觉得有必要记录一下,SOCKS5也是以前只是眼熟,但是没有具体了解过的。

SOCKS5

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

SOCKS5原理

image.png

首先是浏览器和socks5代理建立TCP连接,代理再和真正的服务器建立TCP连接。这里可以分成四个阶段,握手阶段、认证阶段、请求阶段、relay 阶段。

第一个握手阶段,浏览器会向socks5代理发送请求,包的内容包括一个协议的版本号,还有支持的认证的种类,socks5服务器会选中一个认证方式,返回给浏览器。如果返回的是00的话就代表不需要认证,返回其 类型的话会开始认证流程

认证流程直接省略,这里实现一个不加密的

第三个阶段是请求阶段,认证通过之后浏览器会通过socks5服务器发起请求。主要信息包括版本号,请求的类型,一般主要是connection请求,就代表代理服务器要和某个域名或者某个IP地址某个端口建立TCP连接。 代理服务器收到响应之后,会真正和后端服务器建立连接,然后返回一个响应

第四个阶段是relay阶段。此时浏览器会发送正常发送请求,然后代理服务器接收到请求之后,会直接把请求转换到真正的服务器上。 然后如果真正的服务器以后返回响应的话,那么也会把请求转发到浏览器这边。今 后实际上代理服务器并不关心流量的细节,可以是HTTP流量, 也可以是其它TCP流量。 这个就是socks5协议的工作原理

SOCKS5的优点

1)绕过互联网块

由于代理服务器充设备和互联网之间的中继,因此它们可以轻松绕过互联网块。例如,如果客户端IP被某个网站列入黑名单(或者使用VPN并且其服务器IP已被列入黑名单),则可以通过SOCKS5代理路由客户端的流量,从而绕过此块。但是,它无法规避国家防火墙,因为大多数防火墙都使用深度数据包检测(DPI)。这意味着客户端的ISP在到达网站之前就已经阻止了客户端的流量。

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

与只能解释和使用HTTP和HTTPS网页的HTTP代理不同,SOCKS5代理可以处理任何流量。HTTP代理是通常为特定协议设计的高级代理。虽然这意味着可以获得更好的连接速度,但它们并不像SOCKS代理那样灵活和安全。 SOCKS代理是低级代理,可以处理任何程序或协议以及任何流量。

3)更快,更可靠的连接

与仅使用TCP协议的前代产品不同,SOCKS5代理服务器可以使用UDP协议,确保可靠的连接和高效的性能。TCP互联网协议在客户端和服务器之间形成连接,确保所有数据包从一端到达另一端。它需要将内容拟合为固定格式,以便可以轻松传输。另一方面,UDP不关注来自客户端或服务器的所有数据包是否到达另一方以及它们是否以相同的顺序传输。 UDP不会浪费时间将数据包转换为固定包流。因此,有了这些UDP,SOCKS5可以提供更快的速度和可靠的连接。

4)错误减少,整体性能提升

许多其他代理重写数据包标头。因此,错误路由或错误标记数据的可能性很高。但SOCKS5代理服务器不会重写数据包标头,因此错误的可能性较低。由于错误少得多,性能会自动提高。但是,这会以客户端的隐私和安全为代价,因为数据包标头包含客户端的个人信息,并且可以轻松识别。

5)在P2P平台上表现更好 SOCKS5比其他代理更快,因为它传输较小的数据包。因此,它提供更快的下载速度,这就是许多用户使用它连接到P2P共享网站和平台的原因。

代码

package main

import (
	"bufio"
	"context"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
)

const socks5Ver = 0x05
const cmdBind = 0x01
const atypIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04

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)
	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/PASSWORD

        // 版本和NMETHODS值都是单字节的,所以ReadByte读一个字节就好了
	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)
	if err != nil {
		return fmt.Errorf("read method failed:%w", err)
	}

	// +----+--------+
	// |VER | METHOD |
	// +----+--------+
	// | 1  |   1    |
	// +----+--------+
	_, err = conn.Write([]byte{socks5Ver, 0x00}) // 如果返回的是00的话就代表不需要认证
	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", ver)
	}
	addr := ""
	switch atyp {
	case atypIPV4:
		_, 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])

	dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))//建立TCP连接
	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)
	}
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	go func() {
		_, _ = io.Copy(dest, reader)
		cancel()
	}()
	go func() {
		_, _ = io.Copy(conn, dest)
		cancel()
	}()

	<-ctx.Done()
	return nil
}

小结

也是用SwitchyOmega测试成功了

image.png

image.png