delete works and with test

This commit is contained in:
2025-07-30 20:29:24 -04:00
parent 379eb6e206
commit e92644cf72
10 changed files with 427 additions and 36 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
.git
target
proxy-lite

View File

@ -1,2 +1,3 @@
# proxy-lite
# infinite-noodle
The best damn network proxy in the universe.

17
build.sh Executable file
View File

@ -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

86
database.go Normal file
View File

@ -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()
}

20
go.mod
View File

@ -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
)

51
go.sum
View File

@ -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=

119
main.go
View File

@ -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)
}
}

12
model.go Normal file
View File

@ -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
}

View File

@ -1,12 +1,104 @@
<!DOCTYPE html>
<html>
<html data-theme="dark">
<head>
<title>Embedded Template</title>
<title>Infinite-Noodles</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/static/pub/bulma.min.css">
<link rel="stylesheet" href="/static/pub/all.min.css">
<style>
.banner-container {
background-image: url("/static/pub/long-banner.jpg");
background-repeat: no-repeat;
background-size: cover;
position: relative;
width: 100%;
}
img.banner {
height: 50px;
}
img.banner-long {
height: 50px;
object-fit: fill;
}
table {
width: 100%;
}
</style>
</head>
<body>
<h1>Hello, {{.Name}}!</h1>
<body class="has-navbar-fixed-top">
<nav class="navbar is-fixed-top" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<img class="banner" src="/static/pub/logo.jpg"/>
</div>
<div class="banner-container">
<!-- <img class="banner-long" src="/static/pub/long-banner.jpg"/> -->
</div>
</nav>
<div class="table-container">
<table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth">
<thead>
<tr>
<th>Name</th>
<th>Proto</th>
<th>Listening Port</th>
<th>Dest Port</th>
<th>Dest Host/IP</th>
<th>Expiration</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><input class="input is-link is-small" type="text" placeholder="Name"/></td>
<td>TCP</td>
<td><input class="input is-link is-small" type="text" placeholder="Listen Port"/></td>
<td><input class="input is-link is-small" type="text" placeholder="Destination Port"/></td>
<td><input class="input is-link is-small" type="text" placeholder="Destination Host"/></td>
<td>Expiration</td>
<td>
</td>
<td>
<button class="button">
<span class="icon has-text-success"><i class="fas fa-plus"></i></span>
</button>
</td>
</tr>
{{range .}}
<tr>
<td>{{.Name}}</td>
<td>{{.Proto}}</td>
<td>{{.ListenPort}}</td>
<td>{{.DestPort}}</td>
<td>{{.DestHost}}</td>
<td>{{.Expiration}}</td>
<td>
{{if .IsUp}}
<span class="icon has-text-success"><i class="fas fa-check-square"></i></span>
{{ else }}
<span class="icon has-text-danger"><i class="fas fa-ban"></i></span>
{{ end }}
</td>
<td>
<a href="/delete?id={{.Id}}">
<span class="icon has-text-danger"><i class="fas fa-minus"></i></span>
</a>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
<footer class="footer">
<div class="content has-text-centered">
<p>
<strong>Infinite-Noodles Network Proxy</strong> - &copy; 2025 Jimmy Allen
</p>
</div>
</footer>
</body>
</html>

52
web.go Normal file
View File

@ -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)
}
}