package app import ( "fmt" "log" "net" "net/http" "time" "infinite-noodle/internal/noodle" "infinite-noodle/internal/web" "inet.af/tcpproxy" ) type Config struct { DataPath string Host string Port int RunTest bool } func Run(cfg Config) error { listenAddr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) noodleChannel := make(chan noodle.Noodle) db := noodle.NewDatabase(cfg.DataPath) defer db.Close() if cfg.RunTest { runTestSequence(db) } go systemCheck(db, noodleChannel) go expirationCheck(db, noodleChannel) go tcpProxify(noodleChannel) http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(web.StaticFiles())))) http.HandleFunc("/", web.HandleMain(db, &noodleChannel)) http.HandleFunc("/add", web.HandleAdd(db, &noodleChannel)) http.HandleFunc("/toggle", web.HandleToggle(db, &noodleChannel)) http.HandleFunc("/delete", web.HandleDelete(db, &noodleChannel)) log.Printf("Server starting on %s", listenAddr) return http.ListenAndServe(listenAddr, nil) } func systemCheck(db *noodle.Database, noodleChannel chan noodle.Noodle) { for { noodles := db.GetAll() for _, item := range noodles { noodleChannel <- item } time.Sleep(60 * time.Second) } } func startProxy(proxy *tcpproxy.Proxy) { log.Print(proxy.Run()) } func tcpProxify(noodleChannel chan noodle.Noodle) { noodleMap := make(map[string]*tcpproxy.Proxy) for { item := <-noodleChannel _, running := noodleMap[item.Id] if item.IsUp && !running { var proxy tcpproxy.Proxy src := fmt.Sprintf("0.0.0.0:%d", item.ListenPort) dst := fmt.Sprintf("%s:%d", item.DestHost, item.DestPort) log.Printf("Starting a noodle from %s to %s with source=%s", src, dst, item.Src) proxy.AddRoute(src, sourceRestrictedTarget{ allowedIP: item.Src, target: tcpproxy.To(dst), }) noodleMap[item.Id] = &proxy go startProxy(&proxy) continue } if !item.IsUp && running { log.Printf("Closing noodle=%v", item) if err := noodleMap[item.Id].Close(); err != nil { log.Print(err) } delete(noodleMap, item.Id) } } } type sourceRestrictedTarget struct { allowedIP string target tcpproxy.Target } func (t sourceRestrictedTarget) HandleConn(conn net.Conn) { if t.allowedIP == "" || t.allowedIP == "All" { t.target.HandleConn(conn) return } host, _, err := net.SplitHostPort(conn.RemoteAddr().String()) if err != nil { log.Printf("Rejected noodle connection with invalid remote address %q", conn.RemoteAddr().String()) conn.Close() return } if host != t.allowedIP { log.Printf("Rejected noodle connection from %s; allowed source is %s", host, t.allowedIP) conn.Close() return } t.target.HandleConn(conn) } func expirationCheck(db *noodle.Database, noodleChannel chan noodle.Noodle) { ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() for range ticker.C { noodles := db.GetAll() for _, item := range noodles { if !item.IsUp { continue } if item.Expiration <= 0 { if item.IsUp { item.IsUp = false noodleChannel <- item } if err := db.Delete(item.Id); err != nil { log.Print(err) } continue } item.Expiration -= time.Second if item.Expiration <= 0 { item.Expiration = 0 if err := db.Update(item); err != nil { log.Print(err) continue } if item.IsUp { item.IsUp = false noodleChannel <- item } if err := db.Delete(item.Id); err != nil { log.Print(err) } continue } if err := db.Update(item); err != nil { log.Print(err) } } } } func runTestSequence(db *noodle.Database) { for i := 0; i < 21; i++ { item := noodle.Noodle{ Id: db.MakeID(), Name: "Name_Test", Proto: "Proto_Test", Src: "All", ListenPort: 1080 + i, DestPort: 22, DestHost: "localhost", Expiration: time.Duration(time.Now().Second()) * time.Second, IsUp: true, } log.Printf("Test noodle=%v", item) db.Add(item) log.Printf("Test id=%s exists=%t", item.Id, db.Handle.Has(item.Id)) log.Printf("Test GetAllGeneric=%v", db.GetAllGeneric()) } }