要求写一个socks5
首先要清楚socks5的流程
- 授权
- connect
- 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 关闭的问题,但是~ 不理解 回去再看看吧~