godyn/client/client.go

179 lines
5.7 KiB
Go

package client
import (
"errors"
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"velvettear/godyn/config"
"velvettear/godyn/log"
"velvettear/godyn/notifications"
)
var wanIp string
var ipFile string
// exported function(s)
func Run() {
timestamp := time.Now().UnixMilli()
log.Debug("starting godyn in client mode...")
if !config.HasValidDyndnsConfig() {
log.Fatal("encountered an error starting godyn in client mode", "missing parameters for the dyndns provider")
}
interval := strconv.Itoa(config.Interval())
notifications.Send(notifications.Notification{
Title: "godyn started",
Message: "interval: " + interval + " seconds\n" +
"ip-provider: " + config.IpProviderUrl(),
Emoji: "white_circle",
})
readIpFile()
for config.Interval() > 0 {
log.Info("waiting " + interval + " seconds before next remote/wan ip query and dyndns update...")
time.Sleep(time.Duration(config.Interval()) * time.Second)
updateDyndns()
}
log.DebugTimed("client mode finished", timestamp)
}
// unexported function(s)
func readIpFile() {
ipFile = filepath.Join(os.TempDir(), ".ipfile")
log.Debug("using file '" + ipFile + "' to store ip address")
timestamp := time.Now().UnixMilli()
bytes, error := os.ReadFile(ipFile)
if error != nil {
if errors.Is(error, os.ErrNotExist) {
return
}
log.ErrorTimed("encountered an error reading from the '.ipfile'", timestamp, "location: '"+ipFile+"'", error.Error())
}
wanIp = string(bytes)
log.DebugTimed("read latest remote/wan ip from '.ipfile'", timestamp, "value: '"+wanIp+"'", "location: '"+ipFile+"'")
}
func writeIpFile(value string) {
timestamp := time.Now().UnixMilli()
error := os.WriteFile(ipFile, []byte(value), 0644)
if error != nil {
log.ErrorTimed("encountered an error writing the '.ipfile'", timestamp, "value: '"+value+"'", "location: '"+ipFile+"'", error.Error())
return
}
log.DebugTimed("wrote latest remote/wan ip to '.ipfile'", timestamp, "value: '"+wanIp+"'", "location: '"+ipFile+"'")
}
func getWANIp() (string, error) {
timestamp := time.Now().UnixMilli()
var ip string
providerUrl := config.IpProviderUrl()
response, error := http.Get(providerUrl)
if error != nil {
return ip, error
}
body, error := io.ReadAll(response.Body)
if error != nil {
return ip, error
}
ip = strings.TrimSpace(string(body))
log.InfoTimed("got remote/wan ip '"+ip+"'", timestamp, "ip provider: '"+providerUrl+"'")
return ip, nil
}
func buildDyndnsdUrl(wanIp string) string {
return config.DyndnsUrl() + "?hostname=" + config.DyndnsHostname() + "&myip=" + wanIp
}
func updateDyndns() {
timestamp := time.Now().UnixMilli()
tmpIp, error := getWANIp()
if error != nil {
log.ErrorTimed("encountered an error getting the remote/wan ip", timestamp, "ip provider: '"+config.IpProviderUrl()+"'", error.Error())
}
if len(tmpIp) <= 0 {
log.ErrorTimed("will not update dyndns because remote/wan ip is empty", timestamp)
}
if tmpIp == wanIp {
log.InfoTimed("nothing to do - remote/wan ip has not changed", timestamp, "ip: "+wanIp)
return
}
dyndnsUrl := buildDyndnsdUrl(tmpIp)
request, error := http.NewRequest("GET", dyndnsUrl, nil)
if error != nil {
msg := "encountered an error creating the http request to update dyndns"
log.ErrorTimed(msg, timestamp, error.Error())
notifications.Send(notifications.Notification{
Title: "godyn",
Message: msg + "\n" + error.Error(),
Priority: notifications.PRIORITY_MAX,
Emoji: "red_circle",
})
return
}
request.SetBasicAuth(config.DyndnsUsername(), config.DyndnsPassword())
response, error := http.DefaultClient.Do(request)
if error != nil {
msg := "encountered an error updating dyndns"
log.ErrorTimed(msg, timestamp, "url: '"+dyndnsUrl+"'", error.Error())
notifications.Send(notifications.Notification{
Title: "godyn",
Message: msg + "\n" + error.Error(),
Priority: notifications.PRIORITY_MAX,
Emoji: "red_circle",
})
return
}
bytes, error := io.ReadAll(response.Body)
if error != nil {
msg := "encountered an error reading the response from the dyndns provider"
log.ErrorTimed(msg, timestamp, error.Error())
notifications.Send(notifications.Notification{
Title: "godyn",
Message: msg + "\n" + error.Error(),
Priority: notifications.PRIORITY_MAX,
Emoji: "red_circle",
})
return
}
body := strings.ToLower(strings.TrimSpace(string(bytes)))
if !strings.Contains(body, tmpIp) {
statuscode := strconv.Itoa(response.StatusCode)
msg := "probably encountered an error updating dyndns"
log.WarningTimed(msg, timestamp, "status code: "+statuscode+"'", "response: '"+body+"'")
notifications.Send(notifications.Notification{
Message: msg + "\n" +
"status code: " + statuscode + "\n" +
"response: '" + body + "'",
Priority: notifications.PRIORITY_MAX,
Emoji: "red_circle",
})
return
}
notification := notifications.Notification{}
var msg string
if strings.Contains(body, "abuse") {
msg = "dyndns was not updated because remote/wan ip did not change and the dyndns provider reported an abusive update"
log.WarningTimed(msg, timestamp, "response: '"+body+"'")
notification.Priority = notifications.PRIORITY_HIGH
notification.Emoji = "orange_circle"
} else if strings.Contains(body, "nochg") {
msg = "dyndns was not updated because remote/wan ip did not change"
log.WarningTimed(msg, timestamp, "response: '"+body+"'")
notification.Emoji = "yellow_circle"
} else {
msg = "dyndns was updated successfully"
log.InfoTimed(msg, timestamp, "response: '"+strings.TrimSpace(string(bytes))+"'")
msg += "\n" + "ip-address: " + tmpIp
notification.Priority = notifications.PRIORITY_MAX
notification.Emoji = "green_circle"
}
notification.Message = msg
notifications.Send(notification)
wanIp = tmpIp
writeIpFile(wanIp)
}