feat: DNS lookup instead of HTTP HEAD request (#1)

This commit is contained in:
Antoine Langlois 2021-03-18 10:12:04 +01:00 committed by GitHub
parent 0553bf07e5
commit 6fead37859
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 68 additions and 75 deletions

View File

@ -25,7 +25,7 @@ deploy-image-release:
ifndef DOCKER ifndef DOCKER
@echo "docker is required!" @echo "docker is required!"
endif endif
@echo "Pushing image ddnsclient:$(RELEASE_VERSION) to docker hub..." @echo "Pushing image with tag to docker hub..."
@docker push ddnsclient:$(RELEASE_VERSION) @docker push ddnsclient:$(RELEASE_VERSION)
@echo "Image pushed!" @echo "Image pushed!"

View File

@ -16,12 +16,13 @@ var (
Checkout the documentation for parameters in the yaml config file. Checkout the documentation for parameters in the yaml config file.
`, `,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
if err := ddnsclient.Start(logger); err != nil { if err := ddnsclient.Start(logger, config); err != nil {
logrus.Error(err) logrus.Error(err)
} }
}, },
} }
logger = logrus.StandardLogger() logger = logrus.StandardLogger()
config ddnsclient.ClientConfig
) )
func init() { func init() {
@ -34,6 +35,9 @@ func init() {
} }
utils.LoadConfig() utils.LoadConfig()
if err := viper.Unmarshal(&config); err != nil {
logger.WithError(err).Fatalln("failed to map yaml config file into ClientConfig struct")
}
utils.SetupLogger(logger) utils.SetupLogger(logger)
} }

30
config.go Normal file
View File

@ -0,0 +1,30 @@
package ddnsclient
type ClientConfig struct {
Logger Logger `mapstructure:"logger"`
Providers Providers `mapstructure:"providers"`
Watcher Watcher `mapstructure:"watcher"`
UpdateTime int `mapstructure:"update-time"`
WebIP string `mapstructure:"web-ip"`
}
type Logger struct {
Level string `mapstructure:"level"`
DisableTimestamp bool `mapstructure:"disable-timestamp"`
DisableColor bool `mapstructure:"disable-color"`
}
type Providers struct {
Ovh Ovh `mapstructure:"ovh,omitempty"`
}
type Ovh struct {
URL string `mapstructure:"url"`
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
}
type Watcher struct {
Domain string `mapstructure:"domain"`
Subdomains []string `mapstructure:"subdomains"`
}

View File

@ -13,7 +13,7 @@ import (
) )
// Start create a new instance of ddns-client // Start create a new instance of ddns-client
func Start(logger logrus.FieldLogger) error { func Start(logger logrus.FieldLogger, config ClientConfig) error {
log := logger.WithFields(logrus.Fields{ log := logger.WithFields(logrus.Fields{
"pkg": "ddnsclient", "pkg": "ddnsclient",
"component": "root", "component": "root",
@ -40,13 +40,13 @@ func Start(logger logrus.FieldLogger) error {
defer close(sigc) defer close(sigc)
log.Infoln("Start watching periodically for changes!") log.Infoln("Start watching periodically for changes!")
t := time.NewTicker(viper.GetDuration("update-time")*time.Second) t := time.NewTicker(viper.GetDuration("update-time") * time.Second)
go w.Run(t, chClose, chErr) go w.Run(t, chClose, chErr)
for { for {
select { select {
case err := <-chErr: case err := <-chErr:
log.WithError(err).Errorln("An error occured while running the watcher. Retrying in the next tick") log.Errorln(err.Error())
continue continue
case <-sigc: case <-sigc:
log.Infoln("Interrupt signal received. Stopping watcher...") log.Infoln("Interrupt signal received. Stopping watcher...")

View File

@ -46,7 +46,6 @@ func (ovh *ovh) UpdateIP(subdomain, ip string) error {
// * create GET request // * create GET request
req, err := http.NewRequest("GET", newURL, nil) req, err := http.NewRequest("GET", newURL, nil)
if err != nil { if err != nil {
logger.WithError(err).WithField("request-type", "GET").Errorln(utils.ErrCreateNewRequest.Error())
return utils.ErrCreateNewRequest return utils.ErrCreateNewRequest
} }
req.SetBasicAuth(ovh.ovhConfig["username"].(string), ovh.ovhConfig["password"].(string)) req.SetBasicAuth(ovh.ovhConfig["username"].(string), ovh.ovhConfig["password"].(string))
@ -54,19 +53,16 @@ func (ovh *ovh) UpdateIP(subdomain, ip string) error {
// * perform GET request // * perform GET request
logger.WithFields(logrus.Fields{ logger.WithFields(logrus.Fields{
"subdomain": subdomain, "subdomain": subdomain,
"new-ip": ip, "new-ip": ip,
}).Debugln("calling OVH DynHost to update subdomain IP") }).Debugln("calling OVH DynHost to update subdomain IP")
c := new(http.Client) c := new(http.Client)
resp, err := c.Do(req) resp, err := c.Do(req)
if err != nil { if err != nil {
logger.WithError(err).Errorln(utils.ErrUpdateRequest.Error())
return utils.ErrUpdateRequest return utils.ErrUpdateRequest
} }
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
logger.WithField("status-code", resp.StatusCode).Errorln(utils.ErrWrongStatusCode.Error())
return utils.ErrWrongStatusCode return utils.ErrWrongStatusCode
} }
return nil return nil
} }

View File

@ -2,9 +2,6 @@ package subdomain
import ( import (
"net" "net"
"net/http"
h "net/http"
"net/http/httptrace"
"strings" "strings"
"time" "time"
@ -45,54 +42,24 @@ func NewSubdomain(logger logrus.FieldLogger, subdomainAddr string) (Subdomain, e
// RetrieveSubdomainIP will retrieve the subdomain IP with a HEAD request // RetrieveSubdomainIP will retrieve the subdomain IP with a HEAD request
func (sd *subdomain) retrieveSubdomainIP() error { func (sd *subdomain) retrieveSubdomainIP() error {
var remoteAddr string ips, err := net.LookupIP(sd.subdomainAddr)
logger := sd.logger.WithField("component", "retrieve-subdomain-ip")
// * create HEAD request
req, err := http.NewRequest("HEAD", "https://"+sd.subdomainAddr, nil)
if err != nil { if err != nil {
return err return err
} }
// * create a trace to get server remote address
trace := &httptrace.ClientTrace{
GotConn: func(gci httptrace.GotConnInfo) {
remoteAddr = gci.Conn.RemoteAddr().String()
},
}
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
// * create a client to perform the request if len(ips) != 1 {
client := new(h.Client) return utils.ErrIpLenght
client.Timeout = 5 * time.Second
// * perform the request
resp, err := client.Do(req)
if err != nil {
// todo: ignoring errors is bad. Implement a solution to scrape 100% of the time remote addr
logger.WithError(err).WithFields(logrus.Fields{
"subdomain": sd.subdomainAddr,
}).Errorln(utils.ErrHeadRemoteIP.Error())
sd.ip = ""
return nil
}
if resp.StatusCode != 200 && remoteAddr == "" {
logger.WithFields(logrus.Fields{
"status-code": resp.StatusCode,
"subdomain": sd.subdomainAddr,
}).Errorln(utils.ErrWrongStatusCode.Error())
return utils.ErrWrongStatusCode
} }
// * check if remote address contains a port ip := ips[0].String()
if strings.Contains(remoteAddr, ":") { if strings.Contains(ip, ":") {
remoteAddr, _, err = net.SplitHostPort(remoteAddr) ip, _, err = net.SplitHostPort(ip)
if err != nil { if err != nil {
logger.WithError(err).WithField("remote-address", remoteAddr).Errorln(utils.ErrSplitAddr.Error())
return utils.ErrSplitAddr return utils.ErrSplitAddr
} }
} }
sd.ip = remoteAddr sd.ip = ip
return nil return nil
} }

View File

@ -13,6 +13,18 @@ func (sb *subdomain) SubIsPending(sbs PendingSubdomains) bool {
return false return false
} }
// FindSubdomain returns a subdomain found in the pending map of subdomain.
// If not found, it returns nil.
func (sb *subdomain) FindSubdomain(sbs PendingSubdomains) Subdomain {
for _, sub := range sbs {
if sub == sb {
return sb
}
}
return nil
}
// CheckPendingSubdomains check if any pending subdomains are waiting to be restored. // CheckPendingSubdomains check if any pending subdomains are waiting to be restored.
// If so, it/they will be returned as a slice. // If so, it/they will be returned as a slice.
// If not, it returns nil. // If not, it returns nil.
@ -31,18 +43,6 @@ func CheckPendingSubdomains(sbs PendingSubdomains, now time.Time) PendingSubdoma
return delSbs return delSbs
} }
// FindSubdomain returns a subdomain found in the pending map of subdomain.
// If not found, it returns nil.
func (sb *subdomain) FindSubdomain(sbs PendingSubdomains) Subdomain {
for _, sub := range sbs {
if sub == sb {
return sb
}
}
return nil
}
func DeletePendingSubdomains(delSbs PendingSubdomains, pending PendingSubdomains) PendingSubdomains { func DeletePendingSubdomains(delSbs PendingSubdomains, pending PendingSubdomains) PendingSubdomains {
for t := range delSbs { for t := range delSbs {
delete(pending, t) delete(pending, t)

View File

@ -17,9 +17,9 @@ var (
// ErrNilHTTP ... // ErrNilHTTP ...
ErrNilHTTP = errors.New("http is mandatory") ErrNilHTTP = errors.New("http is mandatory")
// ErrWrongStatusCode is thrown when the response status code isn't a 200 // ErrWrongStatusCode is thrown when the response status code isn't a 200
ErrWrongStatusCode = errors.New("response sent an non 200 status code") ErrWrongStatusCode = errors.New("web-ip returns a non 200 status code")
// ErrGetServerIP is thrown when HTTP can't contact the web-ip service // ErrGetServerIP is thrown when HTTP can't contact the web-ip service
ErrGetServerIP = errors.New("failed to fetch server IP") ErrGetServerIP = errors.New("HTTP error")
// ErrParseHTTPBody is thrown when the HTTP service can't parse the body response // ErrParseHTTPBody is thrown when the HTTP service can't parse the body response
ErrParseHTTPBody = errors.New("can't parse response body") ErrParseHTTPBody = errors.New("can't parse response body")
// ErrHeadRemoteIP ... // ErrHeadRemoteIP ...
@ -30,6 +30,8 @@ var (
ErrCreateNewRequest = errors.New("can't create http request") ErrCreateNewRequest = errors.New("can't create http request")
// ErrUpdateRequest ... // ErrUpdateRequest ...
ErrUpdateRequest = errors.New("failed to set new IP address") ErrUpdateRequest = errors.New("failed to set new IP address")
// ErrIpLength ...
ErrIpLenght = errors.New("zero or more than 1 ips have been found")
) )
type ( type (

View File

@ -65,29 +65,18 @@ func AggregateSubdomains(subdomains []string, domain string) []string {
// RetrieveServerIP will use the defined web-ip service to get the server public address and save it to the struct // RetrieveServerIP will use the defined web-ip service to get the server public address and save it to the struct
func RetrieveServerIP(webIP string) (string, error) { func RetrieveServerIP(webIP string) (string, error) {
logger := logrus.WithFields(logrus.Fields{
"pkg": "utils",
"component": "server-ip",
})
// * retrieve client's server IP // * retrieve client's server IP
resp, err := http.Get(webIP) resp, err := http.Get(webIP)
if err != nil { if err != nil {
logger.WithError(err).WithField("web-ip", webIP).Errorln(ErrGetServerIP.Error())
return "", ErrGetServerIP return "", ErrGetServerIP
} }
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
logger.WithError(err).WithFields(logrus.Fields{
"web-ip": webIP,
"statuc-code": resp.StatusCode,
}).Errorln(ErrWrongStatusCode.Error())
return "", ErrWrongStatusCode return "", ErrWrongStatusCode
} }
// * get ip from body // * get ip from body
d, err := ioutil.ReadAll(resp.Body) d, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
logger.WithError(err).WithField("web-ip", webIP).Errorln(ErrParseHTTPBody.Error())
return "", ErrParseHTTPBody return "", ErrParseHTTPBody
} }

View File

@ -94,11 +94,16 @@ func (w *watcher) Run(t *time.Ticker, chClose chan struct{}, chErr chan error) {
func (w *watcher) runDDNSCheck() error { func (w *watcher) runDDNSCheck() error {
logger := w.logger.WithField("component", "runDDNSCheck") logger := w.logger.WithField("component", "runDDNSCheck")
logger.Infoln("Starting DDNS check...") logger.Infoln("Starting DDNS check...")
srvIP, err := utils.RetrieveServerIP(w.webIP) srvIP, err := utils.RetrieveServerIP(w.webIP)
if err != nil { if err != nil {
return err return err
} }
logger.Debugln("Checking server IP...")
srvIP = "109.14.53.74" // tmp
for _, sb := range w.subdomains { for _, sb := range w.subdomains {
if sb.SubIsPending(w.pendingSubdomains) { if sb.SubIsPending(w.pendingSubdomains) {