├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── client.go ├── cmd ├── client │ └── main.go └── server │ └── main.go ├── cryptogram.go ├── cryptogram_test.go ├── docs ├── cryptogram.md ├── release.md └── socks5.md ├── e2e_test.go ├── go.mod ├── img └── logo.png ├── server.go └── socks5.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | go.sum -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 4 | - 1.10.x 5 | - 1.11.x 6 | - master 7 | 8 | matrix: 9 | fast_finish: true 10 | include: 11 | - go: 1.11.x 12 | env: GO111MODULE=on 13 | 14 | 15 | before_install: 16 | - if [[ "${GO111MODULE}" = "on" ]]; then mkdir "${HOME}/go"; export GOPATH="${HOME}/go"; fi 17 | - if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi 18 | 19 | install: 20 | - go get github.com/stretchr/testify 21 | 22 | go_import_path: github.com/shikanon/socks5proxy 23 | 24 | script: 25 | - go test -cover -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 South China University 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Socks5Proxy 4 | 5 | [![GitHub license](https://img.shields.io/github/license/shikanon/socks5proxy)](https://github.com/shikanon/socks5proxy/blob/master/LICENSE) 6 | [![GitHub stars](https://img.shields.io/github/stars/shikanon/socks5proxy)](https://github.com/shikanon/socks5proxy/stargazers) 7 | [![GitHub forks](https://img.shields.io/github/forks/shikanon/socks5proxy)](https://github.com/shikanon/socks5proxy/network) 8 | [![Language](https://img.shields.io/badge/Language-Go-blue.svg)](https://golang.org/) 9 | [![Go Report Card](https://goreportcard.com/badge/github.com/shikanon/socks5proxy)](https://goreportcard.com/report/github.com/shikanon/socks5proxy) 10 | 11 | 12 | 用golang 实现了一个简单的socks5协议来实现代理转发,主要应用场景是給公司内部做VPN登陆,提供内网访问。*(声明:由于采用的是原始的socks5协议,并没有对协议做改造加工,并不一定能防范GFW的主动探测,请勿用于非法用途)* 13 | 14 | 15 | 文件结构 16 | ``` 17 | cryptogram.go `加密算法` 18 | socks5.go `socks5协议实现` 19 | server.go `服务端实现` 20 | client.go `客户端实现` 21 | cmd/server/main.go `服务端主启动程序` 22 | cmd/client/main.go `客户端主启动程` 23 | ``` 24 | 25 | 26 | - [SOCKS5协议介绍](./docs/socks5.md) 27 | - [加密算法介绍](./docs/cryptogram.md) 28 | - [软件下载及版本说明](./docs/release.md) 29 | 30 | #### 使用说明 31 | 32 | **服务端** 33 | 在服务器端中启动路径,打开。/cmd/server/,运行`go run main.go` 34 | 服务端命令参数有三个: 35 | ``` 36 | -local string #设置服务器对外端口 37 | Input server listen address(Default 8888): (default ":18888") 38 | -passwd string #设置服务器对外密码 39 | Input server proxy password: (default "123456") 40 | -type string #设置加密类型 41 | Input encryption type: (default "random") 42 | ``` 43 | 44 | **客户端** 45 | 在客户端中启动路径,打开。/cmd/client/,运行`go run main.go` 46 | 服务端命令参数有四个: 47 | ``` 48 | -local string #设置客户端的本地转发端口 49 | Input server listen address(Default 8888): (default ":8888") 50 | -passwd string #设置服务器的密码 51 | Input server proxy password: (default "123456") 52 | -server string #设置服务器ip地址和端口 53 | Input server listen address, for example: 16.158.6.16:18181 54 | -type string #设置加密类型 55 | Input encryption type: (default "random") 56 | ``` 57 | 58 | ## TODO 59 | 60 | * [ * ] 混淆加密 61 | * [ * ] 客户端 -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package socks5proxy 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "log" 7 | "net" 8 | "net/url" 9 | "strconv" 10 | "strings" 11 | "sync" 12 | ) 13 | 14 | type TcpClient struct { 15 | conn *net.TCPConn 16 | server *net.TCPAddr 17 | } 18 | 19 | func handleProxyRequest(localClient *net.TCPConn, serverAddr *net.TCPAddr, auth socks5Auth, recvHTTPProto string) { 20 | 21 | // 远程连接IO 22 | dstServer, err := net.DialTCP("tcp", nil, serverAddr) 23 | if err != nil { 24 | log.Print("远程服务器地址连接错误!!!") 25 | log.Print(err) 26 | return 27 | } 28 | defer dstServer.Close() 29 | 30 | defer localClient.Close() 31 | 32 | // 和远程端建立安全信道 33 | wg := new(sync.WaitGroup) 34 | wg.Add(2) 35 | 36 | if recvHTTPProto == "http" { 37 | // socket5请求认证协商 38 | // 第一阶段协议版本及认证方式 39 | auth.EncodeWrite(dstServer, []byte{0x05, 0x01, 0x00}) 40 | resp := make([]byte, 1024) 41 | n, err := auth.DecodeRead(dstServer, resp) 42 | if err != nil { 43 | log.Fatal(err) 44 | return 45 | } 46 | if n == 0 { 47 | log.Fatal("协议错误,服务器返回为空") 48 | return 49 | } 50 | if resp[1] == 0x00 && n == 2 { 51 | log.Print("success") 52 | } else { 53 | log.Fatal("协议错误,连接失败") 54 | return 55 | } 56 | // 第二阶段根据认证方式执行对应的认证,由于采用无密码格式,这里省略验证 57 | // 第三阶段请求信息 58 | // VER, CMD, RSV, ATYP, ADDR, PORT 59 | buff := make([]byte, 1024) 60 | n, err = localClient.Read(buff) 61 | if err != nil { 62 | log.Print(err) 63 | return 64 | } 65 | localReq := buff[:n] 66 | j := 0 67 | z := 0 68 | httpreq := []string{} 69 | for i := 0; i < n; i++ { 70 | if buff[i] == 32 { 71 | httpreq = append(httpreq, string(buff[j:i])) 72 | j = i + 1 73 | } 74 | if buff[i] == 10 { 75 | z += 1 76 | } 77 | } 78 | 79 | dstURI, err := url.ParseRequestURI(httpreq[1]) 80 | if err != nil { 81 | log.Print(err) 82 | return 83 | } 84 | var dstAddr string 85 | var dstPort = "80" 86 | dstAddrPort := strings.Split(dstURI.Host, ":") 87 | if len(dstAddrPort) == 1 { 88 | dstAddr = dstAddrPort[0] 89 | } else if len(dstAddrPort) == 2 { 90 | dstAddr = dstAddrPort[0] 91 | dstPort = dstAddrPort[1] 92 | } else { 93 | log.Print("URL parse error!") 94 | return 95 | } 96 | 97 | resp = []byte{0x05, 0x01, 0x00, 0x03} 98 | // 域名 99 | // dstAddrLenBuff := bytes.NewBuffer(make([]byte, 1)) 100 | // binary.BigEndian.PutUint16(dstAddrLenBuff, uint8(len(dstAddr))) 101 | // binary.Write(dstAddrLenBuff, binary.BigEndian, uint8(len(dstAddr))) 102 | // log.Print("AdrrLength:", dstAddrLenBuff.Bytes()[dstAddrLenBuff.Len()-1]) 103 | // resp = append(resp, dstAddrLenBuff.Bytes()[dstAddrLenBuff.Len()-1]) 104 | resp = append(resp, byte(len([]byte(dstAddr)))) 105 | resp = append(resp, []byte(dstAddr)...) 106 | // 端口 107 | dstPortBuff := bytes.NewBuffer(make([]byte, 0)) 108 | dstPortInt, err := strconv.ParseUint(dstPort, 10, 16) 109 | if err != nil { 110 | log.Fatal(err) 111 | return 112 | } 113 | binary.Write(dstPortBuff, binary.BigEndian, dstPortInt) 114 | dstPortBytes := dstPortBuff.Bytes() // int为8字节 115 | resp = append(resp, dstPortBytes[len(dstPortBytes)-2:]...) 116 | n, err = auth.EncodeWrite(dstServer, resp) 117 | if err != nil { 118 | log.Print(dstServer.RemoteAddr(), err) 119 | return 120 | } 121 | n, err = auth.DecodeRead(dstServer, resp) 122 | if err != nil { 123 | log.Print(dstServer.RemoteAddr(), err) 124 | return 125 | } 126 | var targetResp [10]byte 127 | copy(targetResp[:10], resp[:n]) 128 | specialResp := [10]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} 129 | if targetResp != specialResp { 130 | log.Print("协议错误, 第二次协商返回出错") 131 | return 132 | } 133 | log.Print("认证成功") 134 | 135 | // 转发消息 136 | go func() { 137 | defer wg.Done() 138 | auth.Encrypt(localReq) 139 | dstServer.Write(localReq) 140 | // SecureCopy(localClient, dstServer, auth.Encrypt) 141 | }() 142 | 143 | go func() { 144 | defer wg.Done() 145 | SecureCopy(dstServer, localClient, auth.Decrypt) 146 | }() 147 | 148 | wg.Wait() 149 | 150 | } else { 151 | 152 | // 本地的内容copy到远程端 153 | go func() { 154 | defer wg.Done() 155 | SecureCopy(localClient, dstServer, auth.Encrypt) 156 | }() 157 | 158 | // 远程得到的内容copy到源地址 159 | go func() { 160 | defer wg.Done() 161 | SecureCopy(dstServer, localClient, auth.Decrypt) 162 | }() 163 | wg.Wait() 164 | } 165 | 166 | } 167 | 168 | func Client(listenAddrString string, serverAddrString string, encrytype string, passwd string, recvHTTPProto string) { 169 | //所有客户服务端的流都加密, 170 | auth, err := CreateAuth(encrytype, passwd) 171 | if err != nil { 172 | log.Fatal(err) 173 | } 174 | log.Printf("你的密码是: %s ,请保管好你的密码", passwd) 175 | 176 | // proxy地址 177 | serverAddr, err := net.ResolveTCPAddr("tcp", serverAddrString) 178 | if err != nil { 179 | log.Fatal(err) 180 | } 181 | log.Printf("连接远程服务器: %s ....", serverAddrString) 182 | 183 | listenAddr, err := net.ResolveTCPAddr("tcp", listenAddrString) 184 | if err != nil { 185 | log.Fatal(err) 186 | } 187 | log.Printf("监听本地端口: %s ", listenAddrString) 188 | 189 | listener, err := net.ListenTCP("tcp", listenAddr) 190 | if err != nil { 191 | log.Fatal(err) 192 | } 193 | 194 | for { 195 | localClient, err := listener.AcceptTCP() 196 | if err != nil { 197 | log.Fatal(err) 198 | } 199 | go handleProxyRequest(localClient, serverAddr, auth, recvHTTPProto) 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /cmd/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | 7 | "github.com/shikanon/socks5proxy" 8 | ) 9 | 10 | func main() { 11 | listenAddr := flag.String("local", ":8888", "Input server listen address(Default 8888):") 12 | serverAddr := flag.String("server", "", "Input server listen address:") 13 | passwd := flag.String("passwd", "123456", "Input server proxy password:") 14 | encrytype := flag.String("type", "random", "Input encryption type:") 15 | recvHTTPProto := flag.String("recv", "http", "use http or sock5 protocol(default http):") 16 | flag.Parse() 17 | if *serverAddr == "" { 18 | log.Fatal("请输入正确的远程地址") 19 | } 20 | log.Println("客户端正在启动...") 21 | log.Println(&recvHTTPProto) 22 | socks5proxy.Client(*listenAddr, *serverAddr, *encrytype, *passwd, *recvHTTPProto) 23 | } 24 | -------------------------------------------------------------------------------- /cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "github.com/shikanon/socks5proxy" 6 | "log" 7 | ) 8 | 9 | func main() { 10 | listenAddr := flag.String("local", ":18888", "Input server listen address(Default 8888):") 11 | passwd := flag.String("passwd", "123456", "Input server proxy password:") 12 | encrytype := flag.String("type", "random", "Input encryption type:") 13 | flag.Parse() 14 | log.Println("服务器正在启动...") 15 | socks5proxy.Server(*listenAddr, *encrytype, *passwd) 16 | } 17 | -------------------------------------------------------------------------------- /cryptogram.go: -------------------------------------------------------------------------------- 1 | package socks5proxy 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | ) 7 | 8 | const ( 9 | RANDOM_A = 13 10 | RANDOM_B = 7 11 | RANDOM_M = 256 12 | ) 13 | 14 | type socks5Auth interface { 15 | Encrypt([]byte) error 16 | Decrypt([]byte) error 17 | EncodeWrite(io.ReadWriter, []byte) (int, error) 18 | DecodeRead(io.ReadWriter, []byte) (int, error) 19 | } 20 | 21 | type DefaultAuth struct { 22 | Encode *[256]byte //编码表 23 | Decode *[256]byte //解码表 24 | } 25 | 26 | /** 27 | 加密方法:根据编码表将字符串进行编码 28 | **/ 29 | 30 | func (s *DefaultAuth) Encrypt(b []byte) error { 31 | for i, v := range b { 32 | // 编码 33 | if int(v) >= len(s.Encode) { 34 | return errors.New("socks5Auth Encode 超出范围") 35 | } 36 | b[i] = s.Encode[v] 37 | } 38 | return nil 39 | } 40 | 41 | func (s *DefaultAuth) Decrypt(b []byte) error { 42 | for i, v := range b { 43 | // 编码 44 | if int(v) >= len(s.Encode) { 45 | return errors.New("socks5Auth Encode 超出范围") 46 | } 47 | b[i] = s.Decode[v] 48 | } 49 | return nil 50 | } 51 | 52 | func (s *DefaultAuth) EncodeWrite(c io.ReadWriter, b []byte) (int, error) { 53 | // 编码 54 | err := s.Encrypt(b) 55 | if err != nil { 56 | return 0, err 57 | } 58 | return c.Write(b) 59 | } 60 | 61 | func (s *DefaultAuth) DecodeRead(c io.ReadWriter, b []byte) (int, error) { 62 | // 解码 63 | n, err := c.Read(b) 64 | if err != nil { 65 | return 0, err 66 | } 67 | err = s.Decrypt(b) 68 | if err != nil { 69 | return 0, err 70 | } 71 | return n, err 72 | } 73 | 74 | func CreateSimpleCipher(passwd string) (*DefaultAuth, error) { 75 | var s *DefaultAuth 76 | // 采用最简单的凯撒位移法 77 | sumint := 0 78 | if len(passwd) == 0 { 79 | return nil, errors.New("密码不能为空") 80 | } 81 | for v := range passwd { 82 | sumint += int(v) 83 | } 84 | sumint = sumint % 256 85 | var encodeString [256]byte 86 | var decodeString [256]byte 87 | for i := 0; i < 256; i++ { 88 | encodeString[i] = byte((i + sumint) % 256) 89 | decodeString[i] = byte((i - sumint + 256) % 256) 90 | } 91 | s = &DefaultAuth{ 92 | Encode: &encodeString, 93 | Decode: &decodeString, 94 | } 95 | return s, nil 96 | } 97 | 98 | func CreateRandomCipher(passwd string) (*DefaultAuth, error) { 99 | var s *DefaultAuth 100 | // 采用随机编码表进行加密 101 | sumint := 0 102 | if len(passwd) == 0 { 103 | return nil, errors.New("密码不能为空") 104 | } 105 | for v := range passwd { 106 | sumint += int(v) 107 | } 108 | var encodeString [256]byte 109 | var decodeString [256]byte 110 | // 创建随机数 (a*x + b) mod m 111 | for i := 0; i < 256; i++ { 112 | encodeString[i] = byte((RANDOM_A*sumint + RANDOM_B) % RANDOM_M) 113 | decodeString[(RANDOM_A*sumint+RANDOM_B)%RANDOM_M] = byte(i) 114 | sumint = (RANDOM_A*sumint + RANDOM_B) % RANDOM_M 115 | } 116 | s = &DefaultAuth{ 117 | Encode: &encodeString, 118 | Decode: &decodeString, 119 | } 120 | return s, nil 121 | } 122 | 123 | // 创建认证证书 124 | func CreateAuth(encrytype string, passwd string) (socks5Auth, error) { 125 | if len(passwd) == 0 { 126 | return nil, errors.New("密码不能为空") 127 | } 128 | var s socks5Auth 129 | var err error 130 | switch encrytype { 131 | case "simple": 132 | s, err = CreateSimpleCipher(passwd) 133 | 134 | case "random": 135 | s, err = CreateRandomCipher(passwd) 136 | default: 137 | return nil, errors.New("错误加密方法类型!") 138 | } 139 | 140 | if err != nil { 141 | return nil, err 142 | } 143 | return s, nil 144 | } 145 | 146 | // 加密io复制,可接收加密函数作为参数 147 | func SecureCopy(src io.ReadWriteCloser, dst io.ReadWriteCloser, secure func(b []byte) error) (written int64, err error) { 148 | size := 1024 149 | buf := make([]byte, size) 150 | for { 151 | nr, er := src.Read(buf) 152 | secure(buf) 153 | if nr > 0 { 154 | nw, ew := dst.Write(buf[0:nr]) 155 | if nw > 0 { 156 | written += int64(nw) 157 | } 158 | if ew != nil { 159 | err = ew 160 | break 161 | } 162 | if nr != nw { 163 | err = io.ErrShortWrite 164 | break 165 | } 166 | } 167 | if er != nil { 168 | if er != io.EOF { 169 | err = er 170 | } 171 | break 172 | } 173 | } 174 | return written, err 175 | } 176 | -------------------------------------------------------------------------------- /cryptogram_test.go: -------------------------------------------------------------------------------- 1 | package socks5proxy 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "log" 6 | "testing" 7 | ) 8 | 9 | func TestSampleCipher(t *testing.T) { 10 | auth, err := CreateSimpleCipher("abc") 11 | if err != nil { 12 | log.Panic(err) 13 | } 14 | b := []byte{0x05, 0x01, 0x04} 15 | c := []byte{0x05, 0x01, 0x04} 16 | // 加密 17 | err = auth.Encrypt(b) 18 | if err != nil { 19 | log.Panic(err) 20 | } 21 | // 解密 22 | err = auth.Decrypt(b) 23 | if err != nil { 24 | log.Panic(err) 25 | } 26 | assert.Equal(t, b, c) 27 | } 28 | 29 | func TestRandomCipher(t *testing.T) { 30 | auth, err := CreateRandomCipher("123456") 31 | if err != nil { 32 | log.Panic(err) 33 | } 34 | var b []byte 35 | var c []byte 36 | for i := 0; i < 256; i++ { 37 | b = append(b, byte(i)) 38 | c = append(c, byte(i)) 39 | } 40 | // 加密 41 | err = auth.Encrypt(b) 42 | if err != nil { 43 | log.Panic(err) 44 | } 45 | // 解密 46 | err = auth.Decrypt(b) 47 | if err != nil { 48 | log.Panic(err) 49 | } 50 | assert.Equal(t, b, c) 51 | } 52 | -------------------------------------------------------------------------------- /docs/cryptogram.md: -------------------------------------------------------------------------------- 1 | # 加密算法 2 | 3 | ## 凯撒加密算法 4 | 5 | 这里使用了最简单的加密算法,凯撒加密(Caesar cipher),即明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。 6 | 因为该算法非常容易实现,而且简单快速,因此被选择为这个工具的加密算法。 7 | 8 | ``` 9 | func CreateAuth(passwd string) *socks5Auth{ 10 | // 采用最简单的凯撒位移法 11 | sumint := 0 12 | for v := range passwd { 13 | sumint += int(v) 14 | } 15 | sumint = sumint % 256 16 | var encodeString [256]byte 17 | var decodeString [256]byte 18 | for i := 0; i < 256; i++{ 19 | encodeString[i] = byte((i+sumint)%256) 20 | decodeString[i] = byte((i-sumint+256)%256) 21 | } 22 | return &socks5Auth{ 23 | KeyMoved: sumint, 24 | Encode: &encodeString, 25 | Decode: &decodeString, 26 | } 27 | } 28 | ``` 29 | 30 | ## 随机数表加密算法 31 | 32 | 这种加密算法是在凯撒加密算法上面的升级,我们知道凯撒加密算法主要是对字母表进行移位来加密,但是如何原文中存在高频单词,那么可以通过统计学推断出高频的原文和密文对应关系, 33 | 而凯撒加密是通过位移得到的,只要找出其中一个对应关系就可以计算出位移数,这样全文就被破解了,比如:`a -> c` 位移是 2, 那么整个原文通过位移数进行还原即可。 34 | 针对这种情况,我们可以构建一张加密算表的来将这种破解的难度增加:设定255个随机的对应加密表。这样即使通过统计学特征推断出其中几个字母,依然无法破解全文。加密算法实现: 35 | 36 | ``` 37 | func CreateRandomCipher(passwd string) (*DefaultAuth, error){ 38 | var s *DefaultAuth 39 | // 采用随机编码表进行加密 40 | sumint := 0 41 | if len(passwd) == 0 { 42 | return nil, errors.New("密码不能为空") 43 | } 44 | for v := range passwd { 45 | sumint += int(v) 46 | } 47 | var encodeString [256]byte 48 | var decodeString [256]byte 49 | // 创建随机数 (a*x + b) mod m 50 | for i := 0; i < 256; i++{ 51 | encodeString[i] = byte((RANDOM_A*sumint+RANDOM_B)%RANDOM_M) 52 | decodeString[(RANDOM_A*sumint+RANDOM_B)%RANDOM_M] = byte(i) 53 | sumint = (RANDOM_A*sumint+RANDOM_B)%RANDOM_M 54 | } 55 | s = &DefaultAuth{ 56 | Encode: &encodeString, 57 | Decode: &decodeString, 58 | } 59 | return s, nil 60 | } 61 | ``` -------------------------------------------------------------------------------- /docs/release.md: -------------------------------------------------------------------------------- 1 | # Release 2 | 3 | ## V0.1版本 4 | 5 | ### 说明 6 | 7 | 1、添加了socks5的服务端和客户端功能 8 | 2、添加了随机混淆加密和Caser加密算法 9 | 10 | ### 下载 11 | 12 | - [vpn_client_linux_amd64_v0.1](https://pan.baidu.com/s/1r4ZEleOu4RgaSldkSfhHTQ) 13 | - [vpn_server_linux_amd64_v0.1](https://pan.baidu.com/s/1TAZmSqtRZME55q-ROGFsJQ) 14 | - [vpn_client_windows_amd64_v0.1](https://pan.baidu.com/s/1fJpAyLs9jjx23Q0itPrtzw) 15 | - [vpn_server_windows_amd64_v0.1](https://pan.baidu.com/s/1AGp-erpri3k0NkSrEGCoIQ) -------------------------------------------------------------------------------- /docs/socks5.md: -------------------------------------------------------------------------------- 1 | # SOCKS5协议介绍 2 | 3 | SOCKS是一种网络传输协议,主要用于客户端与外网服务器之间通讯的中间传递,SOCKS是"SOCKetS"的缩写。 4 | SOCKS5是SOCKS4的升级版,其主要多了鉴定、IPv6、UDP支持。 5 | 6 | SOCKS5协议可以分为三个部分: 7 | * (1) 协议版本及认证方式 8 | * (2) 根据认证方式执行对应的认证 9 | * (3) 请求信息 10 | 11 | 12 | 13 | - [SOCKS5协议介绍](#socks5协议介绍) 14 | - [(1)协议版本及认证方式](#1协议版本及认证方式) 15 | - [(2)根据认证方式执行对应的认证](#2根据认证方式执行对应的认证) 16 | - [(3)请求信息](#3请求信息) 17 | - [(4)最后将信息进行转发即可](#4最后将信息进行转发即可) 18 | 19 | 20 | 21 | #### (1)协议版本及认证方式 22 | 创建与SOCKS5服务器的TCP连接后**客户端**需要先发送请求来协议版本及认证方式, 23 | 24 | 25 | |VER| NMETHODS | METHODS | 26 | |----|-----|-------| 27 | |1 |1 |1-255 | 28 | 29 | * VER是SOCKS版本,这里应该是0x05; 30 | * NMETHODS是METHODS部分的长度; 31 | * METHODS是客户端支持的认证方式列表,每个方法占1字节。当前的定义是: 32 | * 0x00 不需要认证 33 | * 0x01 GSSAPI 34 | * 0x02 用户名、密码认证 35 | * 0x03 - 0x7F由IANA分配(保留) 36 | * 0x80 - 0xFE为私人方法保留 37 | * 0xFF 无可接受的方法 38 | 39 | 40 | **服务器**回复客户端可用方法: 41 | 42 | |VER| METHOD | 43 | |----|-----| 44 | |1 |1 | 45 | 46 | * VER是SOCKS版本,这里应该是0x05; 47 | * METHOD是服务端选中的方法。如果返回0xFF表示没有一个认证方法被选中,客户端需要关闭连接。 48 | 49 | 代码实现: 50 | 51 | ``` 52 | type ProtocolVersion struct { 53 | VER uint8 54 | NMETHODS uint8 55 | METHODS []uint8 56 | } 57 | 58 | 59 | func (s *ProtocolVersion) handshake(conn net.Conn) error { 60 | b := make([]byte, 255) 61 | n, err := conn.Read(b) 62 | if err != nil { 63 | log.Println(err) 64 | return err 65 | } 66 | s.VER = b[0] //ReadByte reads and returns a single byte,第一个参数为socks的版本号 67 | s.NMETHODS = b[1] //nmethods是记录methods的长度的。nmethods的长度是1个字节 68 | if n != int(2+s.NMETHODS) { 69 | return errors.New("协议错误, sNMETHODS不对") 70 | } 71 | s.METHODS = b[2:2+s.NMETHODS] //读取指定长度信息,读取正好len(buf)长度的字节。如果字节数不是指定长度,则返回错误信息和正确的字节数 72 | 73 | if s.VER != 5 { 74 | return errors.New("该协议不是socks5协议") 75 | } 76 | 77 | //服务器回应客户端消息: 78 | //第一个参数表示版本号为5,即socks5协议, 79 | // 第二个参数表示服务端选中的认证方法,0即无需密码访问, 2表示需要用户名和密码进行验证。 80 | resp :=[]byte{5, 0} 81 | conn.Write(resp) 82 | return nil 83 | } 84 | ``` 85 | 86 | 87 | #### (2)根据认证方式执行对应的认证 88 | 89 | SOCKS5协议提供5种认证方式: 90 | * 0x00 不需要认证 91 | * 0x01 GSSAPI 92 | * 0x02 用户名、密码认证 93 | * 0x03 - 0x7F由IANA分配(保留) 94 | * 0x80 - 0xFE为私人方法保留 95 | 96 | 这里就主要介绍用户名、密码认证。 97 | 在客户端、服务端协商使用用户名密码认证后,客户端发出用户名密码: 98 | 99 | |鉴定协议版本 |用户名长度 |用户名 |密码长度 |密码 | 100 | |------|----|------|---------|---| 101 | |1 |1 |动态 |1 |动态 | 102 | 103 | 服务器鉴定后发出如下回应: 104 | 105 | |鉴定协议版本 |鉴定状态 | 106 | |------|--------| 107 | |1 |1 | 108 | 109 | 其中鉴定状态 0x00 表示成功,0x01 表示失败。 110 | 111 | 代码实现: 112 | ``` 113 | type Socks5Auth struct { 114 | VER uint8 115 | ULEN uint8 116 | UNAME string 117 | PLEN uint8 118 | PASSWD string 119 | } 120 | 121 | func (s *Socks5Auth) authenticate(conn net.Conn) error { 122 | b := make([]byte, 128) 123 | n, err := conn.Read(b) 124 | if err != nil{ 125 | log.Println(err) 126 | return err 127 | } 128 | 129 | s.VER = b[0] 130 | if s.VER != 5 { 131 | return errors.New("该协议不是socks5协议") 132 | } 133 | 134 | s.ULEN = b[1] 135 | s.UNAME = string(b[2:2+s.ULEN]) 136 | s.PLEN = b[2+s.ULEN+1] 137 | s.PASSWD = string(b[n-int(s.PLEN):n]) 138 | log.Println(s.UNAME, s.PASSWD) 139 | if username != s.UNAME || passwd != s.PASSWD { 140 | return errors.New("账号密码错误") 141 | } 142 | 143 | /** 144 | 回应客户端,响应客户端连接成功 145 | The server verifies the supplied UNAME and PASSWD, and sends the 146 | following response: 147 | +----+--------+ 148 | |VER | STATUS | 149 | +----+--------+ 150 | | 1 | 1 | 151 | +----+--------+ 152 | A STATUS field of X'00' indicates success. If the server returns a 153 | `failure' (STATUS value other than X'00') status, it MUST close the 154 | connection. 155 | */ 156 | resp := []byte{0x05, 0x00} 157 | conn.Write(resp) 158 | 159 | return nil 160 | } 161 | ``` 162 | 163 | 但其实,现在大家都习惯自己采用加密流的方式进行加密,很少采用用户名密码的形式进行加密,后面章节会介绍一种对SOCKS的混淆加密方式。 164 | 165 | 166 | #### (3)请求信息 167 | 认证结束后客户端就可以发送请求信息。如果认证方法有特殊封装要求,请求必须按照方法所定义的方式进行封装解密,其原始格式如下: 168 | 169 | |VER |CMD |RSV |ATYP |DST.ADDR |DST.PORT| 170 | |------|-------|-------|-------|---------|-----------| 171 | |1 |1 |0x00 |1 |动态 |2 | 172 | 173 | * VER是SOCKS版本,这里应该是0x05; 174 | * CMD是SOCK的命令码 175 | * 0x01表示CONNECT请求 176 | * 0x02表示BIND请求 177 | * 0x03表示UDP转发 178 | * RSV 0x00,保留 179 | * ATYP DST.ADDR类型 180 | * DST.ADDR 目的地址 181 | * 0x01 IPv4地址,DST.ADDR部分4字节长度 182 | * 0x03 域名,DST.ADDR部分第一个字节为域名长度,DST.ADDR剩余的内容为域名,没有\0结尾。 183 | * 0x04 IPv6地址,16个字节长度。 184 | * DST.PORT 网络字节序表示的目的端口 185 | 186 | 代码实现: 187 | ``` 188 | type Socks5Resolution struct { 189 | VER uint8 190 | CMD uint8 191 | RSV uint8 192 | ATYP uint8 193 | DSTADDR []byte 194 | DSTPORT uint16 195 | DSTDOMAIN string 196 | RAWADDR *net.TCPAddr 197 | } 198 | 199 | func (s *Socks5Resolution) lstRequest(conn net.Conn) error { 200 | b := make([]byte, 128) 201 | n, err := conn.Read(b) 202 | if err != nil || n < 7 { 203 | log.Println(err) 204 | return errors.New("请求协议错误") 205 | } 206 | s.VER = b[0] 207 | if s.VER != 5 { 208 | return errors.New("该协议不是socks5协议") 209 | } 210 | 211 | s.CMD = b[1] 212 | if s.CMD != 1 { 213 | return errors.New("客户端请求类型不为代理连接, 其他功能暂时不支持.") 214 | } 215 | s.RSV = b[2] //RSV保留字端,值长度为1个字节 216 | 217 | s.ATYP = b[3] 218 | 219 | switch s.ATYP { 220 | case 1: 221 | // IP V4 address: X'01' 222 | s.DSTADDR = b[4 : 4+net.IPv4len] 223 | case 3: 224 | // DOMAINNAME: X'03' 225 | s.DSTDOMAIN = string(b[5:n-2]) 226 | ipAddr, err := net.ResolveIPAddr("ip", s.DSTDOMAIN) 227 | if err != nil { 228 | return err 229 | } 230 | s.DSTADDR = ipAddr.IP 231 | case 4: 232 | // IP V6 address: X'04' 233 | s.DSTADDR = b[4 : 4+net.IPv6len] 234 | default: 235 | return errors.New("IP地址错误") 236 | } 237 | 238 | s.DSTPORT = binary.BigEndian.Uint16(b[n-2:n]) 239 | // DSTADDR全部换成IP地址,可以防止DNS污染和封杀 240 | s.RAWADDR = &net.TCPAddr{ 241 | IP: s.DSTADDR, 242 | Port: int(s.DSTPORT), 243 | } 244 | 245 | /** 246 | 回应客户端,响应客户端连接成功 247 | +----+-----+-------+------+----------+----------+ 248 | |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | 249 | +----+-----+-------+------+----------+----------+ 250 | | 1 | 1 | X'00' | 1 | Variable | 2 | 251 | +----+-----+-------+------+----------+----------+ 252 | */ 253 | resp := []byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} 254 | conn.Write(resp) 255 | 256 | return nil 257 | 258 | 259 | } 260 | ``` 261 | 262 | 263 | #### (4)最后将信息进行转发即可 264 | 265 | 代码实现: 266 | ``` 267 | wg := new(sync.WaitGroup) 268 | wg.Add(2) 269 | 270 | go func() { 271 | defer wg.Done() 272 | defer dstServer.Close() 273 | io.Copy(dstServer, client) 274 | }() 275 | 276 | go func() { 277 | defer wg.Done() 278 | defer client.Close() 279 | io.Copy(client, dstServer) 280 | }() 281 | 282 | wg.Wait() 283 | ``` -------------------------------------------------------------------------------- /e2e_test.go: -------------------------------------------------------------------------------- 1 | package socks5proxy 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "net/http" 7 | "net/url" 8 | "sync" 9 | "testing" 10 | "time" 11 | 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestConncet(t *testing.T) { 16 | go Server("127.0.0.1:18189", "random", "abcedfg") 17 | go Client("127.0.0.1:18190", "127.0.0.1:18189", "random", "abcedfg", "sock5") 18 | 19 | time.Sleep(1 * time.Second) 20 | 21 | // 连接 22 | conn, err := net.Dial("tcp", "127.0.0.1:18190") 23 | if err != nil { 24 | log.Fatalln(err) 25 | } 26 | defer conn.Close() 27 | 28 | readResult := make([]byte, 256) 29 | wg := new(sync.WaitGroup) 30 | wg.Add(2) 31 | 32 | // socks5协商验证 33 | // 写 34 | go func() { 35 | defer wg.Done() 36 | conn.Write([]byte{0x05, 0x01, 0x00}) 37 | }() 38 | 39 | // 读 40 | go func() { 41 | defer wg.Done() 42 | n, err := conn.Read(readResult) 43 | if err != nil { 44 | log.Panic(err) 45 | } 46 | assert.Equal(t, readResult[0:n], []byte{0x05, 0x00}) 47 | }() 48 | 49 | wg.Wait() 50 | } 51 | 52 | func TestHTTPConnect(t *testing.T) { 53 | go Server("127.0.0.1:18289", "random", "abcedfg1") 54 | go Client("127.0.0.1:18290", "127.0.0.1:18289", "random", "abcedfg1", "http") 55 | 56 | time.Sleep(1 * time.Second) 57 | 58 | ProxyURI, err := url.ParseRequestURI("http://127.0.0.1:18290") 59 | if err != nil { 60 | log.Panic(err) 61 | } 62 | reqClient := http.Client{ 63 | Timeout: 2 * time.Second, 64 | Transport: &http.Transport{ 65 | Proxy: http.ProxyURL(ProxyURI), 66 | TLSHandshakeTimeout: 1 * time.Second, 67 | }, 68 | } 69 | // http.ListenAndServe 70 | 71 | req, err := http.NewRequest("GET", "http://www.baidu.com/", nil) 72 | if err != nil { 73 | log.Panic(err) 74 | } 75 | resp, err := reqClient.Do(req) 76 | if err != nil { 77 | log.Panic(err) 78 | } 79 | assert.Equal(t, resp.StatusCode, 200) 80 | var respbody []byte 81 | n, err := resp.Body.Read(respbody) 82 | if err != nil { 83 | log.Panic(err) 84 | } 85 | log.Print(string(respbody[:n])) 86 | } 87 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/shikanon/socks5proxy 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/golang/mock v1.3.1 7 | github.com/stretchr/testify v1.4.0 8 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect 9 | golang.org/x/net v0.0.0-20191021144547-ec77196f6094 // indirect 10 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect 11 | golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae // indirect 12 | golang.org/x/text v0.3.2 // indirect 13 | golang.org/x/tools v0.0.0-20191025023517-2077df36852e // indirect 14 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shikanon/socks5proxy/fbd4d87aa81cecd68de0accf3960eb335aa8b8f2/img/logo.png -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package socks5proxy 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "sync" 7 | ) 8 | 9 | func handleClientRequest(client *net.TCPConn, auth socks5Auth) { 10 | if client == nil { 11 | return 12 | } 13 | defer client.Close() 14 | 15 | // 初始化一个字符串buff 16 | buff := make([]byte, 255) 17 | 18 | // 认证协商 19 | var proto ProtocolVersion 20 | n, err := auth.DecodeRead(client, buff) //解密 21 | resp, err := proto.HandleHandshake(buff[0:n]) 22 | auth.EncodeWrite(client, resp) //加密 23 | if err != nil { 24 | log.Print(client.RemoteAddr(), err) 25 | return 26 | } 27 | 28 | //获取客户端代理的请求 29 | var request Socks5Resolution 30 | n, err = auth.DecodeRead(client, buff) 31 | resp, err = request.LSTRequest(buff[0:n]) 32 | auth.EncodeWrite(client, resp) 33 | if err != nil { 34 | log.Print(client.RemoteAddr(), err) 35 | return 36 | } 37 | 38 | log.Println(client.RemoteAddr(), request.DSTDOMAIN, request.DSTADDR, request.DSTPORT) 39 | 40 | // 连接真正的远程服务 41 | dstServer, err := net.DialTCP("tcp", nil, request.RAWADDR) 42 | if err != nil { 43 | log.Print(client.RemoteAddr(), err) 44 | return 45 | } 46 | defer dstServer.Close() 47 | 48 | wg := new(sync.WaitGroup) 49 | wg.Add(2) 50 | 51 | // 本地的内容copy到远程端 52 | go func() { 53 | defer wg.Done() 54 | SecureCopy(client, dstServer, auth.Decrypt) 55 | }() 56 | 57 | // 远程得到的内容copy到源地址 58 | go func() { 59 | defer wg.Done() 60 | SecureCopy(dstServer, client, auth.Encrypt) 61 | }() 62 | wg.Wait() 63 | 64 | } 65 | 66 | func Server(listenAddrString string, encrytype string, passwd string) { 67 | //所有客户服务端的流都加密, 68 | auth, err := CreateAuth(encrytype, passwd) 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | log.Printf("你的密码是:%s ,请保管好你的密码", passwd) 73 | 74 | // 监听客户端 75 | listenAddr, err := net.ResolveTCPAddr("tcp", listenAddrString) 76 | if err != nil { 77 | log.Fatal(err) 78 | } 79 | log.Printf("监听服务器端口: %s ", listenAddrString) 80 | 81 | listener, err := net.ListenTCP("tcp", listenAddr) 82 | if err != nil { 83 | log.Fatal(err) 84 | } 85 | defer listener.Close() 86 | 87 | for { 88 | conn, err := listener.AcceptTCP() 89 | if err != nil { 90 | log.Fatal(err) 91 | } 92 | go handleClientRequest(conn, auth) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /socks5.go: -------------------------------------------------------------------------------- 1 | package socks5proxy 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "log" 7 | "net" 8 | ) 9 | 10 | const ( 11 | SOCKS_VERSION = 0x05 12 | METHOD_CODE = 0x00 13 | ) 14 | 15 | //go:generate mockgen -source=socks5.go -destination=socks5_mock.go -package=mock 16 | type Protocol interface { 17 | HandleHandshake(b []byte) ([]byte, error) 18 | SentHandshake(conn net.Conn) error 19 | } 20 | 21 | /** 22 | The localConn connects to the dstServer, and sends a ver 23 | identifier/method selection message: 24 | +----+----------+----------+ 25 | |VER | NMETHODS | METHODS | 26 | +----+----------+----------+ 27 | | 1 | 1 | 1 to 255 | 28 | +----+----------+----------+ 29 | The VER field is set to X'05' for this ver of the protocol. The 30 | NMETHODS field contains the number of method identifier octets that 31 | appear in the METHODS field. 32 | METHODS常见的几种方式如下: 33 | 1>.数字“0”:表示不需要用户名或者密码验证; 34 | 2>.数字“1”:GSSAPI是SSH支持的一种验证方式; 35 | 3>.数字“2”:表示需要用户名和密码进行验证; 36 | 4>.数字“3”至“7F”:表示用于IANA 分配(IANA ASSIGNED) 37 | 5>.数字“80”至“FE”表示私人方法保留(RESERVED FOR PRIVATE METHODS) 38 | 4>.数字“FF”:不支持所有的验证方式,无法进行连接 39 | **/ 40 | type ProtocolVersion struct { 41 | VER uint8 42 | NMETHODS uint8 43 | METHODS []uint8 44 | } 45 | 46 | func (s *ProtocolVersion) HandleHandshake(b []byte) ([]byte, error) { 47 | n := len(b) 48 | if n < 3 { 49 | return nil, errors.New("协议错误, sNMETHODS不对") 50 | } 51 | s.VER = b[0] //ReadByte reads and returns a single byte,第一个参数为socks的版本号 52 | if s.VER != 0x05 { 53 | return nil, errors.New("协议错误, version版本不为5!") 54 | } 55 | s.NMETHODS = b[1] //nmethods是记录methods的长度的。nmethods的长度是1个字节 56 | if n != int(2+s.NMETHODS) { 57 | return nil, errors.New("协议错误, sNMETHODS不对") 58 | } 59 | s.METHODS = b[2 : 2+s.NMETHODS] //读取指定长度信息,读取正好len(buf)长度的字节。如果字节数不是指定长度,则返回错误信息和正确的字节数 60 | 61 | useMethod := byte(0x00) //默认不需要密码 62 | for _, v := range s.METHODS { 63 | if v == METHOD_CODE { 64 | useMethod = METHOD_CODE 65 | } 66 | } 67 | 68 | if s.VER != SOCKS_VERSION { 69 | return nil, errors.New("该协议不是socks5协议") 70 | } 71 | 72 | //服务器回应客户端消息: 73 | //第一个参数表示版本号为5,即socks5协议, 74 | // 第二个参数表示服务端选中的认证方法,0即无需密码访问, 2表示需要用户名和密码进行验证。 75 | // 88是一种私有的加密协议 76 | if useMethod != METHOD_CODE { 77 | return nil, errors.New("协议错误, 加密方法不对") 78 | } 79 | resp := []byte{SOCKS_VERSION, useMethod} 80 | return resp, nil 81 | 82 | } 83 | 84 | func (s *ProtocolVersion) SentHandshake(conn net.Conn) error { 85 | // 采用0x88私有加密方法进行加密 86 | resp := []byte{SOCKS_VERSION, 0x01, METHOD_CODE} 87 | conn.Write(resp) 88 | return nil 89 | } 90 | 91 | /* 92 | This begins with the client producing a 93 | Username/Password request: 94 | +----+------+----------+------+----------+ 95 | |VER | ULEN | UNAME | PLEN | PASSWD | 96 | +----+------+----------+------+----------+ 97 | | 1 | 1 | 1 to 255 | 1 | 1 to 255 | 98 | +----+------+----------+------+----------+ 99 | 100 | */ 101 | 102 | type Socks5AuthUPasswd struct { 103 | VER uint8 104 | ULEN uint8 105 | UNAME string 106 | PLEN uint8 107 | PASSWD string 108 | } 109 | 110 | func (s *Socks5AuthUPasswd) HandleAuth(b []byte) ([]byte, error) { 111 | // b := make([]byte, 128) 112 | // n, err := conn.Read(b) 113 | // if err != nil{ 114 | // log.Println(err) 115 | // return err 116 | // } 117 | n := len(b) 118 | 119 | s.VER = b[0] 120 | if s.VER != 5 { 121 | return nil, errors.New("该协议不是socks5协议") 122 | } 123 | 124 | s.ULEN = b[1] 125 | s.UNAME = string(b[2 : 2+s.ULEN]) 126 | s.PLEN = b[2+s.ULEN+1] 127 | s.PASSWD = string(b[n-int(s.PLEN) : n]) 128 | log.Println(s.UNAME, s.PASSWD) 129 | 130 | /** 131 | 回应客户端,响应客户端连接成功 132 | The server verifies the supplied UNAME and PASSWD, and sends the 133 | following response: 134 | 135 | +----+--------+ 136 | |VER | STATUS | 137 | +----+--------+ 138 | | 1 | 1 | 139 | +----+--------+ 140 | 141 | A STATUS field of X'00' indicates success. If the server returns a 142 | `failure' (STATUS value other than X'00') status, it MUST close the 143 | connection. 144 | */ 145 | resp := []byte{SOCKS_VERSION, 0x00} 146 | // conn.Write(resp) 147 | 148 | return resp, nil 149 | } 150 | 151 | /** 152 | 结构: 153 | +----+-----+-------+------+----------+----------+ 154 | |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 155 | +----+-----+-------+------+----------+----------+ 156 | | 1 | 1 | X'00' | 1 | Variable | 2 | 157 | +----+-----+-------+------+----------+----------+ 158 | cmd代表客户端请求的类型,值长度也是1个字节,有三种类型: 159 | 1>.数字“1”:表示客户端需要你帮忙代理连接,即CONNECT ; 160 | 2>.数字“2”:表示让你代理服务器,帮他建立端口,即BIND ; 161 | 3>.数字“3”:表示UDP连接请求用来建立一个在UDP延迟过程中操作UDP数据报的连接,即UDP ASSOCIATE; 162 | ATYP代表请求的远程服务器地址类型,它是一个可变参数,但是它值的长度1个字节, 163 | 有三种类型: 164 | 1>.数字“1”:表示是一个IPV4地址(IP V4 address); 165 | 2>.数字“3”:表示是一个域名(DOMAINNAME); 166 | 3>.数字“4”:表示是一个IPV6地址(IP V6 address); 167 | 168 | **/ 169 | type Socks5Resolution struct { 170 | VER uint8 171 | CMD uint8 172 | RSV uint8 173 | ATYP uint8 174 | DSTADDR []byte 175 | DSTPORT uint16 176 | DSTDOMAIN string 177 | RAWADDR *net.TCPAddr 178 | } 179 | 180 | func (s *Socks5Resolution) LSTRequest(b []byte) ([]byte, error) { 181 | // b := make([]byte, 128) 182 | // n, err := conn.Read(b) 183 | n := len(b) 184 | if n < 7 { 185 | return nil, errors.New("请求协议错误") 186 | } 187 | s.VER = b[0] 188 | if s.VER != SOCKS_VERSION { 189 | return nil, errors.New("该协议不是socks5协议") 190 | } 191 | 192 | s.CMD = b[1] 193 | if s.CMD != 1 { 194 | return nil, errors.New("客户端请求类型不为代理连接, 其他功能暂时不支持.") 195 | } 196 | s.RSV = b[2] //RSV保留字端,值长度为1个字节 197 | 198 | s.ATYP = b[3] 199 | 200 | switch s.ATYP { 201 | case 1: 202 | // IP V4 address: X'01' 203 | s.DSTADDR = b[4 : 4+net.IPv4len] 204 | case 3: 205 | // DOMAINNAME: X'03' 206 | s.DSTDOMAIN = string(b[5 : n-2]) 207 | ipAddr, err := net.ResolveIPAddr("ip", s.DSTDOMAIN) 208 | if err != nil { 209 | return nil, err 210 | } 211 | s.DSTADDR = ipAddr.IP 212 | case 4: 213 | // IP V6 address: X'04' 214 | s.DSTADDR = b[4 : 4+net.IPv6len] 215 | default: 216 | return nil, errors.New("IP地址错误") 217 | } 218 | 219 | s.DSTPORT = binary.BigEndian.Uint16(b[n-2 : n]) 220 | // DSTADDR全部换成IP地址,可以防止DNS污染和封杀 221 | s.RAWADDR = &net.TCPAddr{ 222 | IP: s.DSTADDR, 223 | Port: int(s.DSTPORT), 224 | } 225 | 226 | /** 227 | 回应客户端,响应客户端连接成功 228 | +----+-----+-------+------+----------+----------+ 229 | |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | 230 | +----+-----+-------+------+----------+----------+ 231 | | 1 | 1 | X'00' | 1 | Variable | 2 | 232 | +----+-----+-------+------+----------+----------+ 233 | */ 234 | resp := []byte{SOCKS_VERSION, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} 235 | // conn.Write(resp) 236 | 237 | return resp, nil 238 | } 239 | --------------------------------------------------------------------------------