Socks5代理协议与Golang实现

简介: 本文主要讲解了SOCKS5协议以及golang实现相关内容

1. 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为主。

2. 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协议,并输入本地地址与监听端口。
image.png

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()
    }()

    <-ctx.Done()
    return nil
}

这部分其实没什么好说的,主要分为两部分,一部分是socks5服务器需要知道请求的就细节,一部分是开始进行中继服务,// BND.ADDR 服务绑定的地址 // BND.PORT 服务绑定的端口DST.PORT这部分返回0意思是socks5服务器和中继服务器是在一起的,不过你也可以进行拆分,把中继服务独立出去,并告知客户端具体的地址。

3. 测试

SwitchyOmega不支持socks5的认证,所以这里使用curl进行测试。

#不需要认证 
curl -x socks5h://127.0.0.1:7582 http://www.baidu.com
#如果需要认证 
curl -x socks5h://bigbyto:123456@127.0.0.1:7582 http://www.baidu.com

如果你使用的是cmd或者powershell进行测试,可能会出现下面的情况。
image.png
如果你有git,在vscode里面切换到git bash就可以了。 测试验证功能
image.png

目录
相关文章
|
8月前
|
物联网 Go 网络性能优化
使用Go语言(Golang)可以实现MQTT协议的点对点(P2P)消息发送。MQTT协议本身支持多种消息收发模式
使用Go语言(Golang)可以实现MQTT协议的点对点(P2P)消息发送。MQTT协议本身支持多种消息收发模式【1月更文挑战第21天】【1月更文挑战第104篇】
541 1
|
8月前
|
NoSQL Go Redis
Golang实现redis系列-(3)封装RESP协议
Golang实现redis系列-(3)封装RESP协议
63 0
|
数据采集 存储 JSON
猜谜游戏、彩云词典爬虫、SOCKS5代理的 Go(Golang) 小实践,附带全代码解释
猜谜游戏在编程语言实践都已经和 HelloWord 程序成为必不可少的新手实践环节,毕竟,它能够让我们基本熟悉 for 循环、变量定义、打印、if else 语句等等的使用,当我们基本熟悉该语言基础之后,就要学会其优势方面的程序实践,比如 Golang 所具备的爬虫及其并发优势。我们将采用彩云词典的英文单词翻译成中文的在线词典爬虫程序,及其改进版本,在并发上,我们将采用 SOCKS5 代理服务器的方式体验 Golang 语言的高并发易用性
101 0
猜谜游戏、彩云词典爬虫、SOCKS5代理的 Go(Golang) 小实践,附带全代码解释
|
安全 网络协议 Go
TLS1.3 协议的Golang 实现——ClientHello
# 前言 撰写本文时[TLS1.3 RFC](https://tools.ietf.org/html/draft-ietf-tls-tls13-28) 已经发布到28版本。以前写过一点密码学及TLS 相关的文章,为了更深入理解TLS1.3协议,这次将尝试使用Go语言实现它。网络上已有部分站点支持TLS1.3,Chrome浏览器通过设置可支持TLS1.3 (draft23),利用这些条件可验证,
4124 0
|
网络协议 Go 存储
golang 自定义封包协议(转的)
package protocol import ( "bytes" "encoding/binary" ) const ( ConstHeader = "jackluo" ConstHeaderLength = 7 ...
2441 0
|
4月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
160 4
Golang语言之管道channel快速入门篇
|
4月前
|
Go
Golang语言文件操作快速入门篇
这篇文章是关于Go语言文件操作快速入门的教程,涵盖了文件的读取、写入、复制操作以及使用标准库中的ioutil、bufio、os等包进行文件操作的详细案例。
81 4
Golang语言文件操作快速入门篇
|
4月前
|
Go
Golang语言之gRPC程序设计示例
这篇文章是关于Golang语言使用gRPC进行程序设计的详细教程,涵盖了RPC协议的介绍、gRPC环境的搭建、Protocol Buffers的使用、gRPC服务的编写和通信示例。
134 3
Golang语言之gRPC程序设计示例
|
4月前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
108 4
|
4月前
|
Go 调度
Golang语言goroutine协程篇
这篇文章是关于Go语言goroutine协程的详细教程,涵盖了并发编程的常见术语、goroutine的创建和调度、使用sync.WaitGroup控制协程退出以及如何通过GOMAXPROCS设置程序并发时占用的CPU逻辑核心数。
118 4
Golang语言goroutine协程篇