diff --git a/README.md b/README.md index c670669..4768afc 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 * DNSPod ([https://www.dnspod.cn/](https://www.dnspod.cn/)) * HE.net (Hurricane Electric) ([https://dns.he.net/](https://dns.he.net/)) * AliDNS ([https://help.aliyun.com/product/29697.html](https://help.aliyun.com/product/29697.html)) +* DuckDNS ([https://www.duckdns.org](https://www.duckdns.org)) ## Supported Platforms @@ -201,6 +202,29 @@ For AliDNS, you need to provide `AccessKeyID` & `AccessKeySecret` as `email` & ` } ``` +### Config example for DuckDNS + +For DuckDNS, only need to provide the `token`, config 1 default domain & subdomains. + +```json +{ + "provider": "DuckDNS", + "password": "", + "login_token": "3aaaaaaaa-f411-4198-a5dc-8381cac61b87", + "domains": [ + { + "domain_name": "www.duckdns.org", + "sub_domains": [ + "myname" + ] + } + ], + "ip_url": "https://myip.biturl.top", + "interval": 30, + "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/handler/duck/duck_handler.go b/handler/duck/duck_handler.go new file mode 100644 index 0000000..decb618 --- /dev/null +++ b/handler/duck/duck_handler.go @@ -0,0 +1,103 @@ +package duck + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + "runtime/debug" + "time" + + "github.com/TimothyYe/godns" + "golang.org/x/net/proxy" +) + +var ( + // DuckUrl the API address for Duck DNS + DuckUrl = "https://www.duckdns.org/update?domains=%s&token=%s&ip=%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 + } + }() + + var lastIP string + + for { + currentIP, err := godns.GetCurrentIP(handler.Configuration) + + if err != nil { + log.Println("get_currentIP:", err) + continue + } + log.Println("currentIP is:", currentIP) + + //check against locally cached IP, if no change, skip update + if currentIP == lastIP { + log.Printf("IP is the same as cached one. Skip update.\n") + } else { + client := &http.Client{} + + if handler.Configuration.Socks5Proxy != "" { + log.Println("use socks5 proxy:" + handler.Configuration.Socks5Proxy) + dialer, err := proxy.SOCKS5("tcp", handler.Configuration.Socks5Proxy, nil, proxy.Direct) + if err != nil { + log.Println("can't connect to the proxy:", err) + return + } + + httpTransport := &http.Transport{} + client.Transport = httpTransport + httpTransport.Dial = dialer.Dial + } + + for _, subDomain := range domain.SubDomains { + // update IP with HTTP GET request + resp, err := client.Get(fmt.Sprintf(DuckUrl, subDomain, handler.Configuration.LoginToken, currentIP)) + 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 || string(body) != "OK" { + // handle error + log.Print("Failed to update sub domain:", subDomain, err.Error()) + continue + } else { + log.Print("IP updated to:", currentIP) + } + + // Send mail notification if notify is enabled + if handler.Configuration.Notify.Enabled { + log.Print("Sending notification to:", handler.Configuration.Notify.SendTo) + if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil { + log.Println("Failed to send notification") + } + } + } + } + + // 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)) + } +} diff --git a/handler/handler.go b/handler/handler.go index 0dccf33..0b15fae 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -5,6 +5,7 @@ import ( "github.com/TimothyYe/godns/handler/alidns" "github.com/TimothyYe/godns/handler/cloudflare" "github.com/TimothyYe/godns/handler/dnspod" + "github.com/TimothyYe/godns/handler/duck" "github.com/TimothyYe/godns/handler/google" "github.com/TimothyYe/godns/handler/he" ) @@ -30,6 +31,8 @@ func CreateHandler(provider string) IHandler { handler = IHandler(&alidns.Handler{}) case godns.GOOGLE: handler = IHandler(&google.Handler{}) + case godns.DUCK: + handler = IHandler(&duck.Handler{}) } return handler diff --git a/utils.go b/utils.go index d9430ab..1a7278a 100644 --- a/utils.go +++ b/utils.go @@ -44,6 +44,8 @@ const ( ALIDNS = "AliDNS" // GOOGLE for Google Domains GOOGLE = "Google" + // DUCK for Duck DNS + DUCK = "DuckDNS" ) //GetIPFromInterface gets IP address from the specific interface @@ -187,8 +189,12 @@ func CheckSettings(config *Settings) error { if config.Password == "" { return errors.New("password cannot be empty") } + } else if config.Provider == DUCK { + if config.LoginToken == "" { + return errors.New("login token cannot be empty") + } } else { - return errors.New("please provide supported DNS provider: DNSPod/HE") + return errors.New("please provide supported DNS provider: DNSPod/HE/AliDNS/Cloudflare/GoogleDomain/DuckDNS") } return nil