1
0
mirror of https://github.com/taigrr/godns synced 2025-01-18 04:03:25 -08:00

add AliDNS support

This commit is contained in:
TimothyYe 2019-01-22 20:18:19 +08:00
parent 66187e0269
commit 57b3c4c79d
9 changed files with 287 additions and 13 deletions

View File

@ -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
@ -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
@ -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.
@ -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.

157
handler/alidns/alidns.go Normal file
View File

@ -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))
}

View File

@ -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)
}
}

View File

@ -1,4 +1,4 @@
package handler
package cloudflare
import (
"bytes"

View File

@ -1,4 +1,4 @@
package handler
package cloudflare
import (
"encoding/json"

View File

@ -1,4 +1,4 @@
package handler
package dnspod
import (
"encoding/json"
@ -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"
)
@ -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)

View File

@ -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 {
@ -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

View File

@ -1,4 +1,4 @@
package handler
package he
import (
"fmt"

View File

@ -11,7 +11,7 @@ import (
"strings"
"golang.org/x/net/proxy"
"gopkg.in/gomail.v2"
gomail "gopkg.in/gomail.v2"
)
var (
@ -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
@ -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")
}