SOCKS5代理简记 | 青训营笔记

456 阅读7分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记

SOCK5代理协议

简介

SOCKS5是一种代理服务协议,主要用于客户端与目标服务器之间通讯的透明传递。

在现代网络中,很多情况下,由于网络或者防火墙的原因,我们很难直接去访问对方的网络,所以需要一种代理机制来充当本地网络和大型网络之间的网关。代理服务器通过拦截发送方和接收方之间的连接来工作。 所有传入的数据都通过一个端口进入,并通过另一个端口转发到目标网络中。

因为SOCKS是运行在OSI七层协议中的第五层会话层,所以它可以处理包括HTTP、HTTPS、POP3、SMTP 和 FTP等多种请求类型,所以可以使用SOCKS协议来进行邮件发送、网页浏览、文件传输等。

SOCKS5协议设计之初是为了让有权限的用户可以穿过防火墙的限制,访问外部资源。

相比较SOCKS4,SOCKS5加入了认证功能,更加的安全。

可以用SOCKS5协议来实现代理服务器。

协议运行过程

下图为SOCK5协议的运行过程 image.png

认证过程

1. 客户端发出请求

VERSIONMETHODS_COUNTMETHODS...
1字节1字节1到255字节,长度由METHODS_COUNT值决定
0x050x030x00 0x01 0x02
  • VERSION SOCKS协议版本,目前固定0x05
  • METHODS_COUNT 客户端支持的认证方法数量
  • METHODS... 客户端支持的认证方法,每个方法占用1个字节

METHOD定义

  • 0x00 不需要认证(常用)
  • 0x01 GSSAPI认证
  • 0x02 账号密码认证(常用)
  • 0x03 - 0x7F IANA分配
  • 0x80 - 0xFE 私有方法保留
  • 0xFF 无支持的认证方法

2. 服务端返回支持的认证方法

VERSIONMETHOD
1字节1字节
0x050x00
  • VERSION SOCKS协议版本,目前固定0x05
  • METHOD 本次连接所用的认证方法,上例中为无需认证 如果是0x02则是账号密码认证 3. 客户端根据认证方法发出认证请求

注意 如果是无认证,则不需要这一步,同样也不需要第四步

VERSIONUSERNAME_LENGTHUSERNAMEPASSWORD_LENGTHPASSWORD
1字节1字节1-255字节1字节1-255字节
0x010x010x0a0x010x0a
  • VERSION 认证子协商版本(与SOCKS协议版本的0x05无关系)
  • USERNAME_LENGTH 用户名长度
  • USERNAME 用户名字节数组,长度为USERNAME_LENGTH
  • PASSWORD_LENGTH 密码长度
  • PASSWORD 密码字节数组,长度为PASSWORD_LENGTH

4. 服务端响应结果

校验后返回结果

VERSIONSTATUS
1字节1字节
  • VERSION 认证子协商版本,与客户端VERSION字段一致

  • STATUS 认证结果 0x00成功 大于0失败

建立连接

1. 客户端发送请求

VERSIONCOMMANDRSVADDRESS_TYPEDST.ADDRDST.PORT
1字节1字节1字节1字节1-255字节2字节
  • VERSION SOCKS协议版本,固定0x05

  • COMMAND 命令

    • 0x01 CONNECT 连接上游服务器
    • 0x02 BIND 绑定,客户端会接收来自代理服务器的链接,著名的FTP被动模式
    • 0x03 UDP ASSOCIATE UDP中继
  • RSV 保留字段

  • ADDRESS_TYPE 目标服务器地址类型

    • 0x01 IP V4地址
    • 0x03 域名地址(没有打错,就是没有0x02),域名地址的第1个字节为域名长度,剩下字节为域名名称字节数组
    • 0x04 IP V6地址
  • DST.ADDR 目标服务器地址

  • DST.PORT 目标服务器端口

被动模式FTP 为了解决服务器主动发起到客户端连接会被阻止的问题,另一种更完善的工作模式出现了,它就是 FTP 的被动模式,缩写作 PASV,它工作的前提是客户端明确告知 FTP 服务器它使用被动模式。 在被动模式的 FTP 中,客户端启动到服务器的两个连接, 解决了防火墙阻止从服务器到客户端的传入数据端口连接的问题 。

2. 代理服务器与目标服务器建立连接

3. 代理服务器向主机返回结果

VERSIONRESPONSERSVADDRESS_TYPEBND.ADDRBND.PORT
1字节1字节1字节1字节1-255字节2字节
  • VERSION SOCKS协议版本,固定0x05

  • RESPONSE 响应命令

    • 0x00 代理服务器连接目标服务器成功
    • 0x01 代理服务器故障
    • 0x02 代理服务器规则集不允许连接
    • 0x03 网络无法访问
    • 0x04 目标服务器无法访问(主机名无效)
    • 0x05 连接目标服务器被拒绝
    • 0x06 TTL已过期
    • 0x07 不支持的命令
    • 0x08 不支持的目标服务器地址类型
    • 0x09 - 0xFF 未分配
  • RSV 保留字段

  • BND.ADDR 代理服务器连接目标服务器成功后的代理服务器IP

  • BND.PORT 代理服务器连接目标服务器成功后的代理服务器端口

数据传输

1. 客户端发送数据

2. 代理服务器relay数据(转发)

3. 代理服务器将结果不加解析发送给主机,起到透明代理的功能

使用GOLANG编写的简易SOCK5代理服务器

// 定义一些常量 用于协议
const socks5Ver = 0x05
const cmdBind = 0x01
const atypIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04

func main() {
// 服务端建立服务 监听1080端口
   server, err := net.Listen("tcp", "127.0.0.1:1080")
   if err != nil {
      panic(err.(any))
   }
   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)

下面是认证过程 实例是无认证

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)
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
}

认证结束


if err != nil {
   log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
   return
}
//建立连接
err = connect(reader, conn)

建立连接过程 只支持ipv4与域名

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))
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
}
if err != nil {
   log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
   return
}
}