├── .github └── FUNDING.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── constants.go ├── go.mod ├── main.go ├── servers_response.go ├── servers_response_linux.go └── socks.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | ko_fi: extremecoders 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | # Go dispatch proxy 2 | 3 | A SOCKS5 load balancing proxy to combine multiple internet connections into one. Works on Windows and Linux. [Reported to work on macOS](https://github.com/extremecoders-re/go-dispatch-proxy/issues/1). Written in pure Go with no additional dependencies. 4 | 5 | It can also be used as a transparent proxy to load balance multiple SSH tunnels. 6 | 7 | ## Rationale 8 | 9 | The idea for this project came from [dispatch-proxy](https://github.com/Morhaus/dispatch-proxy) which is written in NodeJS. 10 | [NodeJS is not entirely disk friendly considering the multitude of files it creates even for very simple programs](https://medium.com/@jdan/i-peeked-into-my-node-modules-directory-and-you-wont-believe-what-happened-next-b89f63d21558). I needed something light & portable, preferably a single binary without polluting the entire drive. 11 | 12 | ## Installation 13 | 14 | No installation required. Grab the latest binary for your platform from the [CI server](https://ci.appveyor.com/project/extremecoders-re/go-dispatch-proxy/build/artifacts) or from [releases](https://github.com/extremecoders-re/go-dispatch-proxy/releases) and start speeding up your internet connection! 15 | 16 | [![Build status](https://ci.appveyor.com/api/projects/status/nll4hvpdjlfsp7mu?svg=true)](https://ci.appveyor.com/project/extremecoders-re/go-dispatch-proxy/build/artifacts) 17 | 18 | ## Usage 19 | 20 | The example below are shown on Windows. The steps are similar for other platforms. 21 | 22 | ### 1 - Load balance connections 23 | 24 | The primary purpose of the tool is to combine multiple internet connections into one. For this we need to know the IP addresses of the interface we wish to combine. You can obtain the IP addresses using the `ipconfig` (`ip a` on linux) command. Alternatively run `go-dispatch-proxy -list`. 25 | 26 | ``` 27 | D:\>go-dispatch-proxy.exe -list 28 | --- Listing the available adresses for dispatching 29 | [+] Mobile Broadband Connection , IPv4:10.81.201.18 30 | [+] Local Area Connection, IPv4:192.168.1.2 31 | ``` 32 | 33 | Start `go-dispatch-proxy` specifying the IP addresses of the load balancers obtained in the previous step. Optionally, along with the IP address you may also provide the contention ratio(after the @ symbol). If no contention ratio is specified, it's assumed as 1. 34 | 35 | ### 2 - Load balance SSH tunnels 36 | 37 | The tool can load balance multiple SSH tunnels. See Example 3 for usage. 38 | 39 | ### Example 1 40 | 41 | SOCKS proxy running on localhost at default port. Contention ratio is specified. 42 | ``` 43 | D:\>go-dispatch-proxy.exe 10.81.201.18@3 192.168.1.2@2 44 | [INFO] Load balancer 1: 10.81.201.18, contention ratio: 3 45 | [INFO] Load balancer 2: 192.168.1.2, contention ratio: 2 46 | [INFO] SOCKS server started at 127.0.0.1:8080 47 | ``` 48 | 49 | ### Example 2 50 | 51 | SOCKS proxy running on a different interface at a custom port. Contention ratio is not specified. 52 | 53 | ``` 54 | D:\>go-dispatch-proxy.exe -lhost 192.168.1.2 -lport 5566 10.81.177.215 192.168.1.100 55 | [INFO] Load balancer 1: 10.81.177.215, contention ratio: 1 56 | [INFO] Load balancer 2: 192.168.1.100, contention ratio: 1 57 | [INFO] SOCKS server started at 192.168.1.2:5566 58 | ``` 59 | 60 | Out of 5 consecutive connections, the first 3 are routed to `10.81.201.18` and the remaining 2 to `192.168.1.2`. The SOCKS server is started by default on `127.0.0.1:8080`. It can be changed using the `-lhost` and `-lport` directive. 61 | 62 | Now change the proxy settings of your browser, download manager etc to point to the above address (eg `127.0.0.1:8080`). Be sure to add this as a SOCKS v5 proxy and NOT as a HTTP/S proxy. 63 | 64 | ### Example 3 65 | 66 | The tool can be used to load balance multiple SSH tunnels. In this mode, go-dispatch-proxy acts as a transparent load balancing proxy. 67 | 68 | First, setup the tunnels. 69 | 70 | ``` 71 | D:\> ssh -D 127.0.0.1:7777 user@192.168.1.100 72 | D:\> ssh -D 127.0.0.1:7778 user@192.168.1.101 73 | ``` 74 | 75 | Here we are setting up two SSH tunnels to remote hosts `192.168.1.100`, and `192.168.1.101` on local ports `7777` and `7778` respectively. The IP address (`127.0.0.1`) if omitted defaults to localhost. The `-D` option stands for dynamic port forwarding. 76 | 77 | Next, launch go-dispatch-proxy using the `-tunnel` argument. 78 | 79 | ``` 80 | D:\> go-dispatch-proxy.exe -tunnel 127.0.0.1:7777 127.0.0.1:7778 81 | ``` 82 | 83 | Both the IP and port must be mentioned while specifying the load balancer addresses. Also instead of specifying the IP address a domain can be specified, hence the following also works. 84 | 85 | ``` 86 | D:\> go-dispatch-proxy.exe -tunnel proxy1.com:7777 proxy2.com:7778 87 | ``` 88 | 89 | Optionally, the listening host, port and contention ratio can also be specified like in example 2. 90 | 91 | ``` 92 | D:\> go-dispatch-proxy.exe -lport 5555 -tunnel 127.0.0.1:7777@1 127.0.0.1:7778@3 93 | ``` 94 | 95 | The `lport` if not specified defaults to 8080. This is the port where you need to point your web browser, download manager etc. Be sure to add this as a SOCKS v5 proxy. 96 | 97 | ## Full Linux Support [NEW] 98 | 99 | Go-dispatch-proxy now supports Linux in both normal mode and tunnel mode. On Linux normal mode, Go-dispatch-proxy uses the `SO_BINDTODEVICE` syscall to bind to the interface corresponding to the load balancer IPs. As a result, the binary must be run with `root` privilege or by giving it the necessary capabilities as shown below. 100 | 101 | ``` 102 | $ sudo ./go-dispatch-proxy 103 | ``` 104 | 105 | OR (Recommended) 106 | 107 | ``` 108 | $ sudo setcap cap_net_raw=eip ./go-dispatch-proxy 109 | $ ./go-dispatch-proxy 110 | ``` 111 | 112 | Tunnel mode doesn't require root privilege. 113 | 114 | ## Compiling (For Development) 115 | 116 | Ensure that Go is installed and available on the system path. 117 | 118 | ```sh 119 | $ git clone https://github.com/extremecoders-re/go-dispatch-proxy.git 120 | $ cd go-dispatch-proxy 121 | 122 | # Compile for Windows x86 123 | $ GOOS=windows GOARCH=386 go build 124 | 125 | # Compile for Windows x64 126 | $ GOOS=windows GOARCH=amd64 go build 127 | 128 | # Compile for Linux x86 129 | $ GOOS=linux GOARCH=386 go build 130 | 131 | # Compile for Linux x64 132 | $ GOOS=linux GOARCH=amd64 go build 133 | 134 | # Compile for Macos x64 135 | $ GOOS=darwin GOARCH=amd64 go build 136 | ``` 137 | 138 | ## Credits 139 | 140 | - [dispatch-proxy](https://github.com/Morhaus/dispatch-proxy): A SOCKS5/HTTP load balancing proxy written in NodeJS. 141 | 142 | ## License 143 | 144 | Licensed under MIT 145 | 146 | ## Community 147 | 148 | - **Go Dispatch Proxy GUI**: A simple gui for go-dispatch-proxy (https://github.com/gulp79/go-dispatch-proxy-gui) 149 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: 2 | - Visual Studio 2019 3 | 4 | version: 'v{build}' 5 | 6 | pull_requests: 7 | do_not_increment_build_number: true 8 | 9 | clone_folder: c:\go-dispatch-proxy 10 | shallow_clone: true 11 | clone_depth: 1 12 | 13 | test: off 14 | 15 | # Install upx using chocolatey 16 | # install: 17 | # - cinst upx 18 | 19 | # Build for Windows, Linux & macOS (x86, x64, arm64) 20 | build_script: 21 | - SET GOOS=windows 22 | - SET GOARCH=386 23 | - go build -x -ldflags "-s -w" -o go-dispatch-proxy.win.x86.exe 24 | 25 | - SET GOARCH=amd64 26 | - go build -x -ldflags "-s -w" -o go-dispatch-proxy.win.x64.exe 27 | 28 | - SET GOOS=linux 29 | - SET GOARCH=386 30 | - go build -x -ldflags "-s -w" -o go-dispatch-proxy.linux.x86 31 | 32 | - SET GOARCH=amd64 33 | - go build -x -ldflags "-s -w" -o go-dispatch-proxy.linux.x64 34 | 35 | - SET GOOS=darwin 36 | - SET GOARCH=amd64 37 | - go build -x -ldflags "-s -w" -o go-dispatch-proxy.macos.x64 38 | 39 | - SET GOOS=darwin 40 | - SET GOARCH=arm64 41 | - go build -x -ldflags "-s -w" -o go-dispatch-proxy.macos.arm64 42 | 43 | after_build: 44 | # - upx -9 -o go-dispatch-proxy.exe go-dispatch-proxy.win.x86.exe 45 | - ren go-dispatch-proxy.win.x86.exe go-dispatch-proxy.exe 46 | - 7z a go-dispatch-proxy.win.x86.7z C:\go-dispatch-proxy\go-dispatch-proxy.exe 47 | - del /q go-dispatch-proxy.exe 48 | 49 | # - upx -9 -o go-dispatch-proxy.exe go-dispatch-proxy.win.x64.exe 50 | - ren go-dispatch-proxy.win.x64.exe go-dispatch-proxy.exe 51 | - 7z a go-dispatch-proxy.win.x64.7z C:\go-dispatch-proxy\go-dispatch-proxy.exe 52 | - del /q go-dispatch-proxy.exe 53 | 54 | # - upx -9 -o go-dispatch-proxy go-dispatch-proxy.linux.x86 55 | - ren go-dispatch-proxy.linux.x86 go-dispatch-proxy 56 | - 7z a go-dispatch-proxy.linux.x86.7z C:\go-dispatch-proxy\go-dispatch-proxy 57 | - del /q go-dispatch-proxy 58 | 59 | # - upx -9 -o go-dispatch-proxy go-dispatch-proxy.linux.x64 60 | - ren go-dispatch-proxy.linux.x64 go-dispatch-proxy 61 | - 7z a go-dispatch-proxy.linux.x64.7z C:\go-dispatch-proxy\go-dispatch-proxy 62 | - del /q go-dispatch-proxy 63 | 64 | - ren go-dispatch-proxy.macos.x64 go-dispatch-proxy 65 | - 7z a go-dispatch-proxy.macos.x64.7z C:\go-dispatch-proxy\go-dispatch-proxy 66 | - del /q go-dispatch-proxy 67 | 68 | - ren go-dispatch-proxy.macos.arm64 go-dispatch-proxy 69 | - 7z a go-dispatch-proxy.macos.arm64.7z C:\go-dispatch-proxy\go-dispatch-proxy 70 | - del /q go-dispatch-proxy 71 | 72 | # Downloadables 73 | artifacts: 74 | - path: go-dispatch-proxy.win.x86.7z 75 | - path: go-dispatch-proxy.win.x64.7z 76 | - path: go-dispatch-proxy.linux.x86.7z 77 | - path: go-dispatch-proxy.linux.x64.7z 78 | - path: go-dispatch-proxy.macos.x64.7z 79 | - path: go-dispatch-proxy.macos.arm64.7z 80 | -------------------------------------------------------------------------------- /constants.go: -------------------------------------------------------------------------------- 1 | // const.go 2 | package main 3 | 4 | // AUTH_METHOD 5 | const ( 6 | NOAUTH = iota 7 | GSSAPI = iota 8 | USERNAME_PASSWORD = iota 9 | NO_ACCEPTABLE_METHOD = 0xFF 10 | ) 11 | 12 | // COMMAND 13 | const ( 14 | CONNECT = 1 + iota 15 | BIND = 1 + iota 16 | UDP_ASSOCIATE = 1 + iota 17 | ) 18 | 19 | // ADDRTYPE 20 | const ( 21 | IPV4 = 1 + iota 22 | DOMAIN = 2 + iota 23 | IPV6 = 2 + iota 24 | ) 25 | 26 | // REQUEST_STATUS 27 | const ( 28 | SUCCESS = iota 29 | SERVER_FAILURE = iota 30 | CONNECTION_NOT_ALLOWED = iota 31 | NETWORK_UNREACHABLE = iota 32 | HOST_UNREACHABLE = iota 33 | CONNECTION_REFUSED = iota 34 | TTL_EXPIRED = iota 35 | COMMAND_NOT_SUPPORTED = iota 36 | ADDRTYPE_NOT_SUPPORTED = iota 37 | ) 38 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module go-dispatch-proxy 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // main.go 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net" 10 | "strconv" 11 | "strings" 12 | "sync" 13 | "math/big" 14 | ) 15 | 16 | type load_balancer struct { 17 | address string 18 | iface string 19 | contention_ratio int 20 | current_connections int 21 | } 22 | 23 | // The load balancer used in the previous connection 24 | var lb_index int = 0 25 | 26 | // List of all load balancers 27 | var lb_list []load_balancer 28 | 29 | // Mutex to serialize access to function get_load_balancer 30 | var mutex *sync.Mutex 31 | 32 | /* 33 | Get a load balancer according to contention ratio 34 | */ 35 | func get_load_balancer(params ...interface{}) (*load_balancer, int) { 36 | var _bitset *big.Int 37 | if len(params) > 0 { 38 | seed := -1 39 | for _, p := range params { 40 | switch v := p.(type) { 41 | case int: 42 | seed = v 43 | case *big.Int: 44 | _bitset = v 45 | } 46 | } 47 | if seed < 0 || seed >= len(lb_list) || _bitset == nil { 48 | seed = -1 49 | _bitset = nil 50 | } 51 | log.Println("[DEBUG] Try to get different load balancer of", seed) 52 | } 53 | 54 | mutex.Lock() 55 | if _bitset != nil { 56 | for { 57 | if _bitset.Bit(lb_index) != 0 { 58 | lb := &lb_list[lb_index] 59 | lb.current_connections = 0 60 | lb_index += 1 61 | 62 | if lb_index == len(lb_list) { 63 | lb_index = 0 64 | } 65 | } else { 66 | break 67 | } 68 | } 69 | } 70 | 71 | lb := &lb_list[lb_index] 72 | lb.current_connections += 1 73 | ilb := lb_index 74 | 75 | if lb.current_connections == lb.contention_ratio { 76 | lb.current_connections = 0 77 | lb_index += 1 78 | 79 | if lb_index == len(lb_list) { 80 | lb_index = 0 81 | } 82 | } 83 | mutex.Unlock() 84 | return lb,ilb 85 | } 86 | 87 | /* 88 | Joins the local and remote connections together 89 | */ 90 | func pipe_connections(local_conn, remote_conn net.Conn) { 91 | go func() { 92 | defer remote_conn.Close() 93 | defer local_conn.Close() 94 | _, err := io.Copy(remote_conn, local_conn) 95 | if err != nil { 96 | return 97 | } 98 | }() 99 | 100 | go func() { 101 | defer remote_conn.Close() 102 | defer local_conn.Close() 103 | _, err := io.Copy(local_conn, remote_conn) 104 | if err != nil { 105 | return 106 | } 107 | }() 108 | } 109 | 110 | /* 111 | Handle connections in tunnel mode 112 | */ 113 | func handle_tunnel_connection(conn net.Conn) { 114 | load_balancer, i := get_load_balancer() 115 | var _bitset *big.Int 116 | complete := 1 == len(lb_list) 117 | 118 | retry: 119 | remote_addr, _ := net.ResolveTCPAddr("tcp4", load_balancer.address) 120 | remote_conn, err := net.DialTCP("tcp4", nil, remote_addr) 121 | 122 | if err != nil { 123 | log.Println("[WARN]", load_balancer.address, fmt.Sprintf("{%s}", err), "LB:", i) 124 | 125 | if !complete && _bitset == nil { 126 | bits := make([]byte, (len(lb_list)+7)/8) 127 | _bitset = new(big.Int).SetBytes(bits) 128 | } 129 | 130 | if !complete { 131 | _bitset.SetBit(_bitset, i, 1) 132 | 133 | // Check if all balancers are used 134 | mask := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), uint(len(lb_list))), big.NewInt(1)) 135 | complete = new(big.Int).And(_bitset, mask).Cmp(mask) == 0 136 | } 137 | 138 | if !complete { 139 | load_balancer, i = get_load_balancer(i, _bitset) 140 | goto retry 141 | } 142 | 143 | log.Println("[WARN]", "all load balancers failed") 144 | conn.Close() 145 | return 146 | } 147 | 148 | log.Println("[DEBUG] Tunnelled to", load_balancer.address, "LB:", i) 149 | pipe_connections(conn, remote_conn) 150 | } 151 | 152 | /* 153 | Calls the apprpriate handle_connections based on tunnel mode 154 | */ 155 | func handle_connection(conn net.Conn, tunnel bool) { 156 | if tunnel { 157 | handle_tunnel_connection(conn) 158 | } else if address, err := handle_socks_connection(conn); err == nil { 159 | server_response(conn, address) 160 | } 161 | } 162 | 163 | /* 164 | Detect the addresses which can be used for dispatching in non-tunnelling mode. 165 | Alternate to ipconfig/ifconfig 166 | */ 167 | func detect_interfaces() { 168 | fmt.Println("--- Listing the available adresses for dispatching") 169 | ifaces, _ := net.Interfaces() 170 | 171 | for _, iface := range ifaces { 172 | if (iface.Flags&net.FlagUp == net.FlagUp) && (iface.Flags&net.FlagLoopback != net.FlagLoopback) { 173 | addrs, _ := iface.Addrs() 174 | for _, addr := range addrs { 175 | if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 176 | if ipnet.IP.To4() != nil { 177 | fmt.Printf("[+] %s, IPv4:%s\n", iface.Name, ipnet.IP.String()) 178 | } 179 | } 180 | } 181 | } 182 | } 183 | 184 | } 185 | 186 | /* 187 | Gets the interface associated with the IP 188 | */ 189 | func get_iface_from_ip(ip string) string { 190 | ifaces, _ := net.Interfaces() 191 | 192 | for _, iface := range ifaces { 193 | if (iface.Flags&net.FlagUp == net.FlagUp) && (iface.Flags&net.FlagLoopback != net.FlagLoopback) { 194 | addrs, _ := iface.Addrs() 195 | for _, addr := range addrs { 196 | if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 197 | if ipnet.IP.To4() != nil { 198 | if ipnet.IP.String() == ip { 199 | return iface.Name + "\x00" 200 | } 201 | } 202 | } 203 | } 204 | } 205 | } 206 | return "" 207 | } 208 | 209 | /* 210 | Parses the command line arguements to obtain the list of load balancers 211 | */ 212 | func parse_load_balancers(args []string, tunnel bool) { 213 | if len(args) == 0 { 214 | log.Fatal("[FATAL] Please specify one or more load balancers") 215 | } 216 | 217 | lb_list = make([]load_balancer, flag.NArg()) 218 | 219 | for idx, a := range args { 220 | splitted := strings.Split(a, "@") 221 | iface := "" 222 | // IP address of a Fully Qualified Domain Name of the load balancer 223 | var lb_ip_or_fqdn string 224 | var lb_port int 225 | var err error 226 | 227 | if tunnel { 228 | ip_or_fqdn_port := strings.Split(splitted[0], ":") 229 | if len(ip_or_fqdn_port) != 2 { 230 | log.Fatal("[FATAL] Invalid address specification ", splitted[0]) 231 | return 232 | } 233 | 234 | lb_ip_or_fqdn = ip_or_fqdn_port[0] 235 | lb_port, err = strconv.Atoi(ip_or_fqdn_port[1]) 236 | if err != nil || lb_port <= 0 || lb_port > 65535 { 237 | log.Fatal("[FATAL] Invalid port ", splitted[0]) 238 | return 239 | } 240 | 241 | } else { 242 | lb_ip_or_fqdn = splitted[0] 243 | lb_port = 0 244 | } 245 | 246 | // FQDN not supported for tunnel modes 247 | if !tunnel && net.ParseIP(lb_ip_or_fqdn).To4() == nil { 248 | log.Fatal("[FATAL] Invalid address ", lb_ip_or_fqdn) 249 | } 250 | 251 | var cont_ratio int = 1 252 | if len(splitted) > 1 { 253 | cont_ratio, err = strconv.Atoi(splitted[1]) 254 | if err != nil || cont_ratio <= 0 { 255 | log.Fatal("[FATAL] Invalid contention ratio for ", lb_ip_or_fqdn) 256 | } 257 | } 258 | 259 | // Obtaining the interface name of the load balancer IP's doesn't make sense in tunnel mode 260 | if !tunnel { 261 | iface = get_iface_from_ip(lb_ip_or_fqdn) 262 | if iface == "" { 263 | log.Fatal("[FATAL] IP address not associated with an interface ", lb_ip_or_fqdn) 264 | } 265 | } 266 | 267 | slbport := "" 268 | if tunnel { 269 | slbport = ":" + strconv.Itoa(lb_port) 270 | } 271 | 272 | log.Printf("[INFO] Load balancer %d: %s%s, contention ratio: %d\n", idx+1, lb_ip_or_fqdn, slbport, cont_ratio) 273 | lb_list[idx] = load_balancer{address: fmt.Sprintf("%s:%d", lb_ip_or_fqdn, lb_port), iface: iface, contention_ratio: cont_ratio, current_connections: 0} 274 | } 275 | } 276 | 277 | /* 278 | Main function 279 | */ 280 | func main() { 281 | var lhost = flag.String("lhost", "127.0.0.1", "The host to listen for SOCKS connection") 282 | var lport = flag.Int("lport", 8080, "The local port to listen for SOCKS connection") 283 | var detect = flag.Bool("list", false, "Shows the available addresses for dispatching (non-tunnelling mode only)") 284 | var tunnel = flag.Bool("tunnel", false, "Use tunnelling mode (acts as a transparent load balancing proxy)") 285 | var quiet = flag.Bool("quiet", false, "disable logs") 286 | 287 | flag.Parse() 288 | if *detect { 289 | detect_interfaces() 290 | return 291 | } 292 | 293 | // Disable timestamp in log messages 294 | log.SetFlags(log.Flags() &^ (log.Ldate | log.Ltime)) 295 | 296 | // Check for valid IP 297 | if net.ParseIP(*lhost).To4() == nil { 298 | log.Fatal("[FATAL] Invalid host ", *lhost) 299 | } 300 | 301 | // Check for valid port 302 | if *lport < 1 || *lport > 65535 { 303 | log.Fatal("[FATAL] Invalid port ", *lport) 304 | } 305 | 306 | //Parse remaining string to get addresses of load balancers 307 | parse_load_balancers(flag.Args(), *tunnel) 308 | 309 | local_bind_address := fmt.Sprintf("%s:%d", *lhost, *lport) 310 | 311 | // Start local server 312 | l, err := net.Listen("tcp4", local_bind_address) 313 | if err != nil { 314 | log.Fatalln("[FATAL] Could not start local server on ", local_bind_address) 315 | } 316 | log.Println("[INFO] Local server started on ", local_bind_address) 317 | defer l.Close() 318 | 319 | if (*quiet) { 320 | log.SetOutput(io.Discard) 321 | } 322 | 323 | mutex = &sync.Mutex{} 324 | for { 325 | conn, err := l.Accept() 326 | if err != nil { 327 | log.Println("[WARN] Could not accept connection") 328 | } else { 329 | go handle_connection(conn, *tunnel) 330 | } 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /servers_response.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | // servers_response.go 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "log" 9 | "net" 10 | ) 11 | 12 | /* 13 | Implements servers response of SOCKS5 for non Linux systems 14 | */ 15 | func server_response(local_conn net.Conn, remote_address string) { 16 | load_balancer, i := get_load_balancer() 17 | 18 | local_tcpaddr, _ := net.ResolveTCPAddr("tcp4", load_balancer.address) 19 | remote_tcpaddr, _ := net.ResolveTCPAddr("tcp4", remote_address) 20 | remote_conn, err := net.DialTCP("tcp4", local_tcpaddr, remote_tcpaddr) 21 | 22 | if err != nil { 23 | log.Println("[WARN]", remote_address, "->", load_balancer.address, fmt.Sprintf("{%s}", err), "LB:", i) 24 | local_conn.Write([]byte{5, NETWORK_UNREACHABLE, 0, 1, 0, 0, 0, 0, 0, 0}) 25 | local_conn.Close() 26 | return 27 | } 28 | log.Println("[DEBUG]", remote_address, "->", load_balancer.address, "LB:", i) 29 | local_conn.Write([]byte{5, SUCCESS, 0, 1, 0, 0, 0, 0, 0, 0}) 30 | pipe_connections(local_conn, remote_conn) 31 | } 32 | -------------------------------------------------------------------------------- /servers_response_linux.go: -------------------------------------------------------------------------------- 1 | // servers_response_linux.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "log" 7 | "net" 8 | "syscall" 9 | ) 10 | 11 | /* 12 | Implements servers response of SOCKS5 for linux systems 13 | */ 14 | func server_response(local_conn net.Conn, remote_address string) { 15 | load_balancer, i := get_load_balancer() 16 | local_tcpaddr, _ := net.ResolveTCPAddr("tcp4", load_balancer.address) 17 | 18 | dialer := net.Dialer{ 19 | LocalAddr: local_tcpaddr, 20 | Control: func(network, address string, c syscall.RawConn) error { 21 | return c.Control(func(fd uintptr) { 22 | // NOTE: Run with root or use setcap to allow interface binding 23 | // sudo setcap cap_net_raw=eip ./go-dispatch-proxy 24 | if err := syscall.BindToDevice(int(fd), load_balancer.iface); err != nil { 25 | log.Println("[WARN] Couldn't bind to interface", load_balancer.iface, "LB:", i) 26 | } 27 | }) 28 | }, 29 | } 30 | 31 | remote_conn, err := dialer.Dial("tcp4", remote_address) 32 | if err != nil { 33 | log.Println("[WARN]", remote_address, "->", load_balancer.address, fmt.Sprintf("{%s}", err), "LB:", i) 34 | local_conn.Write([]byte{5, NETWORK_UNREACHABLE, 0, 1, 0, 0, 0, 0, 0, 0}) 35 | local_conn.Close() 36 | return 37 | } 38 | 39 | log.Println("[DEBUG]", remote_address, "->", load_balancer.address, "LB:", i) 40 | local_conn.Write([]byte{5, SUCCESS, 0, 1, 0, 0, 0, 0, 0, 0}) 41 | pipe_connections(local_conn, remote_conn) 42 | } 43 | -------------------------------------------------------------------------------- /socks.go: -------------------------------------------------------------------------------- 1 | // socks.go 2 | package main 3 | 4 | import ( 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "log" 9 | "net" 10 | ) 11 | 12 | /* 13 | 14 | */ 15 | func client_greeting(conn net.Conn) (byte, []byte, error) { 16 | buf := make([]byte, 2) 17 | 18 | if nRead, err := conn.Read(buf); err != nil || nRead != len(buf) { 19 | return 0, nil, errors.New("[WARN] client greeting failed") 20 | } 21 | 22 | socks_version := buf[0] 23 | num_auth_methods := buf[1] 24 | 25 | auth_methods := make([]byte, num_auth_methods) 26 | 27 | if nRead, err := conn.Read(auth_methods); err != nil || nRead != int(num_auth_methods) { 28 | return 0, nil, errors.New("[WARN] client greeting failed") 29 | } 30 | 31 | return socks_version, auth_methods, nil 32 | } 33 | 34 | /* 35 | 36 | */ 37 | func servers_choice(conn net.Conn) error { 38 | 39 | if nWrite, err := conn.Write([]byte{5, 0}); err != nil || nWrite != 2 { 40 | return errors.New("[WARN] servers choice failed") 41 | } 42 | return nil 43 | } 44 | 45 | /* 46 | 47 | */ 48 | func client_conection_request(conn net.Conn) (string, error) { 49 | header := make([]byte, 4) 50 | port := make([]byte, 2) 51 | var address string 52 | 53 | if nRead, err := conn.Read(header); err != nil || nRead != len(header) { 54 | conn.Write([]byte{5, SERVER_FAILURE, 0, 1, 0, 0, 0, 0, 0, 0}) 55 | conn.Close() 56 | return "", errors.New("[WARN] client connection request failed") 57 | } 58 | 59 | socks_version := header[0] 60 | cmd_code := header[1] 61 | // reserved := header[2] 62 | address_type := header[3] 63 | 64 | if socks_version != 5 { 65 | conn.Write([]byte{5, SERVER_FAILURE, 0, 1, 0, 0, 0, 0, 0, 0}) 66 | conn.Close() 67 | return "", errors.New("[WARN] unsupported SOCKS version") 68 | } 69 | 70 | if cmd_code != CONNECT { 71 | conn.Write([]byte{5, COMMAND_NOT_SUPPORTED, 0, 1, 0, 0, 0, 0, 0, 0}) 72 | conn.Close() 73 | return "", errors.New("[WARN] unsupported command code") 74 | } 75 | 76 | switch address_type { 77 | case IPV4: 78 | ipv4_address := make([]byte, 4) 79 | 80 | if nRead, err := conn.Read(ipv4_address); err != nil || nRead != len(ipv4_address) { 81 | conn.Write([]byte{5, SERVER_FAILURE, 0, 1, 0, 0, 0, 0, 0, 0}) 82 | conn.Close() 83 | return "", errors.New("[WARN] client connection request failed") 84 | } 85 | 86 | if nRead, err := conn.Read(port); err != nil || nRead != len(port) { 87 | conn.Write([]byte{5, SERVER_FAILURE, 0, 1, 0, 0, 0, 0, 0, 0}) 88 | conn.Close() 89 | return "", errors.New("[WARN] client connection request failed") 90 | } 91 | address = fmt.Sprintf("%d.%d.%d.%d:%d", ipv4_address[0], 92 | ipv4_address[1], 93 | ipv4_address[2], 94 | ipv4_address[3], 95 | binary.BigEndian.Uint16(port)) 96 | 97 | case DOMAIN: 98 | domain_name_length := make([]byte, 1) 99 | 100 | if nRead, err := conn.Read(domain_name_length); err != nil || nRead != len(domain_name_length) { 101 | conn.Write([]byte{5, SERVER_FAILURE, 0, 1, 0, 0, 0, 0, 0, 0}) 102 | conn.Close() 103 | return "", errors.New("[WARN] client connection request failed") 104 | } 105 | 106 | domain_name := make([]byte, domain_name_length[0]) 107 | 108 | if nRead, err := conn.Read(domain_name); err != nil || nRead != len(domain_name) { 109 | conn.Write([]byte{5, SERVER_FAILURE, 0, 1, 0, 0, 0, 0, 0, 0}) 110 | conn.Close() 111 | return "", errors.New("[WARN] client connection request failed") 112 | } 113 | 114 | if nRead, err := conn.Read(port); err != nil || nRead != len(port) { 115 | conn.Write([]byte{5, SERVER_FAILURE, 0, 1, 0, 0, 0, 0, 0, 0}) 116 | conn.Close() 117 | return "", errors.New("[WARN] client connection request failed") 118 | } 119 | address = fmt.Sprintf("%s:%d", string(domain_name), binary.BigEndian.Uint16(port)) 120 | 121 | default: 122 | conn.Write([]byte{5, ADDRTYPE_NOT_SUPPORTED, 0, 1, 0, 0, 0, 0, 0, 0}) 123 | conn.Close() 124 | return "", errors.New("[WARN] unsupported address type") 125 | } 126 | return address, nil 127 | } 128 | 129 | /* 130 | 131 | */ 132 | func handle_socks_connection(conn net.Conn) (string, error) { 133 | 134 | if _, _, err := client_greeting(conn); err != nil { 135 | log.Println(err) 136 | return "", err 137 | } 138 | 139 | if err := servers_choice(conn); err != nil { 140 | log.Println(err) 141 | return "", err 142 | } 143 | 144 | address, err := client_conection_request(conn) 145 | if err != nil { 146 | log.Println(err) 147 | return "", err 148 | } 149 | return address, nil 150 | } 151 | --------------------------------------------------------------------------------