diff --git a/README.md b/README.md index 4c64e19..8d2b69d 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Now I rewrite [DynDNS](https://github.com/TimothyYe/DynDNS) by Golang and call i * AliDNS ([https://help.aliyun.com/product/29697.html](https://help.aliyun.com/product/29697.html)) * DuckDNS ([https://www.duckdns.org](https://www.duckdns.org)) * Dreamhost ([https://www.dreamhost.com](https://www.dreamhost.com)) +* No-IP ([https://www.noip.com/](https://www.noip.com)) ## Supported Platforms @@ -123,6 +124,7 @@ Supported provider(s): * DuckDNS * Google Domains * HE.net +* No-IP To enable the `IPv6` support of GoDNS, there are 2 solutions you can choose: * Get IPv6 address online @@ -323,6 +325,27 @@ For DuckDNS, only need to provide the `token`, config 1 default domain & subdoma } ``` +### Config example for No-IP + +```json +{ + "provider": "NoIP", + "email": "mail@example.com", + "password": "YourPassword", + "domains": [ + { + "domain_name": "ddns.net", + "sub_domains": ["timothyye6"] + } + ], + "ip_type": "IPv4", + "ip_url": "https://myip.biturl.top", + "resolver": "8.8.8.8", + "interval": 300, + "socks5_proxy": "" +} +``` + ### Config example for HE.net For HE, email is not needed, just fill DDNS key to password, and config all the domains & subdomains. diff --git a/cmd/godns/godns.go b/cmd/godns/godns.go index 5700343..15abb54 100644 --- a/cmd/godns/godns.go +++ b/cmd/godns/godns.go @@ -49,7 +49,7 @@ func main() { func dnsLoop() { panicChan := make(chan godns.Domain) - log.Println("Creating DNS h with provider:", configuration.Provider) + log.Println("Creating DNS handler with provider:", configuration.Provider) h := handler.CreateHandler(configuration.Provider) h.SetConfiguration(&configuration) for i := range configuration.Domains { diff --git a/handler/alidns/alidns_handler.go b/handler/alidns/alidns_handler.go index 8d2afa4..8885000 100644 --- a/handler/alidns/alidns_handler.go +++ b/handler/alidns/alidns_handler.go @@ -48,7 +48,11 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns. log.Println("currentIP is:", currentIP) for _, subDomain := range domain.SubDomains { hostname := subDomain + "." + domain.DomainName - lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType) + lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType) + if err != nil { + log.Println(err) + continue + } //check against currently known IP, if no change, skip update if currentIP == lastIP { log.Printf("IP is the same as cached one. Skip update.\n") diff --git a/handler/dnspod/dnspod_handler.go b/handler/dnspod/dnspod_handler.go index 84ad0b1..ee7939a 100644 --- a/handler/dnspod/dnspod_handler.go +++ b/handler/dnspod/dnspod_handler.go @@ -63,7 +63,12 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns. for _, subDomain := range domain.SubDomains { hostname := subDomain + "." + domain.DomainName - lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType) + lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType) + if err != nil { + log.Println(err) + continue + } + //check against currently known IP, if no change, skip update if currentIP == lastIP { log.Printf("IP is the same as cached one. Skip update.\n") diff --git a/handler/dreamhost/dreamhost_handler.go b/handler/dreamhost/dreamhost_handler.go index 92df48e..399a3f0 100644 --- a/handler/dreamhost/dreamhost_handler.go +++ b/handler/dreamhost/dreamhost_handler.go @@ -57,7 +57,12 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns. for _, subDomain := range domain.SubDomains { hostname := subDomain + "." + domain.DomainName - lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType) + lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType) + if err != nil { + log.Println(err) + continue + } + //check against currently known IP, if no change, skip update if currentIP == lastIP { log.Printf("IP is the same as cached one. Skip update.\n") diff --git a/handler/duck/duck_handler.go b/handler/duck/duck_handler.go index 0131b22..96cf405 100644 --- a/handler/duck/duck_handler.go +++ b/handler/duck/duck_handler.go @@ -51,10 +51,8 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns. log.Println("get_currentIP:", err) continue } + log.Println("currentIP is:", currentIP) - - //check against locally cached IP, if no change, skip update - client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy) var ip string @@ -66,7 +64,12 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns. for _, subDomain := range domain.SubDomains { hostname := subDomain + "." + domain.DomainName - lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType) + lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType) + if err != nil { + log.Println(err) + continue + } + //check against currently known IP, if no change, skip update if currentIP == lastIP { log.Printf("IP is the same as cached one. Skip update.\n") diff --git a/handler/google/google_handler.go b/handler/google/google_handler.go index 5225723..52339ae 100644 --- a/handler/google/google_handler.go +++ b/handler/google/google_handler.go @@ -54,7 +54,12 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns. log.Println("currentIP is:", currentIP) for _, subDomain := range domain.SubDomains { hostname := subDomain + "." + domain.DomainName - lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType) + lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType) + if err != nil { + log.Println(err) + continue + } + //check against currently known IP, if no change, skip update if currentIP == lastIP { log.Printf("IP is the same as cached one. Skip update.\n") diff --git a/handler/handler.go b/handler/handler.go index 4455fd4..0156c7a 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -9,6 +9,7 @@ import ( "github.com/TimothyYe/godns/handler/duck" "github.com/TimothyYe/godns/handler/google" "github.com/TimothyYe/godns/handler/he" + "github.com/TimothyYe/godns/handler/noip" ) // IHandler is the interface for all DNS handlers @@ -36,6 +37,8 @@ func CreateHandler(provider string) IHandler { handler = IHandler(&google.Handler{}) case godns.DUCK: handler = IHandler(&duck.Handler{}) + case godns.NOIP: + handler = IHandler(&noip.Handler{}) } return handler diff --git a/handler/he/he_handler.go b/handler/he/he_handler.go index ddc2663..0f9c87b 100644 --- a/handler/he/he_handler.go +++ b/handler/he/he_handler.go @@ -58,7 +58,12 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns. for _, subDomain := range domain.SubDomains { hostname := subDomain + "." + domain.DomainName - lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType) + lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType) + if err != nil { + log.Println(err) + continue + } + //check against currently known IP, if no change, skip update if currentIP == lastIP { log.Printf("IP is the same as cached one. Skip update.\n") diff --git a/handler/noip/noip_handler.go b/handler/noip/noip_handler.go new file mode 100644 index 0000000..a373528 --- /dev/null +++ b/handler/noip/noip_handler.go @@ -0,0 +1,114 @@ +package noip + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + "runtime/debug" + "strings" + "time" + + "github.com/TimothyYe/godns" +) + +var ( + // NoIPUrl the API address for NoIP + NoIPUrl = "https://%s:%s@dynupdate.no-ip.com/nic/update?hostname=%s&%s" +) + +// Handler struct +type Handler struct { + Configuration *godns.Settings +} + +// SetConfiguration pass dns settings and store it to handler instance +func (handler *Handler) SetConfiguration(conf *godns.Settings) { + handler.Configuration = conf +} + +// DomainLoop the main logic loop +func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.Domain) { + defer func() { + if err := recover(); err != nil { + log.Printf("Recovered in %v: %v\n", err, debug.Stack()) + panicChan <- *domain + } + }() + + looping := false + + for { + if looping { + // Sleep with interval + log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval) + time.Sleep(time.Second * time.Duration(handler.Configuration.Interval)) + } + + looping = true + currentIP, err := godns.GetCurrentIP(handler.Configuration) + + if err != nil { + log.Println("get_currentIP:", err) + continue + } + + log.Println("currentIP is:", currentIP) + client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy) + + var ip string + if strings.ToUpper(handler.Configuration.IPType) == godns.IPV4 { + ip = fmt.Sprintf("myip=%s", currentIP) + } else if strings.ToUpper(handler.Configuration.IPType) == godns.IPV6 { + ip = fmt.Sprintf("myipv6=%s", currentIP) + } + + for _, subDomain := range domain.SubDomains { + hostname := subDomain + "." + domain.DomainName + lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType) + if err != nil { + log.Println(err) + continue + } + + //check against currently known IP, if no change, skip update + if currentIP == lastIP { + log.Printf("IP is the same as cached one. Skip update.\n") + } else { + req, _ := http.NewRequest("GET", fmt.Sprintf( + NoIPUrl, + handler.Configuration.Email, + handler.Configuration.Password, + hostname, + ip), nil) + + if handler.Configuration.UserAgent != "" { + req.Header.Add("User-Agent", handler.Configuration.UserAgent) + } + + // update IP with HTTP GET request + resp, err := client.Do(req) + if err != nil { + // handle error + log.Print("Failed to update sub domain:", subDomain) + continue + } + + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil || !strings.Contains(string(body), "good") { + log.Println("Failed to update the IP") + continue + } else { + log.Print("IP updated to:", currentIP) + } + + // Send notification + if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil { + log.Println("Failed to send notification") + } + } + } + } +} diff --git a/resolver/resolver.go b/resolver/resolver.go index 1badd89..6c2cfc9 100644 --- a/resolver/resolver.go +++ b/resolver/resolver.go @@ -83,18 +83,26 @@ func (r *DNSResolver) lookupHost(host string, dnsType uint16, triesLeft int) ([] } if dnsType == dns.TypeA { - for _, record := range in.Answer { - if t, ok := record.(*dns.A); ok { - result = append(result, t.A) + if len(in.Answer) > 0 { + for _, record := range in.Answer { + if t, ok := record.(*dns.A); ok { + result = append(result, t.A) + } } + } else { + return result, errors.New("empty result") } } if dnsType == dns.TypeAAAA { - for _, record := range in.Answer { - if t, ok := record.(*dns.AAAA); ok { - result = append(result, t.AAAA) + if len(in.Answer) > 0 { + for _, record := range in.Answer { + if t, ok := record.(*dns.AAAA); ok { + result = append(result, t.AAAA) + } } + } else { + return result, errors.New("Cannot resolve this domain, please make sure the IP type is right") } } diff --git a/utils.go b/utils.go index 0db6df5..bc248c8 100644 --- a/utils.go +++ b/utils.go @@ -54,6 +54,8 @@ const ( DUCK = "DuckDNS" // DREAMHOST for Dreamhost DREAMHOST = "Dreamhost" + // NOIP for NoIP + NOIP = "NoIP" // IPV4 for IPV4 mode IPV4 = "IPV4" // IPV6 for IPV6 mode @@ -218,6 +220,8 @@ func CheckSettings(config *Settings) error { return errors.New("login token cannot be empty") } case GOOGLE: + fallthrough + case NOIP: if config.Email == "" { return errors.New("email cannot be empty") } @@ -427,7 +431,7 @@ func buildTemplate(currentIP, domain string, tplsrc string) string { } // ResolveDNS will query DNS for a given hostname. -func ResolveDNS(hostname, resolver, ipType string) string { +func ResolveDNS(hostname, resolver, ipType string) (string, error) { var dnsType uint16 if ipType == "" || strings.ToUpper(ipType) == IPV4 { dnsType = dns.TypeA @@ -439,12 +443,10 @@ func ResolveDNS(hostname, resolver, ipType string) string { if resolver == "" { dnsAdress, err := net.LookupHost(hostname) if err != nil { - if strings.HasSuffix(err.Error(), "no such host") { - return "" - } - log.Fatalf(err.Error()) + return "", err } - return dnsAdress[0] + + return dnsAdress[0], nil } res := dnsResolver.New([]string{resolver}) // In case of i/o timeout @@ -452,11 +454,9 @@ func ResolveDNS(hostname, resolver, ipType string) string { ip, err := res.LookupHost(hostname, dnsType) if err != nil { - if strings.HasSuffix(err.Error(), "NXDOMAIN") { - return "" - } - log.Fatalf(err.Error()) + return "", err } - return ip[0].String() + + return ip[0].String(), nil }