NetworkMonitor/main.go

290 lines
5.1 KiB
Go

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()
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()
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() 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
}
end[pt.Name] = pt
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
queue = append(queue, 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)
}
}
}
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
}