Socks5代理协议与Golang实现 | 青训营
- socks介绍
1.1 什么是socks
SOCKS(Socket Secure)是一种网络协议,通过代理服务器实现网络通信。简单来说,就是作为一个中转站,在客户端和客户端目标主机之间转发数据。SOCKS是运行在OSI七层协议中的第五层会话层,而我们常用http/https,SMTP,FTP协议都在第七层,所以它可以处理多种常用请求。
1.2 socks有什么用
当我们使用socks代理上网时,我们的目标主机只会看见运行socks的中转站ip地址,这种方式可以隐藏我们的的真实IP地址,增加了匿名性和安全性。如果我们的ip被某些网站限制访问,便可以使用socks进行代理访问。
1.3 socks5与socks4
socks5是socks4的下一个版本,加入了身份认证机制来建立完整的TCP连接,并支持UDP转发,目前互联网上基本以socks5为主。
-
socks5工作过程与Golang实现
本次讲解使用Edge与插件SwitchyOmega作为测试工具,原来来自字节青训营课程资料。
2.1 浏览器和socks5代理建立TCP连接
首先使用golang在本地监听一个端口。
func main() { //监听TCP连接 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) } }
进入SwitchyOmega选项设置,选择socks5协议,并输入本地地址与监听端口
此时,如果打开SwitchyOmega的代理模式,浏览器的所有请求携带socks5验证信息进入本地1080端口。
2.2 socks5协议协商
标准详见RFC 1928 - SOCK5
在进行代理转发前,socks5需要与客户端进行协商,包括协议版本,支持的认证方式等。
func process(conn net.Conn) { defer conn.Close() //把客户端首次请求的数据全部读进reader reader := bufio.NewReader(conn) //------开始协商------ err := auth(reader, conn) if err != nil { log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err) return } //------协商成功------- err = connect(reader, conn) if err != nil { log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err) return } } func auth(reader *bufio.Reader, conn net.Conn) (err error) { // request: // +----+----------+----------+ // |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) } //第三步 //response: // +----+--------+ // |VER | METHOD | // +----+--------+ // | 1 | 1 | // +----+--------+ _, err = conn.Write([]byte{socks5Ver, 0x00}) if err != nil { return fmt.Errorf("write failed:%w", err) } return nil }
- 第一步 读取客户端的首次请求数据放到reader中,读取第一个字节(版本号),判断版本号是否为5,不为5则断开连接.
- 第二步 读取第二个字节(认证方法数量),并根据认证方法数量,make一个用于存放方法的method,并从reader中读入。
- 第三步 返回响应,两个字节,第一个是版本5,第二个是表示认证方法(不需要认证)。如果返回0x02就是需要认证。
2.2.1 扩展--加入认证
原理和上面差不多 标准详见RFC 1929 - Username/Password Authentication for SOCKS
func process(conn net.Conn) { defer conn.Close() //把客户端首次请求的数据全部读进reader reader := bufio.NewReader(conn) //------开始协商------ if err := auth(reader, conn); err != nil { log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err) return } auReader := bufio.NewReader(conn) if err := authentication(auReader, conn); err != nil { log.Printf("authentication err") return } //------协商成功------- if err := connect(reader, conn); err != nil { log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err) return } } const subnegotiation = 0x01
func authentication(reader *bufio.Reader, conn net.Conn) (err error) { /**
- +----+------+----------+------+----------+
- |VER | ULEN | UNAME | PLEN | PASSWD |
- +----+------+----------+------+----------+
- | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
-
+----+------+----------+------+----------+ */ ver, err := reader.ReadByte() if err != nil { return fmt.Errorf("read ver failed:%w", err) } if ver != subnegotiation { return fmt.Errorf("not supported ver:%v", ver) } ulen, err := reader.ReadByte() if err != nil { return fmt.Errorf("read ulen failed:%w", err) } uname := make([]byte, ulen) , err = io.ReadFull(reader, uname) if err != nil { return fmt.Errorf("read uname failed:%w", err) } if string(uname) != "admin" { , err = conn.Write([]byte{0x01, 0x01}) if err != nil { return fmt.Errorf("write failed:%w", err) } return fmt.Errorf("auth uname failed:%w", err) }
plen, err := reader.ReadByte() if err != nil { return fmt.Errorf("read plen failed:%w", err) }
passwd := make([]byte, plen) , err = io.ReadFull(reader, passwd) if err != nil { return fmt.Errorf("read passwd failed:%w", err) } if string(passwd) != "123456" { , err = conn.Write([]byte{0x01, 0x01}) if err != nil { return fmt.Errorf("write failed:%w", err) } return fmt.Errorf("auth uname failed:%w", err) }
/* +----+--------+
- |VER | STATUS |
- +----+--------+
- | 1 | 1 |
-
+----+--------+ */ _, err = conn.Write([]byte{0x01, 0x00}) if err != nil { return fmt.Errorf("write failed:%w", err) }
return }
2.3请求与中继
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", cmd) } addr := "" switch atyp { case atypeIPV4: _, 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() }()