package main import ( "bufio" "bytes" _ "embed" "encoding/json" "fmt" "log" "net" "net/http" "os" "os/signal" "strings" "sync" "syscall" "text/template" "time" ) //go:embed index.html var tmpl string var report = template.Must(template.New("").Parse(tmpl)) var write_report chan bool var interval = time.Second * 2 var wr sync.Mutex type Network struct { Name string IP string OnFail string } var Networks = []*Network{ { Name: "DSL", IP: "220.135.85.67", OnFail: "nmcli connection up DSL", }, { Name: "Asus", IP: "192.168.50.48", }, } func main() { write_report = make(chan bool, 1) file, err := os.OpenFile("network.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.ModePerm) if err != nil { log.Panicln(err) } defer file.Close() log.Println("starting network monitor") go func() { perr := false derr := false for reducer := 0; ; reducer++ { now := time.Now().Format("2006-01-02 15:04:05") // Public go func(print bool) { _, err = net.LookupIP("google.com") if err != nil { if !perr { perr = true log.Println(err) } wr.Lock() fmt.Fprintf(file, `{"x": "%s", "y": "%s", "name": "%s"},`, now, "failed", "Public") fmt.Fprintf(file, "\n") wr.Unlock() } else { perr = false if print { wr.Lock() fmt.Fprintf(file, `{"x": "%s", "y": "%s", "name": "%s"},`, now, "ok", "Public") fmt.Fprintf(file, "\n") wr.Unlock() } } }(reducer == 0) // devices go func(print bool) { for i := range Networks { _, err = GetOutboundIP(Networks[i].IP + ":0") if err != nil { if !derr { derr = true log.Println(err) } wr.Lock() fmt.Fprintf(file, `{"x": "%s", "y": "%s", "name": "%s"},`, now, "failed", Networks[i].Name) fmt.Fprintf(file, "\n") wr.Unlock() // fmt.Fprintf(file, "%s\t%s\t%s\n", now, Networks[i].Name, "failed") } else { derr = false if print { wr.Lock() fmt.Fprintf(file, `{"x": "%s", "y": "%s", "name": "%s"},`, now, "ok", Networks[i].Name) fmt.Fprintf(file, "\n") wr.Unlock() } } } }(reducer == 0) <-time.After(interval) if reducer >= 9 { reducer = -1 } } }() go func() { for { <-time.After(time.Minute * 5) CleanLog(false) WriteReport() } }() go func() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, strings.TrimPrefix(r.URL.Path, "/")) }) port, ok := os.LookupEnv("PORT") if !ok { port = "1080" } if err := http.ListenAndServe("0.0.0.0:"+port, nil); err != nil { log.Fatalln(err) } }() wait := make(chan os.Signal, 1) signal.Notify(wait, syscall.SIGINT, syscall.SIGTERM) <-wait CleanLog(true) WriteReport() } func WriteReport() { select { case write_report <- true: default: return } defer func() { <-write_report }() f, err := os.OpenFile("report.html", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm) if err != nil { log.Println(err) return } defer f.Close() wr.Lock() data, err := os.ReadFile("network.log") wr.Unlock() if err != nil { log.Println(err) return } content := &bytes.Buffer{} if err = report.Execute(content, map[string]string{"Data": string(data)}); err != nil { log.Println(err) return } content.WriteTo(f) } type point struct { Time string `json:"x"` Stat string `json:"y"` Name string `json:"name"` } func (p point) GetTime() time.Time { t, _ := time.Parse("2006-01-02 15:04:05", p.Time) return t } func CleanLog(down bool) error { file, err := os.OpenFile("network.log", os.O_RDONLY|os.O_CREATE, os.ModePerm) if err != nil { log.Println(err) return err } last := map[string]*point{} end := map[string]*point{} queue := []*point{} scanner := bufio.NewScanner(file) for scanner.Scan() { pt := &point{} data := scanner.Bytes() err = json.Unmarshal(data[:len(data)-1], pt) if err != nil { log.Println(err) file.Close() return err } last_pt, ok := last[pt.Name] if !ok { last[pt.Name] = pt queue = append(queue, pt) } else { if last_pt.Stat != pt.Stat { last[pt.Name] = pt if pre_last, ok := end[pt.Name]; ok { queue = append(queue, pre_last) } queue = append(queue, pt) } } end[pt.Name] = pt } if err := scanner.Err(); err != nil { log.Println(err) return err } file.Close() for k, v := range end { if pt, ok := last[k]; ok { if pt.Time != v.Time { queue = append(queue, v) } } } // if down { // now := time.Now().Format("2006-01-02 15:04:05") // for pt := range end { // queue = append(queue, &point{ // Time: now, // Stat: "down", // Name: pt, // }) // } // } wr.Lock() defer wr.Unlock() file, err = os.OpenFile("network.log", os.O_WRONLY|os.O_TRUNC, os.ModePerm) if err != nil { log.Println(err) return err } defer file.Close() for i := range queue { b, err := json.Marshal(queue[i]) if err != nil { log.Println(err) return err } file.Write(b) file.Write([]byte{',', '\n'}) } return nil } // Get preferred outbound ip of this machine func GetOutboundIP(addr string) (net.IP, error) { iface, err := net.ResolveUDPAddr("udp", addr) if err != nil { return nil, err } dialer := &net.Dialer{LocalAddr: iface} conn, err := dialer.Dial("udp", "8.8.8.8:80") if err != nil { return nil, err } defer conn.Close() localAddr := conn.LocalAddr().(*net.UDPAddr) return localAddr.IP, nil }