今天我们就来实现一下socket5的tcp代理。根据之前的学习我们了解到建立socket5代理需要三个阶段 socke5

下面我们使用golang来实现它。

第一阶段:验证

这里为了简单我们使用无须校验的方式,下面是部分代码

1
2
3
4
5
6
7
8
9
buf := make([]byte, 1024)

// 协议校验......省略

len, err = c.Write([]byte{0x05, 0x00})
if len != 2 || err != nil  {
    println("[parse protol] write data fail!")
    return
}

这里因为是用无校验的方式,所以直接返回给客户端0x05,0x00 报文

第二阶段:建立代理链接

客户端收到代理服务器验证通过的响应报文(0x05,0x00)之后会发送需要代理的目标地址信息到代理服务器,代理服务器解析报文获得目标地址信息建立代理链接。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
len, err = c.Read(buf)

// 协议校验......省略


// 解析报文中的地址信息,这里只解析ipv4和domain两种地址信息
var ip string
var port int
switch buf[3] {
    case 0x01: // ip
        ip = net.IPv4(buf[4], buf[5], buf[6], buf[7]).String()
        port = int(buf[len-2:][0])<<8 | int(buf[len-2:][1])
    case 0x03: // domain
        ip = string(buf[4 : len-2])
        port = int(buf[len-2:][0])<<8 | int(buf[len-2:][1])
}

addr := net.JoinHostPort(ip, strconv.Itoa(port))
println("start proxy:",addr)

r, err := net.Dial("tcp", addr) // 建立代理链接
if err != nil {
    println("connect proxy addr fail!", err.Error())
    return
} 

len, err = c.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) // 返回报文告诉客户端代理链接建立成
if len != 10 || err != nil  {
    println("[parse protol] write data fail!",err.Error())
    return
}

注意:

  1. 这里没有实现ipv6

  2. 经过我多次验证发现客户端并不关心返回报文中的地址信息所以我这里全部设为0x00,你也可以设为其他值

第三阶段:数据转发

这里就简单多了,直接将客户端的数据包转发到目标服务器,在将目标服务器的数据包转发给客户端

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 数据包转发函数
func forward(c, r net.Conn, buf []byte) {
    defer c.Close()
    defer r.Close()

    for {
        len, err := c.Read(buf)
        if len <= 0 || (err != nil && err != io.EOF) {
            break
        }

        len, err = r.Write(buf[0:len])
        if len <= 0 || err != nil  {
            break
        }
    }
}

// 使用上面函数转发数据包, c代表client链接,r代表remote链接
go forward(c, r, buf)
go forward(r, c, buf)

注意:因为我们无法得知客户端发给目标服务器数据包的长度和目标服务器返回给客户端数据包的长度(因为不知道传输的数据包具体协议所以无法得知长度),所以我们要使用协程对这两个链路上的数据包进行无限转发。


下面是完整的源码,写得比较简单没有太多校验,主要是为了帮助理解。只实现了tcp代理,udp部分比较复杂没有实现,有时间在弄。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
package main

import (
        "io"
        "net"
        "strconv"
        "log"
       )

func println(s...interface{}){
    log.Println(s...) 
}

func parseProtocol(c net.Conn) {

    buf := make([]byte, 1024)

    len, err := c.Read(buf)
    if len == 0 || err != nil {
        println("[parse protol] read data fail!")
        return
    }

    if buf[0] != 0x05 {
        println("[parse protol] illegal data!")
        return
    }

    len, err = c.Write([]byte{0x05, 0x00})
    if len != 2 || err != nil  {
        println("[parse protol] write data fail!")
        return
    }

    len, err = c.Read(buf)
    if len == 0 || err != nil {
        println("[parse protol] read data fail!")
        return
    }

    if buf[0] != 0x05 || buf[1] != 0x01 {
        println("[parse protol] illegal data!")
        return
    }

    var ip string
    var port int
    switch buf[3] {
        case 0x01:
            ip = net.IPv4(buf[4], buf[5], buf[6], buf[7]).String()
            port = int(buf[len-2:][0])<<8 | int(buf[len-2:][1])
        case 0x03:
            ip = string(buf[4 : len-2])
            port = int(buf[len-2:][0])<<8 | int(buf[len-2:][1])
    }

    addr := net.JoinHostPort(ip, strconv.Itoa(port))
    println("start proxy:",addr)

    r, err := net.Dial("tcp", addr)
    if err != nil {
        println("connect proxy addr fail!", err.Error())
        return
    }

    len, err = c.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
    if len != 10 || err != nil  {
        println("[parse protol] write data fail!",err.Error())
        return
    }

    go forward(c, r, buf)
    go forward(r, c, buf)

}

func forward(c, r net.Conn, buf []byte) {
    defer c.Close()
    defer r.Close()

    for {
        len, err := c.Read(buf)
        if len <= 0 || (err != nil && err != io.EOF) {
            break
        }

        len, err = r.Write(buf[0:len])
        if len <= 0 || err != nil  {
            break
        }
    }
}


func main() {
    in, err := net.Listen("tcp", ":8080")
    if err != nil {
        println("bind 8080 fail!",err.Error())
        return
    }
    println("start server ok. listen port 8080")
    for {
        c, err := in.Accept()
        if err != nil {
            println("accept fail!",err.Error())
            continue
        }
        go parseProtocol(c)
    }
}