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
@echo "docker is required!"
endif
@echo "Pushing image ddnsclient:$(RELEASE_VERSION) to docker hub..."
@echo "Pushing image with tag to docker hub..."
@docker push ddnsclient:$(RELEASE_VERSION)
@echo "Image pushed!"

View File

@ -16,12 +16,13 @@ var (
Checkout the documentation for parameters in the yaml config file.
`,
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)
}
},
}
logger = logrus.StandardLogger()
config ddnsclient.ClientConfig
)
func init() {
@ -34,6 +35,9 @@ func init() {
}
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)
}

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
func Start(logger logrus.FieldLogger) error {
func Start(logger logrus.FieldLogger, config ClientConfig) error {
log := logger.WithFields(logrus.Fields{
"pkg": "ddnsclient",
"component": "root",
@ -40,13 +40,13 @@ func Start(logger logrus.FieldLogger) error {
defer close(sigc)
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)
for {
select {
case err := <-chErr:
log.WithError(err).Errorln("An error occured while running the watcher. Retrying in the next tick")
log.Errorln(err.Error())
continue
case <-sigc:
log.Infoln("Interrupt signal received. Stopping watcher...")

View File

@ -46,7 +46,6 @@ func (ovh *ovh) UpdateIP(subdomain, ip string) error {
// * create GET request
req, err := http.NewRequest("GET", newURL, nil)
if err != nil {
logger.WithError(err).WithField("request-type", "GET").Errorln(utils.ErrCreateNewRequest.Error())
return utils.ErrCreateNewRequest
}
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
logger.WithFields(logrus.Fields{
"subdomain": subdomain,
"new-ip": ip,
"new-ip": ip,
}).Debugln("calling OVH DynHost to update subdomain IP")
c := new(http.Client)
resp, err := c.Do(req)
if err != nil {
logger.WithError(err).Errorln(utils.ErrUpdateRequest.Error())
return utils.ErrUpdateRequest
}
if resp.StatusCode != 200 {
logger.WithField("status-code", resp.StatusCode).Errorln(utils.ErrWrongStatusCode.Error())
return utils.ErrWrongStatusCode
}
return nil
}

View File

@ -2,9 +2,6 @@ package subdomain
import (
"net"
"net/http"
h "net/http"
"net/http/httptrace"
"strings"
"time"
@ -45,54 +42,24 @@ func NewSubdomain(logger logrus.FieldLogger, subdomainAddr string) (Subdomain, e
// RetrieveSubdomainIP will retrieve the subdomain IP with a HEAD request
func (sd *subdomain) retrieveSubdomainIP() error {
var remoteAddr string
logger := sd.logger.WithField("component", "retrieve-subdomain-ip")
// * create HEAD request
req, err := http.NewRequest("HEAD", "https://"+sd.subdomainAddr, nil)
ips, err := net.LookupIP(sd.subdomainAddr)
if err != nil {
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
client := new(h.Client)
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
if len(ips) != 1 {
return utils.ErrIpLenght
}
// * check if remote address contains a port
if strings.Contains(remoteAddr, ":") {
remoteAddr, _, err = net.SplitHostPort(remoteAddr)
ip := ips[0].String()
if strings.Contains(ip, ":") {
ip, _, err = net.SplitHostPort(ip)
if err != nil {
logger.WithError(err).WithField("remote-address", remoteAddr).Errorln(utils.ErrSplitAddr.Error())
return utils.ErrSplitAddr
}
}
sd.ip = remoteAddr
sd.ip = ip
return nil
}

View File

@ -13,6 +13,18 @@ func (sb *subdomain) SubIsPending(sbs PendingSubdomains) bool {
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.
// If so, it/they will be returned as a slice.
// If not, it returns nil.
@ -31,18 +43,6 @@ func CheckPendingSubdomains(sbs PendingSubdomains, now time.Time) PendingSubdoma
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 {
for t := range delSbs {
delete(pending, t)

View File

@ -17,9 +17,9 @@ var (
// ErrNilHTTP ...
ErrNilHTTP = errors.New("http is mandatory")
// 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 = 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 = errors.New("can't parse response body")
// ErrHeadRemoteIP ...
@ -30,6 +30,8 @@ var (
ErrCreateNewRequest = errors.New("can't create http request")
// ErrUpdateRequest ...
ErrUpdateRequest = errors.New("failed to set new IP address")
// ErrIpLength ...
ErrIpLenght = errors.New("zero or more than 1 ips have been found")
)
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
func RetrieveServerIP(webIP string) (string, error) {
logger := logrus.WithFields(logrus.Fields{
"pkg": "utils",
"component": "server-ip",
})
// * retrieve client's server IP
resp, err := http.Get(webIP)
if err != nil {
logger.WithError(err).WithField("web-ip", webIP).Errorln(ErrGetServerIP.Error())
return "", ErrGetServerIP
}
if resp.StatusCode != 200 {
logger.WithError(err).WithFields(logrus.Fields{
"web-ip": webIP,
"statuc-code": resp.StatusCode,
}).Errorln(ErrWrongStatusCode.Error())
return "", ErrWrongStatusCode
}
// * get ip from body
d, err := ioutil.ReadAll(resp.Body)
if err != nil {
logger.WithError(err).WithField("web-ip", webIP).Errorln(ErrParseHTTPBody.Error())
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 {
logger := w.logger.WithField("component", "runDDNSCheck")
logger.Infoln("Starting DDNS check...")
srvIP, err := utils.RetrieveServerIP(w.webIP)
if err != nil {
return err
}
logger.Debugln("Checking server IP...")
srvIP = "109.14.53.74" // tmp
for _, sb := range w.subdomains {
if sb.SubIsPending(w.pendingSubdomains) {