Socks5协议入门解析+GO语言简单实现

1,720 阅读5分钟

本文主要介绍了socks5协议的构成,和基于go语言的tcp实现方案,并且只处理了connent类型的请求。协议详细地址datatracker.ietf.org/doc/html/rf…datatracker.ietf.org/doc/html/rf…

一、认证

认证方式分为了两种,一种是不用认证,一种是用户名密码认证

  • 客户端发送支持的协议数据

image.png

属性描述
VER直接取值0x05,表示socks5协议
NMETHODS客户端支持的认证方式数量
METHODS认证方式的实际值和NMETHODS对应
  • 服务端回复客户端数据

image.png

METHOD支持的值

o  X'00' NO AUTHENTICATION REQUIRED
o  X'01' GSSAPI
o  X'02' USERNAME/PASSWORD
o  X'03' to X'7F' IANA ASSIGNED
o  X'80' to X'FE' RESERVED FOR PRIVATE METHODS
o  X'FF' NO ACCEPTABLE METHODS

不需要密码的方式回复数据

属性描述
VER协议版本,回复0x05,表示socks5协议
METHOD回复0x00不需要密码的方式

需要密码的方式回复数据

属性描述
VER协议版本,回复0x05,表示socks5协议
METHOD回复0x02用户名和密码

客户端发送用户名和密码

image.png

属性描述
VER协议版本,0x05,表示socks5协议
ULEN用户名长度
UNAME用户名
PLEN密码长度
PASWD密码

服务端回复

image.png

属性描述
VER协议版本,0x05,表示socks5协议
STATUS0x00表示成功,其他的回复都是失败

二、连接

客户端会发送如下参数过来

image.png

属性描述
VER协议版本,0x05,表示socks5协议
CMD命令类型
RSV不用管
ATYP地址类型
DST.ADDR地址
DST.PORT端口

详细说明

o  VER    protocol version: X'05'
o  CMD
 o  CONNECT X'01' 
 o  BIND X'02'
 o  UDP ASSOCIATE X'03'
o  RSV    RESERVED
o  ATYP   address type of following address
 o  IP V4 address: X'01'
 o  DOMAINNAME: X'03'
 o  IP V6 address: X'04'
o  DST.ADDR       desired destination address
o  DST.PORT desired destination port in network octet
 order

请求目的地地址解析说明

In an address field (DST.ADDR, BND.ADDR), the ATYP field specifies
the type of address contained within the field:

      o  X'01'

the address is a version-4 IP address, with a length of 4 octets
# IPV4的地址读取4字节

      o  X'03'

the address field contains a fully-qualified domain name.  The first
octet of the address field contains the number of octets of name that
follow, there is no terminating NUL octet.
# 域名方式,第一个字节是域名长度,后面读取按照长度进行读取,转换为域名字符串

      o  X'04'

the address is a version-6 IP address, with a length of 16 octets.
# IPV6则取16字节

服务端回复信息

image.png

o  VER    protocol version: X'05'
o  REP    Reply field:
    o  X'00' succeeded
    o  X'01' general SOCKS server failure
    o  X'02' connection not allowed by ruleset
    o  X'03' Network unreachable
    o  X'04' Host unreachable
    o  X'05' Connection refused
    o  X'06' TTL expired
    o  X'07' Command not supported
    o  X'08' Address type not supported
    o  X'09' to X'FF' unassigned
o  RSV    RESERVED
o  ATYP   address type of following address
    o  IP V4 address: X'01'
    o  DOMAINNAME: X'03'
    o  IP V6 address: X'04'
o  BND.ADDR       server bound address
o  BND.PORT       server bound port in network octet order

此处只只介绍connect类型的指令,其他类型的方式相似,按照官方文档进行实现即可,这里我们读取出来目的地的地址,然后创建一个tcp的连接,把连接返回的dest.LocalAddr()的地址和端口,这里直接填入IPV4类型即可,填入BND.ADDR、BND.PORT连接就完成了,后续就正常进行数据的交换了

三、数据正式传输

由于上面我们已经拿到了client和desc的sockt连接,只需要把他们后续发送的内容交换到他们各自通道上就行。

四、GO语言代码

package main

import (
   "encoding/binary"
   "errors"
   "fmt"
   "io"
   "net"
)

func main() {
   server, err := net.Listen("tcp",":1080")
   if err != nil {
      panic(err)
   }
   for {
      client, err := server.Accept()
      if err != nil {
         panic(err)
      }
      go process(client)
   }
}

func process(client net.Conn)  {
   if err := Socks5Auth(client); err != nil {
      fmt.Println("auth error:", err)
      client.Close()
      return
   }
   target, err := Socks5Connect(client)
   if err != nil {
      fmt.Println("connect error:", err)
      client.Close()
      return
   }
   Socks5Forward(client, target)
}

func Socks5Auth(client net.Conn) (err error)  {
   buf := make([]byte,256)
   n, err := io.ReadFull(client,buf[:2])
   if n!= 2 {
      return errors.New("socks5 connect error")
   }
   ver, nmethods := int(buf[0]), int(buf[1])
   if ver != 5 {
      return errors.New("socks5 version error")
   }
   n, err = io.ReadFull(client, buf[:nmethods])
   if n!= nmethods {
      return errors.New("reading methods: " + err.Error())
   }
   // 用户名密码登陆
   n, err = client.Write([]byte{0x05, 0x02})

   n, err = io.ReadFull(client,buf[:2])
   // 读取用户名
   userNameLength := int(buf[1])
   n, err = io.ReadFull(client,buf[:userNameLength])
   userName := string(buf[:userNameLength])

   n, err = io.ReadFull(client,buf[:1])
   passwordLength := int(buf[0])
   n, err = io.ReadFull(client,buf[:passwordLength])
   password := string(buf[:passwordLength])
   if userName != "hzh" && password != "123456" {
      return errors.New("username or password err")
   }
   n, err = client.Write([]byte{0x05, 0x00})
   // 该回复是不需要用户名和密码
   // n, err = client.Write([]byte{0x05, 0x00})
   if n != 2 || err != nil {
      return errors.New("write rsp err:" + err.Error())
   }
   return nil
}

func Socks5Connect(client net.Conn) (net.Conn, error) {
   buf := make([]byte,256)
   n, err := io.ReadFull(client,buf[:4])
   if n != 4 {
      return nil, errors.New("read header: " + err.Error())
   }
   ver, cmd, _, atyp := buf[0], buf[1], buf[2], buf[3]
   if ver != 5 || cmd != 1 {
      return nil, errors.New("invalid ver/cmd")
   }

   addr := ""
   switch atyp {
   case 1:
      n, err = io.ReadFull(client, buf[:4])
      if n != 4 {
         return nil, errors.New("invalid IPv4: " + err.Error())
      }
      addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])

   case 3:
      n, err = io.ReadFull(client, buf[:1])
      if n != 1 {
         return nil, errors.New("invalid hostname: " + err.Error())
      }
      addrLen := int(buf[0])

      n, err = io.ReadFull(client, buf[:addrLen])
      if n != addrLen {
         return nil, errors.New("invalid hostname: " + err.Error())
      }
      addr = string(buf[:addrLen])

   case 4:
      return nil, errors.New("IPv6: no supported yet")

   default:
      return nil, errors.New("invalid atyp")
   }
   n, err = io.ReadFull(client, buf[:2])
   if n != 2 {
      return nil, errors.New("read port: " + err.Error())
   }
   port := binary.BigEndian.Uint16(buf[:2])
   destAddrPort := fmt.Sprintf("%s:%d", addr, port)
   dest, err := net.Dial("tcp", destAddrPort)
   fmt.Println(dest.LocalAddr())
   if err != nil {
      return nil, errors.New("dial dst: " + err.Error())
   }
   n, err = client.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
   if err != nil {
      dest.Close()
      return nil, errors.New("write rsp: " + err.Error())
   }
   return dest, nil
}

func Socks5Forward(client, target net.Conn) {
   defer client.Close()
   defer target.Close()
   forward := func(src, dest net.Conn,res chan int) {
      io.Copy(src, dest)
      res <- 1
   }
   res := make(chan int)
   go forward(client, target, res)
   go forward(target, client, res)
   // 等待线程执行完毕
   for i := 0; i < 2; i++ {
      <- res
   }
}