diff --git a/src/conf.json.sample b/src/conf.json.sample new file mode 100644 index 0000000..0e0e664 --- /dev/null +++ b/src/conf.json.sample @@ -0,0 +1,20 @@ +{ + "Interfaces": [ + { + "DisplayName": "MainInterface", + "IpOrInterfaceName": "enp2s0" + }, + { + "DisplayName": "SecondInterface", + "IpOrInterfaceName": "enp3s0" + } + ], + "WaitTimeSec": 15, + "PingAddr": "8.8.8.8", + + "TelegramConf": { + "BotToken": "Token", + "ChatID": "Id", + "SendSilent": "false" + } +} \ No newline at end of file diff --git a/src/go.mod b/src/go.mod new file mode 100644 index 0000000..7459f8a --- /dev/null +++ b/src/go.mod @@ -0,0 +1,11 @@ +module multiwan_informer + +go 1.18 + +require ( + github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 // indirect + github.com/google/uuid v1.3.0 // indirect + golang.org/x/net v0.0.0-20220420153159-1850ba15e1be // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect +) diff --git a/src/go.sum b/src/go.sum new file mode 100644 index 0000000..33f2109 --- /dev/null +++ b/src/go.sum @@ -0,0 +1,17 @@ +github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 h1:dhy9OQKGBh4zVXbjwbxxHjRxMJtLXj3zfgpBYQaR4Q4= +github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20220420153159-1850ba15e1be h1:yx80W7nvY5ySWpaU8UWaj5o9e23YgO9BRhQol7Lc+JI= +golang.org/x/net v0.0.0-20220420153159-1850ba15e1be/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/src/main.go b/src/main.go new file mode 100644 index 0000000..81361c0 --- /dev/null +++ b/src/main.go @@ -0,0 +1,198 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net" + "net/http" + "net/url" + "os" + "os/exec" + "strings" + "time" +) + +func messFormat(iface Interface, successful bool) string { + if successful { + return fmt.Sprintf("āœ… Inteface *%s* now is *up*!", iface.DisplayName) + } else { + return fmt.Sprintf("āŒ Inteface *%s* now is *down!*", iface.DisplayName) + } +} + +func main() { + + config := loadConfig() + + validateConfig(config) + + pingable := []PingData{} + + data, dataOk := loadData() + + if !dataOk { + sendMessage("šŸ“ *Data file is not readable*", config.TelegramConf) + } + + for iface_indx, iface := range config.Interfaces { + var ping_param string = "-S " // default ping ip + if net.ParseIP(iface.IpOrInterfaceName) == nil { + ping_param = "-I " + } // ping interface + + pingResult := ping("-w " + fmt.Sprint(config.WaitTimeSec) + " " + ping_param + iface.IpOrInterfaceName + " " + config.PingAddr) + + pingable = append(pingable, PingData{iface.IpOrInterfaceName, pingResult, time.Now()}) + + if dataOk { + getIndex := func() int { + for i, element := range data { + if element.Name == iface.IpOrInterfaceName { + return (i) + } + } + return (-1) + } + + idx := getIndex() + + if idx == -1 { // send message if interface not exist in data + sendMessage(messFormat(iface, pingResult), config.TelegramConf) + } else if data[idx].Successful != pingResult { // send messages if the result is different from what is in the data + if pingResult { + deltaTime := pingable[iface_indx].LastEdit.Sub(data[idx].LastEdit) + sendMessage( + messFormat(iface, pingResult)+ + fmt.Sprintf("\nā³ Down time: *%d* days *%d* hours *%d* min *%d* sec", int(deltaTime.Hours())/24, int(deltaTime.Hours())%24, int(deltaTime.Minutes())%60, int(deltaTime.Seconds())%60%60), + config.TelegramConf) + } else { + sendMessage( + messFormat(iface, pingResult), + config.TelegramConf) + } + } + } else { // send message if data not loaded + sendMessage(messFormat(iface, pingResult), config.TelegramConf) + } + + } + + filse, _ := json.Marshal(pingable) + + _ = ioutil.WriteFile("data.json", filse, 0644) + +} + +func ping(params string) bool { + Command := fmt.Sprintf("ping -c 1 %s > /dev/null && echo true || echo false", params) + output, err := exec.Command("/bin/sh", "-c", Command).Output() + if err != nil { + fmt.Println(err) + return false + } + real_ip := strings.TrimSpace(string(output)) + if real_ip == "false" { + return false + } else { + return true + } +} + +func sendMessage(mess string, tg_conf Telegram) bool { + resp, err := http.Get("https://api.telegram.org/bot" + tg_conf.BotToken + "/sendMessage?parse_mode=Markdown&chat_id=" + tg_conf.ChatID + "&disable_notification=" + tg_conf.SendSilent + "&text=" + url.QueryEscape(mess)) + if err != nil { + log.Fatalln(err) + return false + } + + if resp.StatusCode == 200 { + return true + } + + return false +} + +func loadData() ([]PingData, bool) { + file, _ := os.Open("data.json") + defer file.Close() + decoder := json.NewDecoder(file) + data := []PingData{} + err := decoder.Decode(&data) + if err != nil { + log.Println("Data file error:", err) + return []PingData{}, false + } + + return data, true +} + +func loadConfig() Configuration { + file, _ := os.Open("conf.json") + defer file.Close() + decoder := json.NewDecoder(file) + configuration := Configuration{} + err := decoder.Decode(&configuration) + if err != nil { + log.Fatalln("error:", err) + } + + return configuration +} + +func validateConfig(conf Configuration) { + if !(len(conf.Interfaces) > 0) { + log.Fatalln("You haven't added interfaces") + } + + if conf.PingAddr == "" || !(conf.WaitTimeSec > 0) { + log.Fatalln("Bad config") + } + + // Telegram + resp, err := http.Get("https://api.telegram.org/bot" + conf.TelegramConf.BotToken + "/getMe") + if err != nil { + log.Fatalln(err) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Fatalln(err) + } + + var result map[string]interface{} + json.Unmarshal([]byte(body), &result) + + if resp.StatusCode != 200 || fmt.Sprintf("%v", result["ok"]) != "true" { + log.Fatalln("Failed to connect to bot") + } +} + +// config structs +type Configuration struct { + Interfaces []Interface + WaitTimeSec int + PingAddr string + + TelegramConf Telegram +} + +type Interface struct { + DisplayName string + IpOrInterfaceName string +} + +type Telegram struct { + BotToken string + ChatID string + SendSilent string +} + +// data struct + +type PingData struct { + Name string + Successful bool + LastEdit time.Time +}