├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── cmd └── socksd │ ├── README.md │ ├── cipher.go │ ├── cipher_conn.go │ ├── config.go │ ├── decorate_client.go │ ├── decorate_conn.go │ ├── decorate_direct.go │ ├── decorate_listener.go │ ├── dns_cache.go │ ├── log.go │ ├── main.go │ ├── pac_generator.go │ ├── pac_updater.go │ └── upstream_client.go ├── direct.go ├── example_test.go ├── go.mod ├── go.sum ├── http_proxy.go ├── shadowsocks_client.go ├── socks4.go ├── socks4_test.go ├── socks5_client.go ├── socks5_client_test.go └── socks5_server.go /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # ========================= 18 | # Operating System Files 19 | # ========================= 20 | 21 | # OSX 22 | # ========================= 23 | 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must ends with two \r. 29 | Icon 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | *.exe -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.5.1 4 | install: 5 | - go get github.com/eahydra/socks/cmd/socksd -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2016 Google, Inc. http://angularjs.org 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 13 | all 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 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SOCKS 2 | [![Build Status](https://travis-ci.org/eahydra/socks.svg?branch=master)](https://travis-ci.org/eahydra/socks) [![GoDoc](https://godoc.org/github.com/eahydra/socks?status.svg)](https://godoc.org/github.com/eahydra/socks) 3 | 4 | SOCKS implements SOCKS4/5 Proxy Protocol and HTTP Tunnel which can help you get through firewall. 5 | The [cmd/socksd](https://github.com/eahydra/socks/blob/master/cmd/socksd) build with package SOCKS, supports cipher connection which crypto method is rc4, des, aes-128-cfb, aes-192-cfb and aes-256-cfb, upstream which can be shadowsocks or socsk5. 6 | 7 | # Install 8 | Assume you have go installed, you can install from source. 9 | ``` 10 | go get github.com/eahydra/socks 11 | ``` 12 | 13 | If you want to install [cmd/socksd](https://github.com/eahydra/socks/blob/master/cmd/socksd), please read the [README.md](https://github.com/eahydra/socks/blob/master/cmd/socksd/README.md) -------------------------------------------------------------------------------- /cmd/socksd/README.md: -------------------------------------------------------------------------------- 1 | # SOCKS 2 | [![Build Status](https://travis-ci.org/eahydra/socks.svg?branch=master)](https://travis-ci.org/eahydra/socks) 3 | 4 | The cmd/socksd build with package SOCKS, supports cipher connection which crypto method is rc4, des, aes-128-cfb, aes-192-cfb and aes-256-cfb, upstream which can be shadowsocks or socsk5. 5 | 6 | # Install 7 | Assume you have go installed, you can install from source. 8 | ``` 9 | go get github.com/eahydra/socks/cmd/socksd 10 | ``` 11 | 12 | # Usage 13 | Configuration file is in json format. The file must name **socks.config** and put it with socksd together. 14 | Configuration parameters as follows: 15 | ```json 16 | { 17 | "pac":{ 18 | "address":"127.0.0.1:50000", 19 | "proxy":"127.0.0.1:40000", 20 | "socks5":"127.0.0.1:8000", 21 | "local_rule_file":"gfw.txt", 22 | "remote_rule_file":"https://raw.githubusercontent.com/Leask/BRICKS/master/gfw.bricks", 23 | "upstream":{ 24 | "type":"shadowsocks", 25 | "crypto": "aes-256-cfb", 26 | "password": "111222333", 27 | "address": "106.182.12.24:10089" 28 | } 29 | }, 30 | "proxies":[ 31 | { 32 | "http":":40000", 33 | "socks4": ":9000", 34 | "socks5": ":8000", 35 | "upstreams": [ 36 | { 37 | "type":"shadowsocks", 38 | "crypto": "aes-256-cfb", 39 | "password": "111222333", 40 | "address": "106.182.12.24:10089" 41 | } 42 | ] 43 | } 44 | ] 45 | } 46 | 47 | ``` 48 | 49 | * **pac** - PAC config 50 | * **address** - Specifies the PAC server (127.0.0.1:50000) 51 | * **proxy** - (OPTIONAL) Enable HTTP Proxy in PAC 52 | * **socks5** - (OPTIONAL) Enable SOCKS5 Proxy in PAC 53 | * **local_rule_file** - (OPTIONAL) Local PAC rule file that per line is domain 54 | * **remote_rule_file** - (OPTIONAL) Remote PAC rule file like as [bricks](https://raw.githubusercontent.com/Leask/BRICKS/master/gfw.bricks) 55 | * **upstream** - (OPTIONAL) Through upstream to get **remote_rule_file** 56 | * **proxies** - The array of proxy config item 57 | * **http** - (OPTIONAL) Enable http proxy tunnel (127.0.0.1:8080 or :8080) 58 | * **socks4** - (OPTIONAL) Enable SOCKS4 proxy (127.0.0.1:9090 or :9090) 59 | * **socks5** - (OPTIONAL) Enable SOCKS5 proxy (127.0.0.1:9999 or :9999) 60 | * **crypto** - (OPTIONAL) SOCKS5's crypto method, now supports rc4, des, aes-128-cfb, aes-192-cfb and aes-256-cfb 61 | * **password** - If you set **crypto**, you must also set passsword 62 | * **dnsCacheTimeout** - (OPTIONAL) Enable dns cache (unit is second) 63 | * **upstreams** - The array of **upstream** 64 | * **upstream** 65 | * **type** - Specifies the type of upstream proxy server. Now supports shadowsocks and socks5 66 | * **crypto** - Specifies the crypto method of upstream proxy server. The crypto method is same as **localCryptoMethod** 67 | * **password** - Specifies the crypto password of upstream proxy server 68 | * **address** - Specifies the address of upstream proxy server (8.8.8.8:1111) 69 | -------------------------------------------------------------------------------- /cmd/socksd/cipher.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/des" 7 | "crypto/md5" 8 | "crypto/rand" 9 | "crypto/rc4" 10 | "io" 11 | 12 | "github.com/codahale/chacha20" 13 | ) 14 | 15 | func md5sum(d []byte) []byte { 16 | h := md5.New() 17 | h.Write(d) 18 | return h.Sum(nil) 19 | } 20 | 21 | func evpBytesToKey(password []byte, keylen int) (key []byte) { 22 | const md5len = 16 23 | cnt := (keylen-1)/md5len + 1 24 | m := make([]byte, cnt*md5len) 25 | copy(m, md5sum(password)) 26 | 27 | d := make([]byte, md5len+len(password)) 28 | start := 0 29 | for i := 1; i < cnt; i++ { 30 | start += md5len 31 | copy(d, m[start-md5len:start]) 32 | copy(d[md5len:], password) 33 | copy(m[start:], md5sum(d)) 34 | } 35 | return m[:keylen] 36 | } 37 | 38 | type RC4Cipher struct { 39 | *cipher.StreamReader 40 | *cipher.StreamWriter 41 | } 42 | 43 | func NewRC4Cipher(rwc io.ReadWriteCloser, password []byte) (*RC4Cipher, error) { 44 | decryptCipher, err := rc4.NewCipher(password) 45 | if err != nil { 46 | return nil, err 47 | } 48 | encryptCipher, err := rc4.NewCipher(password) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return &RC4Cipher{ 53 | StreamReader: &cipher.StreamReader{ 54 | S: decryptCipher, 55 | R: rwc, 56 | }, 57 | StreamWriter: &cipher.StreamWriter{ 58 | S: encryptCipher, 59 | W: rwc, 60 | }, 61 | }, nil 62 | } 63 | 64 | type Chacha20Cipher struct { 65 | password []byte 66 | rwc io.ReadWriteCloser 67 | *cipher.StreamReader 68 | *cipher.StreamWriter 69 | } 70 | 71 | func NewChacha20Cipher(rwc io.ReadWriteCloser, password []byte) (*Chacha20Cipher, error) { 72 | password = evpBytesToKey(password, chacha20.KeySize) 73 | return &Chacha20Cipher{ 74 | rwc: rwc, 75 | password: password, 76 | }, nil 77 | } 78 | 79 | func (c *Chacha20Cipher) Read(p []byte) (n int, err error) { 80 | if c.StreamReader == nil { 81 | iv := make([]byte, chacha20.NonceSize) 82 | n, err = io.ReadFull(c.rwc, iv) 83 | if err != nil { 84 | return n, err 85 | } 86 | stream, err := chacha20.New(c.password, iv) 87 | if err != nil { 88 | return n, err 89 | } 90 | 91 | c.StreamReader = &cipher.StreamReader{ 92 | S: stream, 93 | R: c.rwc, 94 | } 95 | } 96 | return c.StreamReader.Read(p) 97 | } 98 | 99 | func (c *Chacha20Cipher) Write(p []byte) (n int, err error) { 100 | if c.StreamWriter == nil { 101 | iv := make([]byte, chacha20.NonceSize) 102 | _, err = rand.Read(iv) 103 | if err != nil { 104 | return 0, err 105 | } 106 | stream, err := chacha20.New(c.password, iv) 107 | if err != nil { 108 | return n, err 109 | } 110 | c.StreamWriter = &cipher.StreamWriter{ 111 | S: stream, 112 | W: c.rwc, 113 | } 114 | n, err := c.rwc.Write(iv) 115 | if err != nil { 116 | return n, err 117 | } 118 | } 119 | return c.StreamWriter.Write(p) 120 | } 121 | 122 | type DESCFBCipher struct { 123 | block cipher.Block 124 | rwc io.ReadWriteCloser 125 | *cipher.StreamReader 126 | *cipher.StreamWriter 127 | } 128 | 129 | func NewDESCFBCipher(rwc io.ReadWriteCloser, password []byte) (*DESCFBCipher, error) { 130 | block, err := des.NewCipher(password) 131 | if err != nil { 132 | return nil, err 133 | } 134 | 135 | return &DESCFBCipher{ 136 | block: block, 137 | rwc: rwc, 138 | }, nil 139 | } 140 | 141 | func (d *DESCFBCipher) Read(p []byte) (n int, err error) { 142 | if d.StreamReader == nil { 143 | iv := make([]byte, d.block.BlockSize()) 144 | n, err = io.ReadFull(d.rwc, iv) 145 | if err != nil { 146 | return n, err 147 | } 148 | stream := cipher.NewCFBDecrypter(d.block, iv) 149 | d.StreamReader = &cipher.StreamReader{ 150 | S: stream, 151 | R: d.rwc, 152 | } 153 | } 154 | return d.StreamReader.Read(p) 155 | } 156 | 157 | func (d *DESCFBCipher) Write(p []byte) (n int, err error) { 158 | if d.StreamWriter == nil { 159 | iv := make([]byte, d.block.BlockSize()) 160 | _, err = rand.Read(iv) 161 | if err != nil { 162 | return 0, err 163 | } 164 | stream := cipher.NewCFBEncrypter(d.block, iv) 165 | d.StreamWriter = &cipher.StreamWriter{ 166 | S: stream, 167 | W: d.rwc, 168 | } 169 | n, err := d.rwc.Write(iv) 170 | if err != nil { 171 | return n, err 172 | } 173 | } 174 | return d.StreamWriter.Write(p) 175 | } 176 | 177 | func (d *DESCFBCipher) Close() error { 178 | if d.StreamWriter != nil { 179 | d.StreamWriter.Close() 180 | } 181 | if d.rwc != nil { 182 | d.rwc.Close() 183 | } 184 | return nil 185 | } 186 | 187 | type AESCFBCipher struct { 188 | rwc io.ReadWriteCloser 189 | iv []byte 190 | block cipher.Block 191 | *cipher.StreamReader 192 | *cipher.StreamWriter 193 | } 194 | 195 | func NewAESCFGCipher(rwc io.ReadWriteCloser, password []byte, bit int) (*AESCFBCipher, error) { 196 | block, err := aes.NewCipher(evpBytesToKey(password, bit)) 197 | if err != nil { 198 | return nil, err 199 | } 200 | return &AESCFBCipher{ 201 | block: block, 202 | rwc: rwc, 203 | }, nil 204 | } 205 | 206 | func (a *AESCFBCipher) Read(p []byte) (n int, err error) { 207 | if a.StreamReader == nil { 208 | iv := make([]byte, a.block.BlockSize()) 209 | n, err = io.ReadFull(a.rwc, iv) 210 | if err != nil { 211 | return n, err 212 | } 213 | stream := cipher.NewCFBDecrypter(a.block, iv) 214 | a.StreamReader = &cipher.StreamReader{ 215 | S: stream, 216 | R: a.rwc, 217 | } 218 | } 219 | return a.StreamReader.Read(p) 220 | } 221 | 222 | func (a *AESCFBCipher) Write(p []byte) (n int, err error) { 223 | if a.StreamWriter == nil { 224 | iv := make([]byte, a.block.BlockSize()) 225 | _, err = rand.Read(iv) 226 | if err != nil { 227 | return 0, err 228 | } 229 | stream := cipher.NewCFBEncrypter(a.block, iv) 230 | a.StreamWriter = &cipher.StreamWriter{ 231 | S: stream, 232 | W: a.rwc, 233 | } 234 | n, err := a.rwc.Write(iv) 235 | if err != nil { 236 | return n, err 237 | } 238 | } 239 | return a.StreamWriter.Write(p) 240 | } 241 | 242 | func (a *AESCFBCipher) Close() error { 243 | if a.StreamWriter != nil { 244 | a.StreamWriter.Close() 245 | } 246 | if a.rwc != nil { 247 | a.rwc.Close() 248 | } 249 | return nil 250 | } 251 | -------------------------------------------------------------------------------- /cmd/socksd/cipher_conn.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "strings" 7 | ) 8 | 9 | type CipherConn struct { 10 | net.Conn 11 | rwc io.ReadWriteCloser 12 | } 13 | 14 | func (c *CipherConn) Read(data []byte) (int, error) { 15 | return c.rwc.Read(data) 16 | } 17 | 18 | func (c *CipherConn) Write(data []byte) (int, error) { 19 | return c.rwc.Write(data) 20 | } 21 | 22 | func (c *CipherConn) Close() error { 23 | err := c.Conn.Close() 24 | c.rwc.Close() 25 | return err 26 | } 27 | 28 | func NewCipherConn(conn net.Conn, cryptMethod string, password []byte) (*CipherConn, error) { 29 | var rwc io.ReadWriteCloser 30 | var err error 31 | switch strings.ToLower(cryptMethod) { 32 | default: 33 | rwc = conn 34 | case "rc4": 35 | rwc, err = NewRC4Cipher(conn, password) 36 | case "des": 37 | rwc, err = NewDESCFBCipher(conn, password) 38 | case "aes-128-cfb": 39 | rwc, err = NewAESCFGCipher(conn, password, 16) 40 | case "aes-192-cfb": 41 | rwc, err = NewAESCFGCipher(conn, password, 24) 42 | case "aes-256-cfb": 43 | rwc, err = NewAESCFGCipher(conn, password, 32) 44 | case "chacha20": 45 | rwc, err = NewChacha20Cipher(conn, password) 46 | } 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | return &CipherConn{ 52 | Conn: conn, 53 | rwc: rwc, 54 | }, nil 55 | } 56 | 57 | func NewCipherConnDecorator(cryptoMethod, password string) ConnDecorator { 58 | return func(conn net.Conn) (net.Conn, error) { 59 | return NewCipherConn(conn, cryptoMethod, []byte(password)) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /cmd/socksd/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | ) 7 | 8 | type Upstream struct { 9 | Type string `json:"type"` 10 | Crypto string `json:"crypto"` 11 | Password string `json:"password"` 12 | Address string `json:"address"` 13 | } 14 | 15 | type PAC struct { 16 | Address string `json:"address"` 17 | Proxy string `json:"proxy"` 18 | SOCKS5 string `json:"socks5"` 19 | LocalRules string `json:"local_rule_file"` 20 | RemoteRules string `json:"remote_rule_file"` 21 | Upstream Upstream `json:"upstream"` 22 | } 23 | 24 | type Proxy struct { 25 | HTTP string `json:"http"` 26 | SOCKS4 string `json:"socks4"` 27 | SOCKS5 string `json:"socks5"` 28 | Crypto string `json:"crypto"` 29 | Password string `json:"password"` 30 | DNSCacheTimeout int `json:"dnsCacheTimeout"` 31 | Upstreams []Upstream `json:"upstreams"` 32 | } 33 | 34 | type Config struct { 35 | PAC PAC `json:"pac"` 36 | Proxies []Proxy `json:"proxies"` 37 | } 38 | 39 | func LoadConfig(s string) (*Config, error) { 40 | data, err := ioutil.ReadFile(s) 41 | if err != nil { 42 | return nil, err 43 | } 44 | cfgGroup := &Config{} 45 | if err = json.Unmarshal(data, cfgGroup); err != nil { 46 | return nil, err 47 | } 48 | return cfgGroup, nil 49 | } 50 | -------------------------------------------------------------------------------- /cmd/socksd/decorate_client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/eahydra/socks" 7 | ) 8 | 9 | type DecorateClient struct { 10 | forward socks.Dialer 11 | decorators []ConnDecorator 12 | } 13 | 14 | func NewDecorateClient(forward socks.Dialer, ds ...ConnDecorator) *DecorateClient { 15 | d := &DecorateClient{ 16 | forward: forward, 17 | } 18 | d.decorators = append(d.decorators, ds...) 19 | return d 20 | } 21 | 22 | func (d *DecorateClient) Dial(network, address string) (net.Conn, error) { 23 | conn, err := d.forward.Dial(network, address) 24 | if err != nil { 25 | ErrLog.Println("DecorateClient forward.Dial failed, err:", err, address) 26 | return nil, err 27 | } 28 | dconn, err := DecorateConn(conn, d.decorators...) 29 | if err != nil { 30 | conn.Close() 31 | return nil, err 32 | } 33 | return dconn, nil 34 | } 35 | -------------------------------------------------------------------------------- /cmd/socksd/decorate_conn.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net" 4 | 5 | type ConnDecorator func(net.Conn) (net.Conn, error) 6 | 7 | func DecorateConn(conn net.Conn, ds ...ConnDecorator) (net.Conn, error) { 8 | decorated := conn 9 | var err error 10 | for _, decorate := range ds { 11 | decorated, err = decorate(decorated) 12 | if err != nil { 13 | return nil, err 14 | } 15 | } 16 | return decorated, nil 17 | } 18 | -------------------------------------------------------------------------------- /cmd/socksd/decorate_direct.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/eahydra/socks" 7 | ) 8 | 9 | type DecorateDirect struct { 10 | dnsCache *DNSCache 11 | } 12 | 13 | func NewDecorateDirect(dnsCacheTime int) *DecorateDirect { 14 | var dnsCache *DNSCache 15 | if dnsCacheTime != 0 { 16 | dnsCache = NewDNSCache(dnsCacheTime) 17 | } 18 | return &DecorateDirect{ 19 | dnsCache: dnsCache, 20 | } 21 | } 22 | 23 | func parseAddress(address string) (interface{}, string, error) { 24 | host, port, err := net.SplitHostPort(address) 25 | if err != nil { 26 | return nil, "", err 27 | } 28 | ip := net.ParseIP(address) 29 | if ip != nil { 30 | return ip, port, nil 31 | } else { 32 | return host, port, nil 33 | } 34 | } 35 | 36 | func (d *DecorateDirect) Dial(network, address string) (net.Conn, error) { 37 | host, port, err := parseAddress(address) 38 | if err != nil { 39 | return nil, err 40 | } 41 | var dest string 42 | var ipCached bool 43 | switch h := host.(type) { 44 | case net.IP: 45 | { 46 | dest = h.String() 47 | ipCached = true 48 | } 49 | case string: 50 | { 51 | dest = h 52 | if d.dnsCache != nil { 53 | if p, ok := d.dnsCache.Get(h); ok { 54 | dest = p.String() 55 | ipCached = true 56 | } 57 | } 58 | } 59 | } 60 | address = net.JoinHostPort(dest, port) 61 | destConn, err := socks.Direct.Dial(network, address) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | if d.dnsCache != nil && !ipCached { 67 | d.dnsCache.Set(host.(string), destConn.RemoteAddr().(*net.TCPAddr).IP) 68 | } 69 | return destConn, nil 70 | } 71 | -------------------------------------------------------------------------------- /cmd/socksd/decorate_listener.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net" 4 | 5 | type DecorateListener struct { 6 | listener net.Listener 7 | decorators []ConnDecorator 8 | } 9 | 10 | func NewDecorateListener(listener net.Listener, ds ...ConnDecorator) *DecorateListener { 11 | l := &DecorateListener{ 12 | listener: listener, 13 | } 14 | l.decorators = append(l.decorators, ds...) 15 | return l 16 | } 17 | 18 | func (s *DecorateListener) Accept() (net.Conn, error) { 19 | conn, err := s.listener.Accept() 20 | if err != nil { 21 | return nil, err 22 | } 23 | dconn, err := DecorateConn(conn, s.decorators...) 24 | if err != nil { 25 | conn.Close() 26 | return nil, err 27 | } 28 | return dconn, nil 29 | } 30 | 31 | func (s *DecorateListener) Close() error { 32 | return s.listener.Close() 33 | } 34 | 35 | func (s *DecorateListener) Addr() net.Addr { 36 | return s.listener.Addr() 37 | } 38 | -------------------------------------------------------------------------------- /cmd/socksd/dns_cache.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type DNSCache struct { 10 | lock sync.RWMutex 11 | cacheTimeout time.Duration 12 | dns map[string]DNSElement 13 | } 14 | 15 | type DNSElement struct { 16 | ip net.IP 17 | startTime time.Time 18 | } 19 | 20 | func NewDNSCache(cacheTimeout int) *DNSCache { 21 | if cacheTimeout <= 0 { 22 | cacheTimeout = 30 23 | } 24 | return &DNSCache{ 25 | cacheTimeout: time.Duration(cacheTimeout) * time.Minute, 26 | dns: make(map[string]DNSElement), 27 | } 28 | } 29 | 30 | func (c *DNSCache) Get(domain string) (net.IP, bool) { 31 | c.lock.RLock() 32 | e, ok := c.dns[domain] 33 | c.lock.RUnlock() 34 | if ok && time.Since(e.startTime) > c.cacheTimeout { 35 | c.lock.Lock() 36 | delete(c.dns, domain) 37 | c.lock.Unlock() 38 | return nil, false 39 | } 40 | return e.ip, ok 41 | } 42 | 43 | func (c *DNSCache) Set(domain string, ip net.IP) { 44 | c.lock.Lock() 45 | c.dns[domain] = DNSElement{ip: ip, startTime: time.Now()} 46 | c.lock.Unlock() 47 | } 48 | -------------------------------------------------------------------------------- /cmd/socksd/log.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | ) 7 | 8 | var ( 9 | InfoLog = log.New(os.Stdout, "INFO ", log.LstdFlags) 10 | ErrLog = log.New(os.Stderr, "ERROR ", log.LstdFlags) 11 | WarnLog = log.New(os.Stdout, "WARN ", log.LstdFlags) 12 | ) 13 | -------------------------------------------------------------------------------- /cmd/socksd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "flag" 7 | "net" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "strings" 12 | 13 | "github.com/eahydra/socks" 14 | ) 15 | 16 | func main() { 17 | var configFile string 18 | flag.StringVar(&configFile, "c", "socks.config", "config file path") 19 | flag.Parse() 20 | 21 | conf, err := LoadConfig(configFile) 22 | if err != nil { 23 | ErrLog.Println("initGlobalConfig failed, err:", err) 24 | return 25 | } 26 | InfoLog.Println("load config succeeded") 27 | 28 | for _, c := range conf.Proxies { 29 | router := BuildUpstreamRouter(c) 30 | runHTTPProxyServer(c, router) 31 | runSOCKS4Server(c, router) 32 | runSOCKS5Server(c, router) 33 | } 34 | runPACServer(conf.PAC) 35 | 36 | sigChan := make(chan os.Signal, 1) 37 | signal.Notify(sigChan, os.Kill, os.Interrupt) 38 | <-sigChan 39 | } 40 | 41 | func BuildUpstream(upstream Upstream, forward socks.Dialer) (socks.Dialer, error) { 42 | cipherDecorator := NewCipherConnDecorator(upstream.Crypto, upstream.Password) 43 | forward = NewDecorateClient(forward, cipherDecorator) 44 | 45 | switch strings.ToLower(upstream.Type) { 46 | case "socks5": 47 | { 48 | return socks.NewSocks5Client("tcp", upstream.Address, "", "", forward) 49 | } 50 | case "shadowsocks": 51 | { 52 | return socks.NewShadowSocksClient("tcp", upstream.Address, forward) 53 | } 54 | } 55 | return nil, errors.New("unknown upstream type" + upstream.Type) 56 | } 57 | 58 | func BuildUpstreamRouter(conf Proxy) socks.Dialer { 59 | var allForward []socks.Dialer 60 | for _, upstream := range conf.Upstreams { 61 | var forward socks.Dialer 62 | var err error 63 | forward = NewDecorateDirect(conf.DNSCacheTimeout) 64 | forward, err = BuildUpstream(upstream, forward) 65 | if err != nil { 66 | ErrLog.Println("failed to BuildUpstream, err:", err) 67 | continue 68 | } 69 | allForward = append(allForward, forward) 70 | } 71 | if len(allForward) == 0 { 72 | router := NewDecorateDirect(conf.DNSCacheTimeout) 73 | allForward = append(allForward, router) 74 | } 75 | return NewUpstreamDialer(allForward) 76 | } 77 | 78 | func runHTTPProxyServer(conf Proxy, router socks.Dialer) { 79 | if conf.HTTP != "" { 80 | listener, err := net.Listen("tcp", conf.HTTP) 81 | if err != nil { 82 | ErrLog.Println("net.Listen at ", conf.HTTP, " failed, err:", err) 83 | return 84 | } 85 | go func() { 86 | defer listener.Close() 87 | httpProxy := socks.NewHTTPProxy(router) 88 | http.Serve(listener, httpProxy) 89 | }() 90 | } 91 | } 92 | 93 | func runSOCKS4Server(conf Proxy, forward socks.Dialer) { 94 | if conf.SOCKS4 != "" { 95 | listener, err := net.Listen("tcp", conf.SOCKS4) 96 | if err != nil { 97 | ErrLog.Println("net.Listen failed, err:", err, conf.SOCKS4) 98 | return 99 | } 100 | cipherDecorator := NewCipherConnDecorator(conf.Crypto, conf.Password) 101 | listener = NewDecorateListener(listener, cipherDecorator) 102 | socks4Svr, err := socks.NewSocks4Server(forward) 103 | if err != nil { 104 | listener.Close() 105 | ErrLog.Println("socks.NewSocks4Server failed, err:", err) 106 | } 107 | go func() { 108 | defer listener.Close() 109 | socks4Svr.Serve(listener) 110 | }() 111 | } 112 | } 113 | 114 | func runSOCKS5Server(conf Proxy, forward socks.Dialer) { 115 | if conf.SOCKS5 != "" { 116 | listener, err := net.Listen("tcp", conf.SOCKS5) 117 | if err != nil { 118 | ErrLog.Println("net.Listen failed, err:", err, conf.SOCKS5) 119 | return 120 | } 121 | cipherDecorator := NewCipherConnDecorator(conf.Crypto, conf.Password) 122 | listener = NewDecorateListener(listener, cipherDecorator) 123 | socks5Svr, err := socks.NewSocks5Server(forward) 124 | if err != nil { 125 | listener.Close() 126 | ErrLog.Println("socks.NewSocks5Server failed, err:", err) 127 | return 128 | } 129 | go func() { 130 | defer listener.Close() 131 | socks5Svr.Serve(listener) 132 | }() 133 | } 134 | } 135 | 136 | func runPACServer(pac PAC) { 137 | pu, err := NewPACUpdater(pac) 138 | if err != nil { 139 | ErrLog.Println("failed to NewPACUpdater, err:", err) 140 | return 141 | } 142 | 143 | http.HandleFunc("/proxy.pac", func(w http.ResponseWriter, r *http.Request) { 144 | w.Header().Add("Content-Type", "application/x-ns-proxy-autoconfig") 145 | data, time := pu.get() 146 | reader := bytes.NewReader(data) 147 | http.ServeContent(w, r, "proxy.pac", time, reader) 148 | }) 149 | go http.ListenAndServe(pac.Address, nil) 150 | } 151 | -------------------------------------------------------------------------------- /cmd/socksd/pac_generator.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "sort" 7 | "text/template" 8 | ) 9 | 10 | var pacTemplate = ` 11 | var proxy = "{{if .Socks5}}SOCKS5 {{.Socks5}};{{end}}{{if .Proxy}}PROXY {{.Proxy}}{{end}};"; 12 | 13 | var domains = { 14 | {{.Rules}} 15 | }; 16 | 17 | var hasOwnProperty = Object.hasOwnProperty; 18 | 19 | function FindProxyForURL(url, host) { 20 | if (isPlainHostName(host) || host === '127.0.0.1' || host === 'localhost') { 21 | return 'DIRECT'; 22 | } 23 | var suffix; 24 | var pos = host.lastIndexOf('.'); 25 | while(1) { 26 | suffix = host.substring(pos + 1); 27 | if (hasOwnProperty.call(domains, suffix)) { 28 | return proxy; 29 | } 30 | if (pos <= 0) { 31 | break; 32 | } 33 | pos = host.lastIndexOf('.', pos - 1); 34 | } 35 | return 'DIRECT'; 36 | } 37 | ` 38 | 39 | type PACGenerator struct { 40 | filter map[string]bool 41 | rules []string 42 | proxy string 43 | socks5 string 44 | } 45 | 46 | func NewPACGenerator(proxy, socks5 string) *PACGenerator { 47 | return &PACGenerator{ 48 | filter: make(map[string]bool), 49 | proxy: proxy, 50 | socks5: socks5, 51 | } 52 | } 53 | 54 | func (p *PACGenerator) Generate(rules []string) ([]byte, error) { 55 | for _, v := range rules { 56 | if _, ok := p.filter[v]; !ok { 57 | p.filter[v] = true 58 | p.rules = append(p.rules, v) 59 | } 60 | } 61 | sort.Strings(p.rules) 62 | 63 | s := "" 64 | for n, v := range p.rules { 65 | s += fmt.Sprintf("'%s' : 1", v) 66 | if n+1 != len(p.rules) { 67 | s += "," 68 | } 69 | } 70 | 71 | data := struct { 72 | Proxy string 73 | Socks5 string 74 | Rules string 75 | }{ 76 | Proxy: p.proxy, 77 | Socks5: p.socks5, 78 | Rules: s, 79 | } 80 | t, err := template.New("proxy.pac").Parse(pacTemplate) 81 | if err != nil { 82 | ErrLog.Println("failed to parse pacTempalte, err:", err) 83 | } 84 | buff := bytes.NewBuffer(nil) 85 | err = t.Execute(buff, &data) 86 | if err != nil { 87 | return nil, err 88 | } 89 | return buff.Bytes(), nil 90 | } 91 | -------------------------------------------------------------------------------- /cmd/socksd/pac_updater.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "net/http" 7 | "os" 8 | "sync" 9 | "time" 10 | 11 | "github.com/eahydra/socks" 12 | ) 13 | 14 | type PACUpdater struct { 15 | pac PAC 16 | lock sync.RWMutex 17 | data []byte 18 | modtime time.Time 19 | timer *time.Timer 20 | } 21 | 22 | func NewPACUpdater(pac PAC) (*PACUpdater, error) { 23 | p := &PACUpdater{ 24 | pac: pac, 25 | } 26 | go p.backgroundUpdate() 27 | return p, nil 28 | } 29 | 30 | func parseRule(reader io.Reader) ([]string, error) { 31 | var err error 32 | var line []byte 33 | var rules []string 34 | r := bufio.NewReader(reader) 35 | for line, _, err = r.ReadLine(); err == nil; line, _, err = r.ReadLine() { 36 | s := string(line) 37 | if s != "" { 38 | rules = append(rules, s) 39 | } 40 | } 41 | if err == io.EOF { 42 | err = nil 43 | } 44 | return rules, err 45 | } 46 | 47 | func loadLocalRule(filepath string) ([]string, error) { 48 | f, err := os.OpenFile(filepath, os.O_RDONLY, os.ModePerm) 49 | if err != nil { 50 | return nil, err 51 | } 52 | defer f.Close() 53 | return parseRule(f) 54 | } 55 | 56 | func loadRemoteRule(ruleURL string, upstream Upstream) ([]string, error) { 57 | forward, err := BuildUpstream(upstream, socks.Direct) 58 | if err != nil { 59 | return nil, err 60 | } 61 | client := &http.Client{ 62 | Transport: &http.Transport{ 63 | Dial: forward.Dial, 64 | }, 65 | } 66 | resp, err := client.Get(ruleURL) 67 | if err != nil { 68 | return nil, err 69 | } 70 | defer resp.Body.Close() 71 | return parseRule(resp.Body) 72 | } 73 | 74 | func (p *PACUpdater) get() ([]byte, time.Time) { 75 | p.lock.RLock() 76 | defer p.lock.RUnlock() 77 | d := make([]byte, len(p.data)) 78 | copy(d, p.data) 79 | return d, p.modtime 80 | } 81 | 82 | func (p *PACUpdater) set(data []byte) { 83 | p.lock.Lock() 84 | defer p.lock.Unlock() 85 | p.data = make([]byte, len(data)) 86 | copy(p.data, data) 87 | p.modtime = time.Now() 88 | } 89 | 90 | func (p *PACUpdater) backgroundUpdate() { 91 | pg := NewPACGenerator(p.pac.Proxy, p.pac.SOCKS5) 92 | for { 93 | duration := 1 * time.Minute 94 | if rules, err := loadLocalRule(p.pac.LocalRules); err == nil { 95 | if data, err := pg.Generate(rules); err == nil { 96 | p.set(data) 97 | InfoLog.Println("update rules from", p.pac.LocalRules, "succeeded") 98 | } 99 | } 100 | 101 | if rules, err := loadRemoteRule(p.pac.RemoteRules, p.pac.Upstream); err == nil { 102 | if data, err := pg.Generate(rules); err == nil { 103 | p.set(data) 104 | duration = 1 * time.Hour 105 | InfoLog.Println("update rules from", p.pac.RemoteRules, "succeeded") 106 | } 107 | } 108 | time.Sleep(duration) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /cmd/socksd/upstream_client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "sync/atomic" 6 | 7 | "github.com/eahydra/socks" 8 | ) 9 | 10 | type UpstreamDialer struct { 11 | nextRouter uint64 12 | forwardDialers []socks.Dialer 13 | } 14 | 15 | func NewUpstreamDialer(forwardDialers []socks.Dialer) *UpstreamDialer { 16 | return &UpstreamDialer{ 17 | forwardDialers: forwardDialers, 18 | } 19 | } 20 | 21 | func (u *UpstreamDialer) getNextDialer() socks.Dialer { 22 | old := atomic.AddUint64(&u.nextRouter, 1) 23 | return u.forwardDialers[old%uint64(len(u.forwardDialers))] 24 | } 25 | 26 | func (u *UpstreamDialer) Dial(network, address string) (net.Conn, error) { 27 | router := u.getNextDialer() 28 | conn, err := router.Dial(network, address) 29 | if err != nil { 30 | ErrLog.Println("UpstreamDialer router.Dial failed, err:", err, network, address) 31 | return nil, err 32 | } 33 | return conn, nil 34 | } 35 | -------------------------------------------------------------------------------- /direct.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import "net" 4 | 5 | // A Dialer is a means to establish a connection. 6 | type Dialer interface { 7 | // Dial connects to the given address via the proxy. 8 | Dial(network, address string) (net.Conn, error) 9 | } 10 | 11 | type direct struct{} 12 | 13 | // Direct is a direct proxy which implements Dialer interface: one that makes connections directly. 14 | var Direct = direct{} 15 | 16 | func (direct) Dial(network, address string) (net.Conn, error) { 17 | return net.Dial(network, address) 18 | } 19 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "net/http/httputil" 7 | ) 8 | 9 | func ExampleSocks5Client() { 10 | user := "" 11 | password := "" 12 | client, err := NewSocks5Client("tcp", "127.0.0.1:1080", user, password, Direct) 13 | if err != nil { 14 | return 15 | } 16 | conn, err := client.Dial("tcp", "www.google.com:80") 17 | if err != nil { 18 | return 19 | } 20 | httpClient := httputil.NewClientConn(conn, nil) 21 | defer httpClient.Close() 22 | 23 | request, err := http.NewRequest("GET", "/", nil) 24 | if err != nil { 25 | return 26 | } 27 | resp, err := httpClient.Do(request) 28 | if err != nil { 29 | return 30 | } 31 | dump, err := httputil.DumpResponse(resp, true) 32 | if err != nil { 33 | return 34 | } 35 | println(string(dump)) 36 | return 37 | } 38 | 39 | func ExampleSocks5Server() { 40 | listener, err := net.Listen("tcp", ":1080") 41 | if err != nil { 42 | return 43 | } 44 | defer listener.Close() 45 | 46 | if server, err := NewSocks5Server(Direct); err == nil { 47 | server.Serve(listener) 48 | } 49 | } 50 | 51 | func ExampleSocks4Client() { 52 | user := "" 53 | client, err := NewSocks4Client("tcp", "127.0.0.1:1080", user, Direct) 54 | if err != nil { 55 | return 56 | } 57 | addrs, err := net.LookupHost("www.google.com") 58 | if err != nil { 59 | return 60 | } 61 | if len(addrs) == 0 { 62 | return 63 | } 64 | conn, err := client.Dial("tcp", addrs[0]+":80") 65 | if err != nil { 66 | return 67 | } 68 | httpClient := httputil.NewClientConn(conn, nil) 69 | defer httpClient.Close() 70 | 71 | request, err := http.NewRequest("GET", "/", nil) 72 | if err != nil { 73 | return 74 | } 75 | resp, err := httpClient.Do(request) 76 | if err != nil { 77 | return 78 | } 79 | dump, err := httputil.DumpResponse(resp, true) 80 | if err != nil { 81 | return 82 | } 83 | println(string(dump)) 84 | return 85 | } 86 | 87 | func ExampleSocks4Server() { 88 | listener, err := net.Listen("tcp", ":1080") 89 | if err != nil { 90 | return 91 | } 92 | defer listener.Close() 93 | 94 | if server, err := NewSocks4Server(Direct); err == nil { 95 | server.Serve(listener) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/eahydra/socks 2 | 3 | go 1.13 4 | 5 | require github.com/codahale/chacha20 v0.0.0-20151107025005-ec07b4f69a3f 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/codahale/chacha20 v0.0.0-20151107025005-ec07b4f69a3f h1:GnkFLgLBj4MDb+UdR1yFlznatawt2U7tEGF1HCwcMSs= 2 | github.com/codahale/chacha20 v0.0.0-20151107025005-ec07b4f69a3f/go.mod h1:2EU+1emidIWL7uTbVXfPFlgYxYo3TGHz+ElH1Tp5GT0= 3 | -------------------------------------------------------------------------------- /http_proxy.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net" 7 | "net/http" 8 | "net/http/httputil" 9 | "net/url" 10 | ) 11 | 12 | // HTTPProxy is an HTTP Handler that serve CONNECT method and 13 | // route request to proxy server by Router. 14 | type HTTPProxy struct { 15 | *httputil.ReverseProxy 16 | forward Dialer 17 | } 18 | 19 | // NewHTTPProxy constructs one HTTPProxy 20 | func NewHTTPProxy(forward Dialer) *HTTPProxy { 21 | return &HTTPProxy{ 22 | ReverseProxy: &httputil.ReverseProxy{ 23 | Director: director, 24 | Transport: &http.Transport{ 25 | Dial: func(network, addr string) (net.Conn, error) { 26 | return forward.Dial(network, addr) 27 | }, 28 | }, 29 | }, 30 | forward: forward, 31 | } 32 | } 33 | 34 | func director(request *http.Request) { 35 | u, err := url.Parse(request.RequestURI) 36 | if err != nil { 37 | return 38 | } 39 | request.RequestURI = u.RequestURI() 40 | v := request.Header.Get("Proxy-Connection") 41 | if v != "" { 42 | request.Header.Del("Proxy-Connection") 43 | request.Header.Del("Connection") 44 | request.Header.Add("Connection", v) 45 | } 46 | } 47 | 48 | // ServeHTTPTunnel serve incoming request with CONNECT method, then route data to proxy server 49 | func (h *HTTPProxy) ServeHTTPTunnel(response http.ResponseWriter, request *http.Request) { 50 | var conn net.Conn 51 | if hj, ok := response.(http.Hijacker); ok { 52 | var err error 53 | if conn, _, err = hj.Hijack(); err != nil { 54 | http.Error(response, err.Error(), http.StatusInternalServerError) 55 | return 56 | } 57 | } else { 58 | http.Error(response, "Hijacker failed", http.StatusInternalServerError) 59 | return 60 | } 61 | defer conn.Close() 62 | 63 | dest, err := h.forward.Dial("tcp", request.Host) 64 | if err != nil { 65 | fmt.Fprintf(conn, "HTTP/1.0 500 NewRemoteSocks failed, err:%s\r\n\r\n", err) 66 | return 67 | } 68 | defer dest.Close() 69 | 70 | if request.Body != nil { 71 | if _, err = io.Copy(dest, request.Body); err != nil { 72 | fmt.Fprintf(conn, "%d %s", http.StatusBadGateway, err.Error()) 73 | return 74 | } 75 | } 76 | fmt.Fprintf(conn, "HTTP/1.0 200 Connection established\r\n\r\n") 77 | 78 | go func() { 79 | defer conn.Close() 80 | defer dest.Close() 81 | io.Copy(dest, conn) 82 | }() 83 | io.Copy(conn, dest) 84 | } 85 | 86 | // ServeHTTP implements HTTP Handler 87 | func (h *HTTPProxy) ServeHTTP(response http.ResponseWriter, request *http.Request) { 88 | if request.Method == "CONNECT" { 89 | h.ServeHTTPTunnel(response, request) 90 | } else { 91 | h.ReverseProxy.ServeHTTP(response, request) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /shadowsocks_client.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "strconv" 7 | ) 8 | 9 | // ShadowSocksClient implements ShadowSocks Proxy Protocol 10 | type ShadowSocksClient struct { 11 | network string 12 | address string 13 | forward Dialer 14 | } 15 | 16 | // NewShadowSocksClient return a new ShadowSocksClient that implements Dialer interface. 17 | func NewShadowSocksClient(network, address string, forward Dialer) (*ShadowSocksClient, error) { 18 | return &ShadowSocksClient{ 19 | network: network, 20 | address: address, 21 | forward: forward, 22 | }, nil 23 | } 24 | 25 | // Dial return a new net.Conn that through proxy server establish with address 26 | func (s *ShadowSocksClient) Dial(network, address string) (net.Conn, error) { 27 | switch network { 28 | case "tcp", "tcp4", "tcp6": 29 | default: 30 | return nil, errors.New("socks: no support ShadowSocks proxy connections of type: " + network) 31 | } 32 | 33 | host, portStr, err := net.SplitHostPort(address) 34 | if err != nil { 35 | return nil, err 36 | } 37 | port, err := strconv.Atoi(portStr) 38 | if err != nil { 39 | return nil, errors.New("socks: failed to parse port number:" + portStr) 40 | } 41 | if port < 1 || port > 0xffff { 42 | return nil, errors.New("socks5: port number out of range:" + portStr) 43 | } 44 | 45 | conn, err := s.forward.Dial(s.network, s.address) 46 | if err != nil { 47 | return nil, err 48 | } 49 | closeConn := &conn 50 | defer func() { 51 | if closeConn != nil { 52 | (*closeConn).Close() 53 | } 54 | }() 55 | 56 | buff := make([]byte, 0, 266) 57 | if ip := net.ParseIP(host); ip != nil { 58 | if ip4 := ip.To4(); ip4 != nil { 59 | buff = append(buff, 1) 60 | ip = ip4 61 | } else { 62 | buff = append(buff, 4) 63 | } 64 | buff = append(buff, ip...) 65 | } else { 66 | if len(host) > 255 { 67 | return nil, errors.New("socks: destination hostname too long: " + host) 68 | } 69 | buff = append(buff, 3) 70 | buff = append(buff, uint8(len(host))) 71 | buff = append(buff, host...) 72 | } 73 | buff = append(buff, uint8(port>>8), uint8(port)) 74 | 75 | _, err = conn.Write(buff) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | closeConn = nil 81 | return conn, nil 82 | } 83 | -------------------------------------------------------------------------------- /socks4.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "net" 9 | "strconv" 10 | ) 11 | 12 | const ( 13 | socks4Version = 4 14 | socks4Connect = 1 15 | socks4Granted = 90 16 | socks4Rejected = 91 17 | socks4ConnectFailed = 92 18 | socks4UserIDInvalid = 93 19 | ) 20 | 21 | var socks4Errors = []string{ 22 | "", 23 | "request rejected or failed", 24 | "request rejected because SOCKS server cannot connect to identd on the client", 25 | "request rejected because the client program and identd report different user-ids", 26 | } 27 | 28 | // Socks4Server implements Socks4 Proxy Protocol(http://www.openssh.com/txt/socks4.protocol). 29 | // Just support CONNECT command. 30 | type Socks4Server struct { 31 | forward Dialer 32 | } 33 | 34 | // NewSocks4Server returns a new Socks4Server that can serve from new clients. 35 | func NewSocks4Server(forward Dialer) (*Socks4Server, error) { 36 | return &Socks4Server{ 37 | forward: forward, 38 | }, nil 39 | } 40 | 41 | // Serve with net.Listener for clients. 42 | func (s *Socks4Server) Serve(listener net.Listener) error { 43 | for { 44 | conn, err := listener.Accept() 45 | if err != nil { 46 | if netErr, ok := err.(net.Error); ok && netErr.Temporary() { 47 | continue 48 | } else { 49 | return err 50 | } 51 | } 52 | 53 | go serveSOCKS4Client(conn, s.forward) 54 | } 55 | } 56 | 57 | // Socks4Client implements Socks4 Proxy Protocol(http://www.openssh.com/txt/socks4.protocol). 58 | type Socks4Client struct { 59 | network string 60 | address string 61 | userID string 62 | forward Dialer 63 | } 64 | 65 | // NewSocks4Client return a new Socks4Client that implements Dialer interface. 66 | // network must be supported by forward, address is proxy server's address, userID can empty. 67 | func NewSocks4Client(network, address, userID string, forward Dialer) (*Socks4Client, error) { 68 | return &Socks4Client{ 69 | network: network, 70 | address: address, 71 | userID: userID, 72 | forward: forward, 73 | }, nil 74 | } 75 | 76 | // Dial return a new net.Conn if succeeded. network must be tcp, tcp4 or tcp6, address only is IPV4. 77 | func (s *Socks4Client) Dial(network, address string) (net.Conn, error) { 78 | switch network { 79 | case "tcp", "tcp4", "tcp6": 80 | default: 81 | return nil, errors.New("socks: no support for SOCKS4 proxy connections of type:" + network) 82 | } 83 | 84 | host, portStr, err := net.SplitHostPort(address) 85 | if err != nil { 86 | return nil, err 87 | } 88 | port, err := strconv.Atoi(portStr) 89 | if err != nil { 90 | return nil, errors.New("socks: failed to parse port:" + portStr) 91 | } 92 | if port < 1 || port > 0xffff { 93 | return nil, errors.New("socks: port number out of range:" + portStr) 94 | } 95 | ip := net.ParseIP(host) 96 | if ip == nil { 97 | return nil, errors.New("socks: destination host invalid:" + host) 98 | } 99 | ip4 := ip.To4() 100 | if ip4 == nil { 101 | return nil, errors.New("socks:destination ip must be ipv4:" + host) 102 | } 103 | 104 | conn, err := s.forward.Dial(s.network, s.address) 105 | if err != nil { 106 | return nil, err 107 | } 108 | closeConn := &conn 109 | defer func() { 110 | if closeConn != nil { 111 | (*closeConn).Close() 112 | } 113 | }() 114 | 115 | buff := make([]byte, 0, 8+len(s.userID)+1) 116 | buff = append(buff, socks4Version, socks4Connect) 117 | buff = append(buff, byte(port>>8), byte(port)) 118 | buff = append(buff, ip4...) 119 | if len(s.userID) != 0 { 120 | buff = append(buff, []byte(s.userID)...) 121 | } 122 | buff = append(buff, 0) 123 | 124 | if _, err := conn.Write(buff); err != nil { 125 | return nil, errors.New("socks: failed to write connect request to SOCKS4 server at: " + s.address + ": " + err.Error()) 126 | } 127 | buff = buff[:8] 128 | if _, err := io.ReadFull(conn, buff); err != nil { 129 | return nil, errors.New("socks: failed to read connect reply from SOCKS4 server at: " + s.address + ": " + err.Error()) 130 | } 131 | if buff[1] != socks4Granted { 132 | cd := int(buff[1]) - socks4Granted 133 | failure := "unknown error" 134 | if cd < len(socks4Errors) && cd >= 0 { 135 | failure = socks4Errors[cd] 136 | } 137 | return nil, errors.New("socks: SOCKS4 server at " + s.address + " failed to connect: " + failure) 138 | } 139 | 140 | closeConn = nil 141 | return conn, nil 142 | } 143 | 144 | func serveSOCKS4Client(conn net.Conn, forward Dialer) { 145 | defer conn.Close() 146 | 147 | reader := bufio.NewReader(conn) 148 | buff, err := reader.Peek(9) 149 | if err != nil { 150 | return 151 | } 152 | if buff[8] != 0 { 153 | if _, err = reader.ReadSlice(0); err != nil { 154 | return 155 | } 156 | } 157 | 158 | reply := make([]byte, 8) 159 | if buff[0] != socks4Version { 160 | reply[1] = socks4Rejected 161 | conn.Write(reply) 162 | return 163 | } 164 | if buff[1] != socks4Connect { 165 | reply[1] = socks4Rejected 166 | conn.Write(reply) 167 | return 168 | } 169 | 170 | port := uint16(buff[2])<<8 | uint16(buff[3]) 171 | ip := buff[4:8] 172 | 173 | host := fmt.Sprintf("%d.%d.%d.%d:%d", ip[0], ip[1], ip[2], ip[3], port) 174 | dest, err := forward.Dial("tcp4", host) 175 | if err != nil { 176 | reply[1] = socks4ConnectFailed 177 | conn.Write(reply) 178 | return 179 | } 180 | defer dest.Close() 181 | 182 | reply[1] = socks4Granted 183 | if _, err = conn.Write(reply); err != nil { 184 | return 185 | } 186 | 187 | go func() { 188 | defer conn.Close() 189 | defer dest.Close() 190 | io.Copy(dest, conn) 191 | }() 192 | io.Copy(conn, dest) 193 | } 194 | -------------------------------------------------------------------------------- /socks4_test.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "net/http/httputil" 7 | "testing" 8 | ) 9 | 10 | const ( 11 | TestSocks4ServerAddr = "127.0.0.1:8000" 12 | TestSocks4TargetDomain = "www.baidu.com" 13 | ) 14 | 15 | func TestSocks4Client(t *testing.T) { 16 | listener, err := net.Listen("tcp", TestSocks4ServerAddr) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | defer listener.Close() 21 | go func() { 22 | server, err := NewSocks4Server(Direct) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | server.Serve(listener) 27 | }() 28 | 29 | client, err := NewSocks4Client("tcp", TestSocks4ServerAddr, "", Direct) 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | 34 | addrs, err := net.LookupHost(TestSocks4TargetDomain) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | if len(addrs) == 0 { 39 | t.Fatal("net.LookupHost with " + TestSocks4TargetDomain + " result invalid") 40 | } 41 | 42 | conn, err := client.Dial("tcp", addrs[0]+":80") 43 | if err != nil { 44 | t.Fatal("socks4 client.Dial failed, err: " + err.Error()) 45 | } 46 | t.Log("client.Dial succeeded") 47 | httpClient := httputil.NewClientConn(conn, nil) 48 | if err != nil { 49 | conn.Close() 50 | t.Fatal(err) 51 | } 52 | defer httpClient.Close() 53 | 54 | request, err := http.NewRequest("GET", "/", nil) 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | resp, err := httpClient.Do(request) 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | data, err := httputil.DumpResponse(resp, true) 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | t.Log("socks4 HTTP Get:", string(data)) 67 | } 68 | -------------------------------------------------------------------------------- /socks5_client.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "net" 7 | "strconv" 8 | ) 9 | 10 | const ( 11 | socks5Version = 5 12 | 13 | socks5AuthNone = 0 14 | socks5AuthPassword = 2 15 | socks5AuthNoAccept = 0xff 16 | 17 | socks5AuthPasswordVer = 1 18 | 19 | socks5Connect = 1 20 | 21 | socks5IP4 = 1 22 | socks5Domain = 3 23 | socks5IP6 = 4 24 | ) 25 | 26 | const ( 27 | socks5Success = 0 28 | socks5GeneralFailure = 1 29 | socks5ConnectNotAllowed = 2 30 | socks5NetworkUnreachable = 3 31 | socks5HostUnreachable = 4 32 | socks5ConnectionRefused = 5 33 | socks5TTLExpired = 6 34 | socks5CommandNotSupported = 7 35 | socks5AddressTypeNotSupported = 8 36 | ) 37 | 38 | var socks5Errors = []string{ 39 | "", 40 | "general SOCKS server failure", 41 | "connection not allowed by ruleset", 42 | "network unreachable", 43 | "Host unreachable", 44 | "Connection refused", 45 | "TTL expired", 46 | "Command not supported", 47 | "Address type not supported", 48 | } 49 | 50 | // Socks5Client implements Socks5 Proxy Protocol(RFC 1928) Client Protocol. 51 | // Just support CONNECT command, and support USERNAME/PASSWORD authentication methods(RFC 1929) 52 | type Socks5Client struct { 53 | network string 54 | address string 55 | user string 56 | password string 57 | forward Dialer 58 | } 59 | 60 | // NewSocks5Client return a new Socks5Client that implements Dialer interface. 61 | func NewSocks5Client(network, address, user, password string, forward Dialer) (*Socks5Client, error) { 62 | return &Socks5Client{ 63 | network: network, 64 | address: address, 65 | user: user, 66 | password: password, 67 | forward: forward, 68 | }, nil 69 | } 70 | 71 | // Dial return a new net.Conn that through the CONNECT command to establish connections with proxy server. 72 | // address as RFC's requirements that can be IPV4, IPV6 and domain host, such as 8.8.8.8:999 or google.com:80 73 | func (s *Socks5Client) Dial(network, address string) (net.Conn, error) { 74 | switch network { 75 | case "tcp", "tcp4", "tcp6": 76 | default: 77 | return nil, errors.New("socks: no support for SOCKS5 proxy connections of type:" + network) 78 | } 79 | 80 | conn, err := s.forward.Dial(s.network, s.address) 81 | if err != nil { 82 | return nil, err 83 | } 84 | closeConn := &conn 85 | defer func() { 86 | if closeConn != nil { 87 | (*closeConn).Close() 88 | } 89 | }() 90 | 91 | host, portStr, err := net.SplitHostPort(address) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | // check port 97 | port, err := strconv.Atoi(portStr) 98 | if err != nil { 99 | return nil, errors.New("socks: failed to parse port number: " + portStr) 100 | } 101 | if port < 1 || port > 0xffff { 102 | return nil, errors.New("socks: port number out of range: " + portStr) 103 | } 104 | 105 | buff := make([]byte, 0, 6+len(host)) 106 | 107 | buff = append(buff, socks5Version) 108 | 109 | // set authentication methods 110 | if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 { 111 | buff = append(buff, 2, socks5AuthNone, socks5AuthPassword) 112 | } else { 113 | buff = append(buff, 1, socks5AuthNone) 114 | } 115 | 116 | // send authentication methods 117 | if _, err := conn.Write(buff); err != nil { 118 | return nil, errors.New("socks: failed to write handshake request at: " + s.address + ": " + err.Error()) 119 | } 120 | if _, err := io.ReadFull(conn, buff[:2]); err != nil { 121 | return nil, errors.New("socks: failed to read handshake reply at: " + s.address + ": " + err.Error()) 122 | } 123 | 124 | // handle authentication methods reply 125 | if buff[0] != socks5Version { 126 | return nil, errors.New("socks: SOCKS5 server at: " + s.address + " invalid version" + strconv.Itoa(int(buff[0]))) 127 | } 128 | if buff[1] == socks5AuthNoAccept { 129 | return nil, errors.New("socks: SOCKS server at: " + s.address + " no acceptable methods") 130 | } 131 | 132 | if buff[1] == socks5AuthPassword { 133 | // build username/password authentication request 134 | buff = buff[:0] 135 | buff = append(buff, socks5AuthPasswordVer) 136 | buff = append(buff, uint8(len(s.user))) 137 | buff = append(buff, []byte(s.user)...) 138 | buff = append(buff, uint8(len(s.password))) 139 | buff = append(buff, []byte(s.password)...) 140 | 141 | if _, err := conn.Write(buff); err != nil { 142 | return nil, errors.New("socks: failed to write password authentication request to SOCKS5 server at: " + s.address + ": " + err.Error()) 143 | } 144 | if _, err := io.ReadFull(conn, buff[:2]); err != nil { 145 | return nil, errors.New("socks: failed to read password authentication reply from SOCKS5 server at: " + s.address + ": " + err.Error()) 146 | } 147 | // 0 indicates success 148 | if buff[1] != 0 { 149 | return nil, errors.New("socks: SOCKS5 server at: " + s.address + " reject username/password") 150 | } 151 | } 152 | 153 | // build connect request 154 | buff = buff[:0] 155 | buff = append(buff, socks5Version, socks5Connect, 0) 156 | 157 | if ip := net.ParseIP(host); ip != nil { 158 | if ip4 := ip.To4(); ip4 != nil { 159 | buff = append(buff, socks5IP4) 160 | ip = ip4 161 | } else { 162 | buff = append(buff, socks5IP6) 163 | } 164 | buff = append(buff, ip...) 165 | } else { 166 | if len(host) > 255 { 167 | return nil, errors.New("socks: destination hostname too long: " + host) 168 | } 169 | buff = append(buff, socks5Domain) 170 | buff = append(buff, uint8(len(host))) 171 | buff = append(buff, host...) 172 | } 173 | buff = append(buff, byte(port>>8), byte(port)) 174 | 175 | if _, err := conn.Write(buff); err != nil { 176 | return nil, errors.New("socks: failed to write connect request to SOCKS5 server at: " + s.address + ": " + err.Error()) 177 | } 178 | if _, err := io.ReadFull(conn, buff[:4]); err != nil { 179 | return nil, errors.New("socks: failed to read connect reply from SOCKS5 server at: " + s.address + ": " + err.Error()) 180 | } 181 | 182 | failure := "unknown error" 183 | if int(buff[1]) < len(socks5Errors) { 184 | failure = socks5Errors[buff[1]] 185 | } 186 | if len(failure) > 0 { 187 | return nil, errors.New("socks: SOCKS5 server failed to connect: " + failure) 188 | } 189 | 190 | // read remain data include BIND.ADDRESS and BIND.PORT 191 | discardBytes := 0 192 | switch buff[3] { 193 | case socks5IP4: 194 | discardBytes = net.IPv4len 195 | case socks5IP6: 196 | discardBytes = net.IPv6len 197 | case socks5Domain: 198 | if _, err := io.ReadFull(conn, buff[:1]); err != nil { 199 | return nil, errors.New("socks: failed to read domain length from SOCKS5 server at: " + s.address + ": " + err.Error()) 200 | } 201 | discardBytes = int(buff[0]) 202 | default: 203 | return nil, errors.New("socks: got unknown address type " + strconv.Itoa(int(buff[3])) + " from SOCKS5 server at: " + s.address) 204 | } 205 | discardBytes += 2 206 | if cap(buff) < discardBytes { 207 | buff = make([]byte, discardBytes) 208 | } else { 209 | buff = buff[:discardBytes] 210 | } 211 | if _, err := io.ReadFull(conn, buff); err != nil { 212 | return nil, errors.New("socks: failed to read address and port from SOCKS5 server at: " + s.address + ": " + err.Error()) 213 | } 214 | 215 | closeConn = nil 216 | return conn, nil 217 | } 218 | 219 | func serveSocks5Client(conn net.Conn, forward Dialer) { 220 | defer conn.Close() 221 | 222 | buff := make([]byte, 262) 223 | reply := []byte{0x05, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22} 224 | 225 | if _, err := io.ReadFull(conn, buff[:2]); err != nil { 226 | return 227 | } 228 | if buff[0] != socks5Version { 229 | reply[1] = socks5AuthNoAccept 230 | conn.Write(reply[:2]) 231 | return 232 | } 233 | numMethod := buff[1] 234 | if _, err := io.ReadFull(conn, buff[:numMethod]); err != nil { 235 | return 236 | } 237 | reply[1] = socks5AuthNone 238 | if _, err := conn.Write(reply[:2]); err != nil { 239 | return 240 | } 241 | 242 | if _, err := io.ReadFull(conn, buff[:4]); err != nil { 243 | return 244 | } 245 | if buff[1] != socks5Connect { 246 | reply[1] = socks5CommandNotSupported 247 | conn.Write(reply) 248 | return 249 | } 250 | 251 | addressType := buff[3] 252 | addressLen := 0 253 | switch addressType { 254 | case socks5IP4: 255 | addressLen = net.IPv4len 256 | case socks5IP6: 257 | addressLen = net.IPv6len 258 | case socks5Domain: 259 | if _, err := io.ReadFull(conn, buff[:1]); err != nil { 260 | return 261 | } 262 | addressLen = int(buff[0]) 263 | default: 264 | reply[1] = socks5AddressTypeNotSupported 265 | conn.Write(reply) 266 | return 267 | } 268 | host := make([]byte, addressLen) 269 | if _, err := io.ReadFull(conn, host); err != nil { 270 | return 271 | } 272 | if _, err := io.ReadFull(conn, buff[:2]); err != nil { 273 | return 274 | } 275 | hostStr := "" 276 | switch addressType { 277 | case socks5IP4, socks5IP6: 278 | ip := net.IP(host) 279 | hostStr = ip.String() 280 | case socks5Domain: 281 | hostStr = string(host) 282 | } 283 | port := uint16(buff[0])<<8 | uint16(buff[1]) 284 | if port < 1 || port > 0xffff { 285 | reply[1] = socks5HostUnreachable 286 | conn.Write(reply) 287 | return 288 | } 289 | portStr := strconv.Itoa(int(port)) 290 | 291 | hostStr = net.JoinHostPort(hostStr, portStr) 292 | dest, err := forward.Dial("tcp", hostStr) 293 | if err != nil { 294 | reply[1] = socks5ConnectionRefused 295 | conn.Write(reply) 296 | return 297 | } 298 | defer dest.Close() 299 | reply[1] = socks5Success 300 | if _, err := conn.Write(reply); err != nil { 301 | return 302 | } 303 | 304 | go func() { 305 | defer conn.Close() 306 | defer dest.Close() 307 | io.Copy(conn, dest) 308 | }() 309 | 310 | io.Copy(dest, conn) 311 | } 312 | -------------------------------------------------------------------------------- /socks5_client_test.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "net/http/httputil" 7 | "testing" 8 | ) 9 | 10 | const ( 11 | TestSocks5ServerAddress = "127.0.0.1:8001" 12 | TestSocks5TargetDomain = "www.baidu.com" 13 | ) 14 | 15 | func TestSocks5Client(t *testing.T) { 16 | listener, err := net.Listen("tcp", TestSocks5ServerAddress) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | defer listener.Close() 21 | 22 | go func() { 23 | server, err := NewSocks5Server(Direct) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | server.Serve(listener) 28 | }() 29 | 30 | client, err := NewSocks5Client("tcp", TestSocks5ServerAddress, "", "", Direct) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | conn, err := client.Dial("tcp", TestSocks5TargetDomain+":80") 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | 39 | t.Log("client.Dial succeeded") 40 | httpClient := httputil.NewClientConn(conn, nil) 41 | if err != nil { 42 | conn.Close() 43 | t.Fatal(err) 44 | } 45 | defer httpClient.Close() 46 | 47 | request, err := http.NewRequest("GET", "/", nil) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | resp, err := httpClient.Do(request) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | data, err := httputil.DumpResponse(resp, true) 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | t.Log("socks5 HTTP Get:", string(data)) 60 | 61 | } 62 | -------------------------------------------------------------------------------- /socks5_server.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import "net" 4 | 5 | // Socks5Server implements Socks5 Proxy Protocol(RFC 1928), just support CONNECT command. 6 | type Socks5Server struct { 7 | forward Dialer 8 | } 9 | 10 | // NewSocks5Server return a new Socks5Server 11 | func NewSocks5Server(forward Dialer) (*Socks5Server, error) { 12 | return &Socks5Server{ 13 | forward: forward, 14 | }, nil 15 | } 16 | 17 | // Serve with net.Listener for new incoming clients. 18 | func (s *Socks5Server) Serve(listener net.Listener) error { 19 | for { 20 | conn, err := listener.Accept() 21 | if err != nil { 22 | if netErr, ok := err.(net.Error); ok && netErr.Temporary() { 23 | continue 24 | } else { 25 | return err 26 | } 27 | } 28 | 29 | go serveSocks5Client(conn, s.forward) 30 | } 31 | } 32 | --------------------------------------------------------------------------------