feat: DNS lookup instead of HTTP HEAD request (#1)
This commit is contained in:
parent
0553bf07e5
commit
6fead37859
2
Makefile
2
Makefile
|
@ -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!"
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"`
|
||||||
|
}
|
6
main.go
6
main.go
|
@ -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...")
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue