Skip to content

Instantly share code, notes, and snippets.

@sleeyax
Forked from movsb/tunnel.go
Created May 4, 2022 13:58
Show Gist options
  • Save sleeyax/9df8733bd07b2271b2f3c4ec4414c1b5 to your computer and use it in GitHub Desktop.
Save sleeyax/9df8733bd07b2271b2f3c4ec4414c1b5 to your computer and use it in GitHub Desktop.
An HTTP Tunnel Proxy, which implements the CONNECT method. Written in Golang, within 100 lines of code.
package main
import (
"io"
"log"
"net"
"net/http"
"sync"
)
var (
listen = "localhost:18080"
connectResponse = []byte("HTTP/1.1 200 OK\r\n\r\n")
username = "my_username"
password = "my_password"
)
func tunnel(w http.ResponseWriter, req *http.Request) {
// We handle CONNECT method only
if req.Method != http.MethodConnect {
log.Println(req.Method, req.RequestURI)
http.NotFound(w, req)
return
}
// Proxy-Authorization is set by client software.
// Authorization is used by req.BasicAuth().
req.Header.Set("Authorization", req.Header.Get("Proxy-Authorization"))
user, pass, ok := req.BasicAuth()
if !ok || !(user == username && pass == password) {
log.Println("bad credential.", "user:", user, "pass:", pass)
// Don't let them know we support CONNECT.
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
// The host:port pair.
log.Println(req.RequestURI)
// Connect to Remote.
dst, err := net.Dial("tcp", req.RequestURI)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
defer dst.Close()
// Upon success, we respond a 200 status code to client.
w.Write(connectResponse)
// Now, Hijack the writer to get the underlying net.Conn.
// Which can be either *tcp.Conn, for HTTP, or *tls.Conn, for HTTPS.
src, bio, err := w.(http.Hijacker).Hijack()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer src.Close()
wg := &sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
// The returned bufio.Reader may contain unprocessed buffered data from the client.
// Copy them to dst so we can use src directly.
if n := bio.Reader.Buffered(); n > 0 {
n64, err := io.CopyN(dst, bio, int64(n))
if n64 != int64(n) || err != nil {
log.Println("io.CopyN:", n64, err)
return
}
}
// Relay: src -> dst
io.Copy(dst, src)
}()
go func() {
defer wg.Done()
// Relay: dst -> src
io.Copy(src, dst)
}()
wg.Wait()
}
func main() {
handler := http.HandlerFunc(tunnel)
err := http.ListenAndServe(listen, handler)
if err != http.ErrServerClosed {
panic(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment