Skip to content

Commit

Permalink
add AliDNS support
Browse files Browse the repository at this point in the history
  • Loading branch information
TimothyYe committed Jan 22, 2019
1 parent 66187e0 commit 57b3c4c
Show file tree
Hide file tree
Showing 9 changed files with 287 additions and 13 deletions.
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Now I rewrite [DynDNS](https://github.com/TimothyYe/DynDNS) by Golang and call i
* Cloudflare ([https://cloudflare.com](https://cloudflare.com))
* 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))

## Supported Platforms
* Linux
Expand All @@ -52,7 +53,7 @@ And the binary can run well on routers.

* Register and own a domain.

* Domain's nameserver points to [DNSPod](https://www.dnspod.cn/) or [HE.net](https://dns.he.net/) or [Cloudflare](https://www.cloudflare.com/).
* Domain's nameserver points to [DNSPod](https://www.dnspod.cn/) or [HE.net](https://dns.he.net/) or [Cloudflare](https://www.cloudflare.com/) or [AliDNS](https://dc.console.aliyun.com).

## Get it

Expand Down Expand Up @@ -89,7 +90,7 @@ Usage of ./godns:

* Get [config_sample.json](https://github.com/timothyye/godns/blob/master/config_sample.json) from Github.
* Rename it to **config.json**.
* Configure your provider, domain/sub-domain info, username and password, etc.
* Configure your provider, domain/subdomain info, username and password, etc.
* Configure the SMTP options if you want, a mail notification will sent to your mailbox once the IP is changed.
* Save it in the same directory of GoDNS, or use -c=your_conf_path command.

Expand Down Expand Up @@ -136,6 +137,28 @@ For DNSPod, you need to provide email & password, and config all the domains &
"socks5_proxy": ""
}
```
### Config example for AliDNS

For AliDNS, you need to provide `AccessKeyID` & `AccessKeySecret` as `email` & `password`, and config all the domains & subdomains.

```json
{
"provider": "AliDNS",
"email": "AccessKeyID",
"password": "AccessKeySecret",
"login_token": "",
"domains": [{
"domain_name": "example.com",
"sub_domains": ["www","test"]
},{
"domain_name": "example2.com",
"sub_domains": ["www","test"]
}
],
"ip_url": "http://members.3322.org/dyndns/getip",
"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.
Expand Down
157 changes: 157 additions & 0 deletions handler/alidns/alidns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package alidns

import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"sync"
"time"
)

// AliDNS token
type AliDNS struct {
AccessKeyID string
AccessKeySecret string
}

var (
publicParm = map[string]string{
"AccessKeyId": "",
"Format": "JSON",
"Version": "2015-01-09",
"SignatureMethod": "HMAC-SHA1",
"Timestamp": "",
"SignatureVersion": "1.0",
"SignatureNonce": "",
}
baseURL = "http://alidns.aliyuncs.com/"
instance *AliDNS
once sync.Once
)

type domainRecordsResp struct {
RequestID string `json:"RequestId"`
TotalCount int
PageNumber int
PageSize int
DomainRecords domainRecords
}

type domainRecords struct {
Record []DomainRecord
}

// DomainRecord struct
type DomainRecord struct {
DomainName string
RecordID string `json:"RecordId"`
RR string
Type string
Value string
Line string
Priority int
TTL int
Status string
Locked bool
}

func getHTTPBody(url string) ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
if resp.StatusCode == http.StatusOK {
return body, err
}
return nil, fmt.Errorf("Status %d, Error:%s", resp.StatusCode, body)
}

func NewAliDNS(key, secret string) *AliDNS {
once.Do(func() {
instance = &AliDNS{
AccessKeyID: key,
AccessKeySecret: secret,
}
})
return instance
}

// GetDomainRecords gets all the doamin records according to input subdomain key
func (d *AliDNS) GetDomainRecords(domain, rr string) []DomainRecord {
resp := &domainRecordsResp{}
parms := map[string]string{
"Action": "DescribeDomainRecords",
"DomainName": domain,
"RRKeyWord": rr,
}
urlPath := d.genRequestURL(parms)
body, err := getHTTPBody(urlPath)
if err != nil {
fmt.Printf("GetDomainRecords error.%+v\n", err)
} else {
json.Unmarshal(body, resp)
return resp.DomainRecords.Record
}
return nil
}

// UpdateDomainRecord updates domain record
func (d *AliDNS) UpdateDomainRecord(r DomainRecord) error {
parms := map[string]string{
"Action": "UpdateDomainRecord",
"RecordId": r.RecordID,
"RR": r.RR,
"Type": r.Type,
"Value": r.Value,
"TTL": strconv.Itoa(r.TTL),
"Line": r.Line,
}

urlPath := d.genRequestURL(parms)
_, err := getHTTPBody(urlPath)
if err != nil {
fmt.Printf("UpdateDomainRecord error.%+v\n", err)
}
return err
}

func (d *AliDNS) genRequestURL(parms map[string]string) string {
pArr := []string{}
ps := map[string]string{}
for k, v := range publicParm {
ps[k] = v
}
for k, v := range parms {
ps[k] = v
}
now := time.Now().UTC()
ps["AccessKeyId"] = d.AccessKeyID
ps["SignatureNonce"] = strconv.Itoa(int(now.UnixNano()) + rand.Intn(99999))
ps["Timestamp"] = now.Format("2006-01-02T15:04:05Z")

for k, v := range ps {
pArr = append(pArr, fmt.Sprintf("%s=%s", k, v))
}
sort.Strings(pArr)
path := strings.Join(pArr, "&")

s := "GET&%2F&" + url.QueryEscape(path)
s = strings.Replace(s, "%3A", "%253A", -1)
s = strings.Replace(s, "%40", "%2540", -1)
s = strings.Replace(s, "%2A", "%252A", -1)
mac := hmac.New(sha1.New, []byte(d.AccessKeySecret+"&"))

mac.Write([]byte(s))
sign := base64.StdEncoding.EncodeToString(mac.Sum(nil))
return fmt.Sprintf("%s?%s&Signature=%s", baseURL, path, url.QueryEscape(sign))
}
77 changes: 77 additions & 0 deletions handler/alidns/alidns_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package alidns

import (
"fmt"
"log"
"runtime/debug"
"time"

"github.com/TimothyYe/godns"
)

// AliDNSHandler struct
type AliDNSHandler struct {
Configuration *godns.Settings
}

// SetConfiguration pass dns settings and store it to handler instance
func (handler *AliDNSHandler) SetConfiguration(conf *godns.Settings) {
handler.Configuration = conf
}

// DomainLoop the main logic loop
func (handler *AliDNSHandler) 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
aliDNS := NewAliDNS(handler.Configuration.Email, handler.Configuration.Password)

for {
currentIP, err := godns.GetCurrentIP(handler.Configuration)

if err != nil {
log.Println("Failed to get current IP:", 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 {
lastIP = currentIP

for _, subDomain := range domain.SubDomains {
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
records := aliDNS.GetDomainRecords(domain.DomainName, subDomain)
if records == nil || len(records) == 0 {
log.Printf("Cannot get subdomain %s from AliDNS.\r\n", subDomain)
continue
}

records[0].Value = currentIP
if err := aliDNS.UpdateDomainRecord(records[0]); err != nil {
log.Printf("Failed to update IP for subdomain:%s\r\n", subDomain)
continue
} else {
log.Printf("IP updated for subdomain:%s\r\n", subDomain)
}

// Send mail notification if notify is enabled
if handler.Configuration.Notify.Enabled {
log.Print("Sending notification to:", handler.Configuration.Notify.SendTo)
godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP)
}
}
}
// Interval is 5 minutes
log.Printf("Going to sleep, will start next checking in %d minutes...\r\n", godns.INTERVAL)
time.Sleep(time.Minute * godns.INTERVAL)
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package handler
package cloudflare

import (
"bytes"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package handler
package cloudflare

import (
"encoding/json"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package handler
package dnspod

import (
"encoding/json"
Expand All @@ -13,7 +13,7 @@ import (
"time"

"github.com/TimothyYe/godns"
"github.com/bitly/go-simplejson"
simplejson "github.com/bitly/go-simplejson"
"golang.org/x/net/proxy"
)

Expand Down Expand Up @@ -68,7 +68,7 @@ func (handler *DNSPodHandler) DomainLoop(domain *godns.Domain, panicChan chan<-
continue
}

// Continue to check the IP of sub-domain
// Continue to check the IP of subdomain
if len(ip) > 0 && strings.TrimRight(currentIP, "\n") != strings.TrimRight(ip, "\n") {
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
handler.UpdateIP(domainID, subDomainID, subDomain, currentIP)
Expand Down
16 changes: 12 additions & 4 deletions handler/handler.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package handler

import "github.com/TimothyYe/godns"
import (
"github.com/TimothyYe/godns"
"github.com/TimothyYe/godns/handler/alidns"
"github.com/TimothyYe/godns/handler/cloudflare"
"github.com/TimothyYe/godns/handler/dnspod"
"github.com/TimothyYe/godns/handler/he"
)

// IHandler is the interface for all DNS handlers
type IHandler interface {
Expand All @@ -14,11 +20,13 @@ func CreateHandler(provider string) IHandler {

switch provider {
case godns.CLOUDFLARE:
handler = IHandler(&CloudflareHandler{})
handler = IHandler(&cloudflare.CloudflareHandler{})
case godns.DNSPOD:
handler = IHandler(&DNSPodHandler{})
handler = IHandler(&dnspod.DNSPodHandler{})
case godns.HE:
handler = IHandler(&HEHandler{})
handler = IHandler(&he.HEHandler{})
case godns.ALIDNS:
handler = IHandler(&alidns.AliDNSHandler{})
}

return handler
Expand Down
2 changes: 1 addition & 1 deletion handler/he_handler.go → handler/he/he_handler.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package handler
package he

import (
"fmt"
Expand Down
11 changes: 10 additions & 1 deletion utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"strings"

"golang.org/x/net/proxy"
"gopkg.in/gomail.v2"
gomail "gopkg.in/gomail.v2"
)

var (
Expand Down Expand Up @@ -42,6 +42,8 @@ const (
HE = "HE"
// CLOUDFLARE for cloudflare.com
CLOUDFLARE = "Cloudflare"
// ALIDNS for AliDNS
ALIDNS = "AliDNS"
)

//GetIPFromInterface gets IP address from the specific interface
Expand Down Expand Up @@ -178,6 +180,13 @@ func CheckSettings(config *Settings) error {
if config.Password == "" {
return errors.New("password cannot be empty")
}
} else if config.Provider == ALIDNS {
if config.Email == "" {
return errors.New("email cannot be empty")
}
if config.Password == "" {
return errors.New("password cannot be empty")
}
} else {
return errors.New("please provide supported DNS provider: DNSPod/HE")
}
Expand Down

0 comments on commit 57b3c4c

Please sign in to comment.