├── .gitignore ├── LICENSE ├── README.md └── proxy.go /.gitignore: -------------------------------------------------------------------------------- 1 | proxy.exe 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2013 Dominic Charley-Roy 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go TCP Proxy 2 | This simple application is a TCP proxy server written in golang. 3 | 4 | By default the server proxies from localhost:80 to localhost:8000 and has a maximum number of active connections (connections connected to the hidden server) of 25. 5 | 6 | ## Usage Examples 7 | - Forwarding all requests from localhost:80 (default) to localhost:3000 8 | 9 | ``` 10 | ./proxy -to localhost:3000 11 | ``` 12 | 13 | - Forwarding all requests from localhost:10000 to somedomain.com:80 14 | 15 | ``` 16 | ./proxy -from localhost:10000 -to somedomain.com:80 17 | ``` 18 | 19 | - Changing the number of maximum active connections to 300 20 | 21 | ``` 22 | ./proxy -c 300 23 | ``` -------------------------------------------------------------------------------- /proxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "net" 8 | "time" 9 | ) 10 | 11 | var fromHost = flag.String("from", "localhost:80", "The proxy server's host.") 12 | var toHost = flag.String("to", "localhost:8000", "The host that the proxy " + 13 | " server should forward requests to.") 14 | var maxConnections = flag.Int("c", 25, "The maximum number of active " + 15 | "connection at any given time.") 16 | var maxWaitingConnections = flag.Int("cw", 10000, "The maximum number of " + 17 | "connections that can be waiting to be served.") 18 | 19 | func main() { 20 | // Parse the command-line arguments. 21 | flag.Parse() 22 | fmt.Printf("Proxying %s->%s.\r\n", *fromHost, *toHost) 23 | 24 | // Set up our listening server 25 | server, err := net.Listen("tcp", *fromHost) 26 | 27 | // If any error occurs while setting up our listening server, error out. 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | // The channel of connections which are waiting to be processed. 33 | waiting := make(chan net.Conn, *maxWaitingConnections) 34 | // The booleans representing the free active connection spaces. 35 | spaces := make(chan bool, *maxConnections) 36 | // Initialize the spaces 37 | for i := 0; i < *maxConnections; i++ { 38 | spaces <- true 39 | } 40 | 41 | // Start the connection matcher. 42 | go matchConnections(waiting, spaces) 43 | 44 | // Loop indefinitely, accepting connections and handling them. 45 | for { 46 | connection, err := server.Accept() 47 | if err != nil { 48 | // Log the error. 49 | log.Print(err) 50 | } else { 51 | // Create a goroutine to handle the conn 52 | log.Printf("Received connection from %s.\r\n", 53 | connection.RemoteAddr()) 54 | waiting <- connection 55 | } 56 | } 57 | } 58 | 59 | func matchConnections(waiting chan net.Conn, spaces chan bool) { 60 | // Iterate over each connection in the waiting channel 61 | for connection := range waiting { 62 | // Block until we have a space. 63 | <-spaces 64 | // Create a new goroutine which will call the connection handler and 65 | // then free up the space. 66 | go func(connection net.Conn) { 67 | handleConnection(connection) 68 | spaces <- true 69 | log.Printf("Closed connection from %s.\r\n", connection.RemoteAddr()) 70 | }(connection) 71 | 72 | } 73 | } 74 | 75 | func handleConnection(connection net.Conn) { 76 | // Always close our connection. 77 | defer connection.Close() 78 | 79 | // Try to connect to remote server. 80 | remote, err := net.Dial("tcp", *toHost) 81 | if err != nil { 82 | // Exit out when an error occurs 83 | log.Print(err) 84 | return 85 | } 86 | defer remote.Close() 87 | 88 | // Create our channel which waits for completion, and our two channels to 89 | // signal that a goroutine is done. 90 | complete := make(chan bool, 2) 91 | ch1 := make(chan bool, 1) 92 | ch2 := make(chan bool, 1) 93 | go copyContent(connection, remote, complete, ch1, ch2) 94 | go copyContent(remote, connection, complete, ch2, ch1) 95 | // Block until we've completed both goroutines! 96 | <- complete 97 | <- complete 98 | } 99 | 100 | func copyContent(from net.Conn, to net.Conn, complete chan bool, done chan bool, otherDone chan bool) { 101 | var err error = nil 102 | var bytes []byte = make([]byte, 256) 103 | var read int = 0 104 | for { 105 | select { 106 | // If we received a done message from the other goroutine, we exit. 107 | case <- otherDone: 108 | complete <- true 109 | return 110 | default: 111 | 112 | // Read data from the source connection. 113 | from.SetReadDeadline(time.Now().Add(time.Second * 5)) 114 | read, err = from.Read(bytes) 115 | // If any errors occured, write to complete as we are done (one of the 116 | // connections closed.) 117 | if err != nil { 118 | complete <- true 119 | done <- true 120 | return 121 | } 122 | // Write data to the destination. 123 | to.SetWriteDeadline(time.Now().Add(time.Second * 5)) 124 | _, err = to.Write(bytes[:read]) 125 | // Same error checking. 126 | if err != nil { 127 | complete <- true 128 | done <- true 129 | return 130 | } 131 | } 132 | } 133 | } 134 | --------------------------------------------------------------------------------