Go 语言基础(12) | 青训营笔记

155 阅读2分钟

SOCKS5 代理服务器续

认证阶段

认证阶段的功能全部写在 auth() 函数里,首先实现一个空的 auth() 函数,被 process() 调用:

package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"net"
)

const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 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
	}
	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)
	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
}

认证部分的代码实际上是没干什么事,因为我们是选择了不认证的方式,浏览器给服务器发一个包,包含三个字段:

  • 版本号:默认是 5
  • 认证的方法数目:我们只需要一个“不认证”,所以是 1
  • 每个方法的编码:“不认证”的编码是 00

目前代码运行结果如下:

第一个终端先跑上这个代码:

$ go run proxy/v2/main.go

第二个终端通过端口访问一下网页:

$ curl --socks5 127.0.0.1:1080 -v baidu.com
*   Trying 127.0.0.1:1080...
* SOCKS5 connect to IPv4 110.242.68.66:80 (locally resolved)
* connection to proxy closed
* Closing connection 0
curl: (97) connection to proxy closed

现在因为只有第一个阶段的代码,所以访问是不成功的,然后第一个终端就会返回版本号和方法的信息:

2023/05/23 23:41:05 ver 5 method [0 1]
2023/05/23 23:41:05 auth success

所以假如这里我是需要用户密码来认证的话应该要修改最后往链接里写入的编号,然后同时要重新写函数实现用户密码的读入判断等。

下篇进入请求阶段。