青训营笔记一补充

125 阅读3分钟

要求写一个socks5

首先要清楚socks5的流程

  1. 授权
  2. connect
  3. dial
package main

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

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

func main() {
   // 监听端口
   server, err := net.Listen("tcp", "127.0.0.1:1080")
   if err != nil {
      panic(err)
   }
   for {
      // server每接收到一个请求,就会返回一个连接
      client, err := server.Accept()
      if err != nil {
         log.Print("Accept failed %v", err)
      }
      // 处理连接
      go process(client)
   }
}
func process(conn net.Conn) {
   // 防止泄露
   defer conn.Close()

   // 1. 认证

   // 读取连接缓冲区的内容
   reader := bufio.NewReader(conn)
   // 进行auth认证
   err := auth(reader, conn)

   if err != nil {
      log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
      return
   }
   log.Println("auth success!")

   // 2.请求
   err = connect(reader, conn)
   if err != nil {
      log.Printf("clent %v request failed:%v", conn.RemoteAddr(), err)
   }
   log.Println("connect success!")
   // 3. 读取数据
   for {
      // 一个一个字节读入
      b, err := reader.ReadByte()
      if err != nil {
         break
      }
      // 再一个一个字节的写入
      _, err = conn.Write([]byte{b})
      if err != nil {
         break
      }
   }
}

// 进行认证
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' 不需要
   // X '02' user/password

   // 阅读的第一个字节是ver,就是用来验证的
   ver, err := reader.ReadByte()
   if err != nil {
      return fmt.Errorf("read ver failed %w", err)
   }
   if ver != socks5Ver {
      return fmt.Errorf("ver not supported:%v", ver)
   }

   // 阅读的第二个字节是methodSize
   methodSize, err := reader.ReadByte()
   if err != nil {
      return fmt.Errorf("read methods failed:%w", err)
   }

   // 根据字节数量,读入
   method := make([]byte, methodSize)
   _, err = io.ReadFull(reader, method)
   if err != nil {
      return fmt.Errorf("read method failed:%w", err)
   }

   log.Println("ver", ver, "method", method)

   _, 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 目的地址类型 ,addr对应对应的类型
   // 0x01 ipv4 dst.addr为一个4个字节
   // 0x03 域名 dst.addr为一个可变长度
   // dist.port目的端口,长度为2
   buf := make([]byte, 4)
   _, err = io.ReadFull(reader, buf)
   if err != nil {
      return fmt.Errorf("connect  failed %w", err)
   }
   ver, cmd, atyp := buf[0], buf[1], buf[3]
   if ver != socks5Ver {
      return fmt.Errorf("not support ver:%v", ver)
   }
   if cmd != cmdBind {
      return fmt.Errorf("not support cmd:%v", cmd)
   }
   addr := ""
   switch atyp {
   case atypIPV4:
      _, err = io.ReadFull(reader, buf)
      if err != nil {
         return fmt.Errorf("read failed %v", err)
      }
      // 将八进制化成10进制
      addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])
   case atypHost:
      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 failed %w", err)
      }
      addr = string(host)
   case atypeIPV6:
      return errors.New("IPV6:no supported")
   default:
      return errors.New("invaild atyp")
   }
   // 读取port
   io.ReadFull(reader, buf[:2])
   if err != nil {
      fmt.Errorf("port reader failed%w", err)
   }
   port := binary.BigEndian.Uint16(buf[:2])
   log.Println("dial", addr, port)

   // 建立tcp连接
   dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
   if err != nil {
      return fmt.Errorf("dial dst fialed:%w", err)
   }
   defer dest.Close()
   log.Println("dial", addr, 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
}

但是里面的context我还不是很理解,感觉很神奇诶,竟然写通了, 看到PPT里面说使用context是因为connect 关闭的问题,但是~ 不理解 回去再看看吧~