diff --git a/.gitignore b/.gitignore index 7f8405b..4656e97 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .git +target proxy-lite \ No newline at end of file diff --git a/README.md b/README.md index d6e7d53..33f124d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ -# proxy-lite +# infinite-noodle +The best damn network proxy in the universe. diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..7f4d5f8 --- /dev/null +++ b/build.sh @@ -0,0 +1,17 @@ +PROG="./target/infinite-noodle.net-proxy" +CODE="" + +echo "building for linux" +env GOOS=linux GOARCH=amd64 go build -o $PROG -buildvcs=false $CODE + +echo "building for mac arm64" +env GOOS=darwin GOARCH=arm64 go build -o $PROG.mac.arm64 -buildvcs=false $CODE + +echo "building for mac amd64" +env GOOS=darwin GOARCH=amd64 go build -o $PROG.mac.amd64 -buildvcs=false $CODE + +echo "building for win arm64" +env GOOS=windows GOARCH=arm64 go build -o $PROG.win.arm64 -buildvcs=false $CODE + +echo "building for win amd64" +env GOOS=windows GOARCH=amd64 go build -o $PROG.win.amd64 -buildvcs=false $CODE diff --git a/database.go b/database.go new file mode 100644 index 0000000..296395a --- /dev/null +++ b/database.go @@ -0,0 +1,86 @@ +package main + +import ( + "github.com/google/uuid" + "go.mills.io/bitcask/v2" + "log" +) + +type Database struct { + connection *bitcask.Bitcask + Handle *bitcask.Collection +} + +func NewDatabase(path string) *Database { + db, err := bitcask.Open(path) + if err != nil { + log.Fatal(err) + } + return &Database{ + connection: db, + Handle: db.Collection("noodles"), + } +} + +func (db *Database) MakeID() string { + id, err := uuid.NewUUID() + if err != nil { + log.Print(err) + return "" + } + return id.String() +} + +func (db *Database) GetAll() []Noodle { + var data []Noodle + err := db.Handle.List(&data) + if err != nil { + log.Print(err) + return nil + } + return data +} + +func (db *Database) GetAllGeneric() []interface{} { + var data []interface{} + err := db.Handle.List(&data) + if err != nil { + log.Print(err) + return nil + } + return data +} + +func (db *Database) Get(id string) Noodle { + var noodle Noodle + log.Printf("Looking up noodle key='%s'", id) + log.Printf("key='%s' exists=%b", id, db.Handle.Has(id)) + err := db.Handle.Get(id, &noodle) + if err != nil { + log.Print(err) + return Noodle{} + } + return noodle +} + +func (db *Database) Add(noodle Noodle) error { + err := db.Handle.Add(noodle.Id, noodle) + if err != nil { + log.Print(err) + return err + } + return nil +} + +func (db *Database) Delete(id string) error { + err := db.Handle.Delete(id) + if err != nil { + log.Print(err) + return err + } + return nil +} + +func (db *Database) Close() error { + return db.connection.Close() +} diff --git a/go.mod b/go.mod index 2dd804d..6d72315 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,21 @@ -module proxy-lite +module infinite-noodle go 1.19 -require inet.af/tcpproxy v0.0.0-20231102063150-2862066fc2a9 +require ( + go.mills.io/bitcask/v2 v2.1.3 + inet.af/tcpproxy v0.0.0-20231102063150-2862066fc2a9 +) + +require ( + github.com/abcum/lcp v0.0.0-20201209214815-7a3f3840be81 // indirect + github.com/gofrs/flock v0.8.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/go-immutable-radix/v2 v2.0.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/mattetti/filebuffer v1.0.1 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect + golang.org/x/sys v0.20.0 // indirect +) diff --git a/go.sum b/go.sum index 1f33d31..029898e 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,54 @@ +github.com/abcum/lcp v0.0.0-20201209214815-7a3f3840be81 h1:uHogIJ9bXH75ZYrXnVShHIyywFiUZ7OOabwd9Sfd8rw= +github.com/abcum/lcp v0.0.0-20201209214815-7a3f3840be81/go.mod h1:6ZvnjTZX1LNo1oLpfaJK8h+MXqHxcBFBIwkgsv+xlv0= github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/hashicorp/go-immutable-radix/v2 v2.0.0 h1:nq9lQ5I71Heg2lRb2/+szuIWKY3Y73d8YKyXyN91WzU= +github.com/hashicorp/go-immutable-radix/v2 v2.0.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= +github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= +github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +go.mills.io/bitcask/v2 v2.1.3 h1:xOe0sdkTHqgpMxsfDjcpaWOm8VtCmA5JzRy4dbicFfk= +go.mills.io/bitcask/v2 v2.1.3/go.mod h1:ZQFykoTTCvMwy24lBstZhSRQuleYIB4EzWKSOgEv6+k= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= inet.af/tcpproxy v0.0.0-20231102063150-2862066fc2a9 h1:zomTWJvjwLbKRgGameQtpK6DNFUbZ2oNJuWhgUkGp3M= inet.af/tcpproxy v0.0.0-20231102063150-2862066fc2a9/go.mod h1:Tojt5kmHpDIR2jMojxzZK2w2ZR7OILODmUo2gaSwjrk= diff --git a/main.go b/main.go index ee0419a..f04640d 100644 --- a/main.go +++ b/main.go @@ -2,47 +2,110 @@ package main import ( "embed" - "html/template" + "flag" + "fmt" "log" "net/http" + "time" "inet.af/tcpproxy" ) -//go:embed templates/*.html -var templatesFS embed.FS +//go:embed pub webfonts templates/*.html +var content embed.FS + +var dataFlag = flag.String("data", "./infinite.db", "Path to database.") +var hostFlag = flag.String("host", "0.0.0.0", "Host to listen on.") +var portFlag = flag.Int("port", 7878, "Port to listen on.") +var runTest = flag.Bool("test", false, "Run data test") + +var listenString string + +func init() { + flag.Parse() + listenString = fmt.Sprintf("%s:%d", *hostFlag, *portFlag) +} func main() { + //currentTime := time.Now() + noodleChannel := make(chan Noodle) - go proxify() + db := NewDatabase(*dataFlag) + defer db.Close() - // Parse templates from the embedded file system - tmpl, err := template.ParseFS(templatesFS, "templates/*.html") - if err != nil { - log.Fatalf("Error parsing templates: %v", err) + if *runTest { + runTestSequence(db) } - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - data := struct{ Name string }{Name: "Go Embed"} - err := tmpl.ExecuteTemplate(w, "index.html", data) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + go systemCheck(db, noodleChannel) + go tcpProxify(noodleChannel) + + http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(content)))) + // TODO: CRYPTO hrefs + http.HandleFunc("/", handleMain(db, &noodleChannel)) + http.HandleFunc("/delete", handleDelete(db, &noodleChannel)) + + log.Printf("Server starting on %s", listenString) + log.Fatal(http.ListenAndServe(listenString, nil)) + +} + +func systemCheck(db *Database, noodleChannel chan Noodle) { + for { + noodles := db.GetAll() + for _, noodle := range noodles { + noodleChannel <- noodle } - }) - - log.Println("Server starting on :8080") - log.Fatal(http.ListenAndServe(":8080", nil)) - + time.Sleep(60 * time.Second) + } } -func proxify() { - var p tcpproxy.Proxy - p.AddRoute("127.0.0.1:5555", tcpproxy.To("127.0.0.1:6666")) - //p.AddHTTPHostRoute(":80", "foo.com", tcpproxy.To("10.0.0.1:8081")) - //p.AddHTTPHostRoute(":80", "bar.com", tcpproxy.To("10.0.0.2:8082")) - //p.AddRoute(":80", tcpproxy.To("10.0.0.1:8081")) // fallback - //p.AddSNIRoute(":443", "foo.com", tcpproxy.To("10.0.0.1:4431")) - //p.AddSNIRoute(":443", "bar.com", tcpproxy.To("10.0.0.2:4432")) - //p.AddRoute(":443", tcpproxy.To("10.0.0.1:4431")) // fallback - log.Fatal(p.Run()) +func startProxy(proxy *tcpproxy.Proxy) { + log.Print(proxy.Run()) +} + +func tcpProxify(noodleChannel chan Noodle) { + noodleMap := make(map[string]*tcpproxy.Proxy) + for { + noodle := <-noodleChannel + _, running := noodleMap[noodle.Id] + if noodle.IsUp && !running { + var p tcpproxy.Proxy + src := fmt.Sprintf("0.0.0.0:%d", noodle.ListenPort) + dst := fmt.Sprintf("%s:%d", noodle.DestHost, noodle.DestPort) + log.Printf("Starting a noodle from %s to %s", src, dst) + p.AddRoute(src, tcpproxy.To(dst)) + //p.Start() + noodleMap[noodle.Id] = &p + go startProxy(&p) + continue + } + + if !noodle.IsUp && running { + log.Printf("Closing noodle=%v", noodle) + err := noodleMap[noodle.Id].Close() + if err != nil { + log.Print(err) + } + } + + //p.AddHTTPHostRoute(":80", "foo.com", tcpproxy.To("10.0.0.1:8081")) + //p.AddHTTPHostRoute(":80", "bar.com", tcpproxy.To("10.0.0.2:8082")) + //p.AddRoute(":80", tcpproxy.To("10.0.0.1:8081")) // fallback + //p.AddSNIRoute(":443", "foo.com", tcpproxy.To("10.0.0.1:4431")) + //p.AddSNIRoute(":443", "bar.com", tcpproxy.To("10.0.0.2:4432")) + //p.AddRoute(":443", tcpproxy.To("10.0.0.1:4431")) // fallback + //log.Fatal(p.Run()) + } +} + +func runTestSequence(db *Database) { + for i := 0; i < 21; i++ { + noodle := Noodle{db.MakeID(), "Name_Test", "Proto_Test", 1080+i, 22, "localhost", time.Now().Second(), true} + log.Printf("Test noodle=%v", noodle) + db.Add(noodle) + log.Printf("Test id=%s exists=%s", noodle.Id, db.Handle.Has(noodle.Id)) + log.Printf("Test GetAllGeneric=%v", db.GetAllGeneric()) + //db.Delete(noodle.Id) + } } diff --git a/model.go b/model.go new file mode 100644 index 0000000..e27006a --- /dev/null +++ b/model.go @@ -0,0 +1,12 @@ +package main + +type Noodle struct { + Id string + Name string + Proto string + ListenPort int + DestPort int + DestHost string + Expiration int + IsUp bool +} diff --git a/templates/index.html b/templates/index.html index 4a99070..c404139 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,12 +1,104 @@ - - + - Embedded Template + Infinite-Noodles + + + + + - -

Hello, {{.Name}}!

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + {{range .}} + + + + + + + + + + + {{end}} + +
NameProtoListening PortDest PortDest Host/IPExpirationStatus
TCPExpiration + + +
{{.Name}}{{.Proto}}{{.ListenPort}}{{.DestPort}}{{.DestHost}}{{.Expiration}} + {{if .IsUp}} + + {{ else }} + + {{ end }} + + + + +
+
+ \ No newline at end of file diff --git a/web.go b/web.go new file mode 100644 index 0000000..dd139bc --- /dev/null +++ b/web.go @@ -0,0 +1,52 @@ +package main + +import ( + "html/template" + "log" + "net/http" +) + +func handleMain(db *Database, pc *chan Noodle) func(w http.ResponseWriter, req *http.Request) { + tmpl, err := template.ParseFS(content, "templates/*.html") + if err != nil { + log.Fatalf("Error parsing templates: %v", err) + } + return func(w http.ResponseWriter, req *http.Request) { + data := db.GetAll() + err := tmpl.ExecuteTemplate(w, "index.html", data) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} + +func handleDelete(db *Database, pc *chan Noodle) func(w http.ResponseWriter, req *http.Request) { + return func(w http.ResponseWriter, req *http.Request) { + + q := req.URL.Query() + vals, ok := q["id"] + if ok { + id := vals[0] + noodle := db.Get(id) + noodle.IsUp = false + *pc <- noodle + log.Printf("Deleting noodle=%v", noodle) + //log.Printf("Deleting noodle=%s", id) + db.Delete(noodle.Id) + } + + http.Redirect(w, req, "/", 307) + } +} + +func handleAdd(db *Database, pc *chan Noodle) func(w http.ResponseWriter, req *http.Request) { + return func(w http.ResponseWriter, req *http.Request) { + + if req.Method != "POST" { + http.Error(w, "", http.StatusMethodNotAllowed) + return + } + + http.Redirect(w, req, "/", 307) + } +}