diff --git a/README.md b/README.md index 01f1bd0..f3e968a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,17 @@ # DDNS Client +## How to install DDNS-CLIENT + +Simply run the command `go get github.com/datahearth/ddnsclient` + +## Run the client + +You have 2 options to run the DDNS client. +You can either run it as a docker container: +`PENDING...` +Or as binary executable: +`PENDING...` + ## Supported providers -- OVH (list of available [regions](https://github.com/ovh/php-ovh/#supported-apis)) \ No newline at end of file +- OVH (list of available [regions](https://github.com/ovh/php-ovh/#supported-apis). TO BE CHECKED HOW TO SHIT WORKS) \ No newline at end of file diff --git a/main.go b/main.go index 7663c7f..500018d 100644 --- a/main.go +++ b/main.go @@ -1,31 +1,30 @@ package ddnsclient import ( - "github.com/datahearth/ddnsclient/pkg/http" + "time" + "github.com/datahearth/ddnsclient/pkg/providers/ovh" + "github.com/datahearth/ddnsclient/pkg/watcher" "github.com/sirupsen/logrus" + "github.com/spf13/viper" ) // Start create a new instance of ddns-client func Start(logger logrus.FieldLogger) error { - ddnsHTTP, err := http.NewHTTP(logger) - if err != nil { - return err - } ovh, err := ovh.NewOVH(logger) if err != nil { return err } - check := make(chan bool) + + w, err := watcher.NewWatcher(logger, ovh, viper.GetString("web-ip")) + if err != nil { + return err + } + c := make(chan bool) - + go w.Run(c) for { - select { - case <-check: - - case <-c: - break - } + } return nil diff --git a/pkg/http/main.go b/pkg/http/main.go deleted file mode 100644 index 894f01a..0000000 --- a/pkg/http/main.go +++ /dev/null @@ -1,132 +0,0 @@ -package http - -import ( - "io/ioutil" - "net" - h "net/http" - - "github.com/datahearth/ddnsclient/pkg/utils" - "github.com/sirupsen/logrus" - "github.com/spf13/viper" -) - -// HTTP is the base interface to interact with websites -type HTTP interface { - CheckIPAddr(addr string) (bool, error) - GetServerIP() (string, error) - GetSubdomainIP(addr string) (string, error) - RetrieveSubdomainIP(addr string) error - RetrieveServerIP() error -} - -type http struct { - logger logrus.FieldLogger - serverIP string - subdomainIP string - webIP string -} - -// NewHTTP instanciate a new http implementation -func NewHTTP(logger logrus.FieldLogger) (HTTP, error) { - if logger == nil { - return nil, utils.ErrNilLogger - } - - logger = logger.WithField("package", "http") - webIP := viper.GetString("web-ip") - - if webIP == "" { - logger.Infoln("web-ip field not set in config. Using default") - webIP = "http://dynamicdns.park-your-domain.com/getip" - } - - return &http{ - logger: logger, - webIP: webIP, - }, nil -} - -// RetrieveServerIP will use the defined web-ip service to get the server public address and save it to the struct -func (http *http) RetrieveServerIP() error { - resp, err := h.Get(http.webIP) - if err != nil { - return err - } - if resp.StatusCode != 200 { - return utils.ErrWrongStatusCode - } - d, err := ioutil.ReadAll(resp.Body) - if err != nil { - return err - } - - http.serverIP = string(d) - - return nil -} - -// GetServerIP will return the IP addr of the server. -// If you haven't triggered the function before manually or with CheckIPAddr, -// it'll trigger the RetrieveServerIP function and save it. Then, it'll return the IP -func (http *http) GetServerIP() (string, error) { - if http.serverIP == "" { - if err := http.RetrieveServerIP(); err != nil { - return "", err - } - } - - return http.serverIP, nil -} - -// RetrieveSubdomainIP will retrieve the subdomain IP with a HEAD request then save it to the struct -func (http *http) RetrieveSubdomainIP(addr string) error { - resp, err := h.Head(addr) - if err != nil { - return err - } - if resp.StatusCode != 200 { - return utils.ErrWrongStatusCode - } - - h, _, err := net.SplitHostPort(resp.Request.RemoteAddr) - if err != nil { - return err - } - - http.subdomainIP = h - - return nil -} - -// GetSubdomainIP will return the IP addr of the subdomain. -// If you haven't triggered the function before manually or with CheckIPAddr, -// it'll trigger the RetrieveSubdomainIP function and save it. Then, it'll return the IP -func (http *http) GetSubdomainIP(addr string) (string, error) { - if http.subdomainIP == "" { - if err := http.RetrieveSubdomainIP(addr); err != nil { - return "", err - } - } - - return http.subdomainIP, nil -} - -// CheckIPAddr will get both ip addr (subdomain from addr and server) and compare it. -// If one of them is missing, it'll retrieve it and then save it -func (http *http) CheckIPAddr(addr string) (bool, error) { - srvIP, err := http.GetServerIP() - if err != nil { - return false, err - } - - subIP, err := http.GetSubdomainIP(addr) - if err != nil { - return false, err - } - - if srvIP != subIP { - return false, nil - } - - return true, nil -} diff --git a/pkg/subdomain/main.go b/pkg/subdomain/main.go new file mode 100644 index 0000000..fedc252 --- /dev/null +++ b/pkg/subdomain/main.go @@ -0,0 +1,73 @@ +package subdomain + +import ( + "net" + h "net/http" + + "github.com/datahearth/ddnsclient/pkg/utils" + "github.com/sirupsen/logrus" +) + +// HTTP is the base interface to interact with websites +type Subdomain interface { + CheckIPAddr(srvIP string) (bool, error) + GetSubdomainIP() string + retrieveSubdomainIP() error +} + +type subdomain struct { + logger logrus.FieldLogger + subdomainAddr string + ip string +} + +// NewSubdomain instanciate a new http implementation +func NewSubdomain(logger logrus.FieldLogger, subdomainAddr string) (Subdomain, error) { + if logger == nil { + return nil, utils.ErrNilLogger + } + + logger = logger.WithField("package", "http") + + return &subdomain{ + logger: logger, + subdomainAddr: subdomainAddr, + }, nil +} + +// RetrieveSubdomainIP will retrieve the subdomain IP with a HEAD request +func (sd *subdomain) retrieveSubdomainIP() error { + resp, err := h.Head(sd.subdomainAddr) + if err != nil { + return err + } + if resp.StatusCode != 200 { + return utils.ErrWrongStatusCode + } + + h, _, err := net.SplitHostPort(resp.Request.RemoteAddr) + if err != nil { + return err + } + + sd.ip = h + + return nil +} + +// CheckIPAddr will compare the srvIP passed in parameter and the subIP retrieved from the head request +func (sd *subdomain) CheckIPAddr(srvIP string) (bool, error) { + if err := sd.retrieveSubdomainIP(); err != nil { + return false, err + } + + if srvIP != sd.ip { + return false, nil + } + + return true, nil +} + +func (sd *subdomain) GetSubdomainIP() string { + return sd.ip +} diff --git a/pkg/utils/subdomains.go b/pkg/utils/subdomains.go deleted file mode 100644 index d8d60cf..0000000 --- a/pkg/utils/subdomains.go +++ /dev/null @@ -1,10 +0,0 @@ -package utils - -func AggregateSubdomains(subdomains []string, domain string) []string { - agdSub := make([]string, len(subdomains)) - for _, sd := range subdomains { - agdSub = append(agdSub, sd+"."+domain) - } - - return agdSub -} diff --git a/pkg/utils/config.go b/pkg/utils/utils.go similarity index 61% rename from pkg/utils/config.go rename to pkg/utils/utils.go index feb7fee..2be7052 100644 --- a/pkg/utils/config.go +++ b/pkg/utils/utils.go @@ -1,6 +1,9 @@ package utils import ( + "io/ioutil" + "net/http" + "github.com/sirupsen/logrus" "github.com/spf13/viper" ) @@ -50,3 +53,32 @@ func SetupLogger(logger *logrus.Logger) { DisableTimestamp: timestamp, }) } + +func AggregateSubdomains(subdomains []string, domain string) []string { + agdSub := make([]string, len(subdomains)) + for _, sd := range subdomains { + agdSub = append(agdSub, sd+"."+domain) + } + + return agdSub +} + +// RetrieveServerIP will use the defined web-ip service to get the server public address and save it to the struct +func RetrieveServerIP(webIP) (string, error) { + // * retrieve client's server IP + resp, err := http.Get(webIP) + if err != nil { + return "", err + } + if resp.StatusCode != 200 { + return "", ErrWrongStatusCode + } + + // * get ip from body + d, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + + return string(d), nil +} diff --git a/pkg/watcher/watcher.go b/pkg/watcher/watcher.go index aefc2f1..1ccb9d3 100644 --- a/pkg/watcher/watcher.go +++ b/pkg/watcher/watcher.go @@ -1,47 +1,76 @@ package watcher import ( - "github.com/datahearth/ddnsclient/pkg/http" "github.com/datahearth/ddnsclient/pkg/providers" + "github.com/datahearth/ddnsclient/pkg/subdomain" "github.com/datahearth/ddnsclient/pkg/utils" "github.com/sirupsen/logrus" "github.com/spf13/viper" ) type Watcher interface { - Run() error + Run(chan bool) error } type watcher struct { logger logrus.FieldLogger provider providers.Provider - http http.HTTP - subdomains []string + subdomains []subdomain.Subdomain domain string + webIP string } -func NewWatcher(logger logrus.FieldLogger, provider providers.Provider, http http.HTTP) (Watcher, error) { +func NewWatcher(logger logrus.FieldLogger, provider providers.Provider, webIP string) (Watcher, error) { if logger == nil { return nil, utils.ErrNilLogger } if provider == nil { return nil, ErrNilProvider } - if http == nil { - return nil, ErrNilHTTP + if webIP == "" { + webIP = "http://dynamicdns.park-your-domain.com/getip" } domain := viper.GetStringMap("watcher")["domain"].(string) - subdomains := utils.AggregateSubdomains(viper.GetStringMap("watcher")["subdomains"].([]string), domain) + sbs := utils.AggregateSubdomains(viper.GetStringMap("watcher")["subdomains"].([]string), domain) + subdomains := make([]subdomain.Subdomain, len(sbs)) + for _, sd := range sbs { + sub, err := subdomain.NewSubdomain(logger, sd) + if err != nil { + return nil, err + } + + subdomains = append(subdomains, sub) + } return &watcher{ logger: logger, provider: provider, - http: http, domain: domain, subdomains: subdomains, + webIP: webIP, }, nil } -func (w *watcher) Run() error { - return nil +func (w *watcher) Run(close chan bool) error { + for { + select { + case <-close: + return nil + default: + srvIP, err := utils.RetrieveServerIP(w.webIP) + if err != nil { + return err + } + + for _, sd := range w.subdomains { + ok, err := sd.CheckIPAddr(srvIP) + if err != nil { + return err + } + if !ok { + w.provider.UpdateIP(sd.GetSubdomainIP(), srvIP) + } + } + } + } }