From f730bdbc74ffc622e89bc83870ce0270005e3619 Mon Sep 17 00:00:00 2001 From: Pior Biernat Date: Tue, 16 May 2023 16:28:49 +0200 Subject: [PATCH] Initial commit with basic version of Ngkrok --- src/.env.dist | 1 + src/.gitignore | 18 ++++++ src/cmd/client/main.go | 67 +++++++++++++++++++ src/cmd/server/main.go | 39 +++++++++++ src/go.mod | 13 ++++ src/go.sum | 21 ++++++ src/internal/ngkrok/server.go | 118 ++++++++++++++++++++++++++++++++++ 7 files changed, 277 insertions(+) create mode 100644 src/.env.dist create mode 100644 src/.gitignore create mode 100644 src/cmd/client/main.go create mode 100644 src/cmd/server/main.go create mode 100644 src/go.mod create mode 100644 src/go.sum create mode 100644 src/internal/ngkrok/server.go diff --git a/src/.env.dist b/src/.env.dist new file mode 100644 index 0000000..f024d89 --- /dev/null +++ b/src/.env.dist @@ -0,0 +1 @@ +SERVER_PORT=:4040 diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..f8d7cca --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,18 @@ +# ---> Go +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +vendor/ + +.env diff --git a/src/cmd/client/main.go b/src/cmd/client/main.go new file mode 100644 index 0000000..d7b29f1 --- /dev/null +++ b/src/cmd/client/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "io" + "log" + "net" + "net/http" + "net/http/httputil" + + "github.com/progrium/qmux/golang/session" +) + +const ( + CLIENT_VERSION = "v0.2" + HEADER_CLIENT_TOKEN = "X-Client-App-Token" + HEADER_USER_AGENT = "User-Agent" +) + +func main() { + flag.Parse() + + srvPort, srvHost, clientToken := "18181", "hop.pbiernat.dev", "ghd7H8H*&6tg8*tg68" + targetPort := flag.Arg(0) + + if targetPort == "" { + log.Fatal("Missing port!") + } + + conn, err := net.Dial("tcp", net.JoinHostPort(srvHost, srvPort)) + fatal("main net.Dial", err) + client := httputil.NewClientConn(conn, bufio.NewReader(conn)) + req, err := http.NewRequest("GET", "/register", nil) + fatal("http.NewRequest", err) + req.Host = net.JoinHostPort(srvHost, srvPort) + req.Header.Add(HEADER_CLIENT_TOKEN, clientToken) + req.Header.Add(HEADER_USER_AGENT, "HopClient/"+CLIENT_VERSION) + client.Write(req) + resp, _ := client.Read(req) + fmt.Printf("http port %s available at:\n", targetPort) + fmt.Printf("https://%s\n", resp.Header.Get("X-Public-Host")) + c, _ := client.Hijack() + sess := session.New(c) + defer sess.Close() + for { + ch, err := sess.Accept() + fatal("Accept", err) + conn, err := net.Dial("tcp", "127.0.0.1:"+targetPort) + fatal("net.Dial", err) + go join(conn, ch) + } +} + +func join(a io.ReadWriteCloser, b io.ReadWriteCloser) { + go io.Copy(b, a) + io.Copy(a, b) + a.Close() + b.Close() +} + +func fatal(title string, err error) { + if err != nil { + log.Fatal(title+": ", err) + } +} diff --git a/src/cmd/server/main.go b/src/cmd/server/main.go new file mode 100644 index 0000000..59f55e6 --- /dev/null +++ b/src/cmd/server/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "log" + "net" + "time" + + "git.pbiernat.dev/golang/ngkrok/internal/ngkrok" + vhost "github.com/inconshreveable/go-vhost" +) + +func main() { + addr, host, port := "::", "hop.pbiernat.dev", "18181" + + lst, err := net.Listen("tcp", net.JoinHostPort(addr, port)) + if err != nil { + log.Fatal(err) + } + + vmux, err := vhost.NewHTTPMuxer(lst, 3*time.Second) + if err != nil { + log.Fatal(err) + } + defer vmux.Close() + + srv := ngkrok.NewServer(addr, host, port, vmux) + go srv.Serve() + + log.Printf("NgKrok server [%s] ready!\n", host) + + for { + conn, err := vmux.NextError() + fmt.Println("vmux error:", err) + if conn != nil { + conn.Close() + } + } +} diff --git a/src/go.mod b/src/go.mod new file mode 100644 index 0000000..5a71cfc --- /dev/null +++ b/src/go.mod @@ -0,0 +1,13 @@ +module git.pbiernat.dev/golang/ngkrok + +go 1.19 + +require ( + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/inconshreveable/go-vhost v1.0.0 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/klauspost/compress v1.16.5 // indirect + github.com/progrium/qmux/golang v0.0.0-20210721211401-475935a675d8 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.46.0 // indirect +) diff --git a/src/go.sum b/src/go.sum new file mode 100644 index 0000000..2575f15 --- /dev/null +++ b/src/go.sum @@ -0,0 +1,21 @@ +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/inconshreveable/go-vhost v1.0.0 h1:IK4VZTlXL4l9vz2IZoiSFbYaaqUW7dXJAiPriUN5Ur8= +github.com/inconshreveable/go-vhost v1.0.0/go.mod h1:aA6DnFhALT3zH0y+A39we+zbrdMC2N0X/q21e6FI0LU= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= +github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= +github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/progrium/qmux/golang v0.0.0-20210721211401-475935a675d8 h1:hLnTL/51vGU9IB4yFFExFSQgRlqFh0GHIJxCcOh7LT0= +github.com/progrium/qmux/golang v0.0.0-20210721211401-475935a675d8/go.mod h1:Z2EPtydgPrcZxO50GhkzTGgdWjA5PPHsZkoq6KTxPVE= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.46.0 h1:6ZRhrFg8zBXTRYY6vdzbFhqsBd7FVv123pV2m9V87U4= +github.com/valyala/fasthttp v1.46.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= +golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/src/internal/ngkrok/server.go b/src/internal/ngkrok/server.go new file mode 100644 index 0000000..172f9fb --- /dev/null +++ b/src/internal/ngkrok/server.go @@ -0,0 +1,118 @@ +package ngkrok + +import ( + "context" + "crypto/rand" + "io" + "log" + "net" + "net/http" + "strings" + + vhost "github.com/inconshreveable/go-vhost" + "github.com/progrium/qmux/golang/session" +) + +const ( + SERVER_VERSION = "v0.5" + HEADER_CLIENT_TOKEN = "X-Client-App-Token" + HEADER_PUBLIC_HOST = "X-Public-Host" +) + +type Server struct { + addr string + host string + port string + VMux *vhost.HTTPMuxer +} + +func NewServer(addr, host, port string, vmux *vhost.HTTPMuxer) *Server { + return &Server{addr, host, port, vmux} +} + +func (s *Server) Serve() { + ml, err := s.VMux.Listen(net.JoinHostPort(s.host, s.port)) + if err != nil { + log.Fatal(err) + } + + srv := &http.Server{} + // http.HandleFunc("/", s.mainHandler) + http.HandleFunc("/register", s.registerHandler) + + srv.Serve(ml) +} + +// func (s *Server) mainHandler(w http.ResponseWriter, r *http.Request) { +// log.Println("mainHandler") +// return +// } + +func (s *Server) registerHandler(w http.ResponseWriter, r *http.Request) { + subdomain := newSubdomain(r) + s.host + publicHost := strings.TrimSuffix(net.JoinHostPort(subdomain, s.port), ":80") + pl, err := s.VMux.Listen(publicHost) + if err != nil { + log.Fatal(err) + } + defer pl.Close() + + w.Header().Add(HEADER_PUBLIC_HOST, subdomain) + w.Header().Add("Connection", "close") + w.WriteHeader(http.StatusOK) + + conn, _, _ := w.(http.Hijacker).Hijack() + sess := session.New(conn) + defer sess.Close() + log.Printf("%s: start session", subdomain) + + go func() { + for { + conn, err := pl.Accept() + if err != nil { + log.Println("Accept:", err) + return + } + defer conn.Close() + + ch, err := sess.Open(context.Background()) + if err != nil { + log.Println("sess.Open:", err) + return + } + defer ch.Close() + + go join(ch, conn) + } + }() + + sess.Wait() + log.Printf("%s: end session", subdomain) +} + +func join(a io.ReadWriteCloser, b io.ReadWriteCloser) { + go io.Copy(b, a) + io.Copy(a, b) + a.Close() + b.Close() +} + +func newSubdomain(r *http.Request) string { + clientToken := r.Header.Get(HEADER_CLIENT_TOKEN) + + if clientToken == "ghd7H8H*&6tg8*tg68" { // hardcoded token for dev tests.... + return "test." + } + + b := make([]byte, 10) + if _, err := rand.Read(b); err != nil { + panic(err) + } + letters := []rune("abcdefghijklmnopqrstuvwxyz1234567890") + rune := make([]rune, 10) + for i := range rune { + rune[i] = letters[int(b[i])*len(letters)/256] + } + + return string(rune) + "." +}