Skip to content

Commit

Permalink
Support updating IPv6 records on CloudFlare
Browse files Browse the repository at this point in the history
Support updating IPv6 records on CloudFlare

Support updating IPv6 records for fixing issue #44

Known bugs:

Addresses with leading zeros(e.g. 2001:0db8:0000:0000:0000:ff00:0042:8329) and without leading zeros(e.g. 2001:db8:0:0:0:ff00:42:8329) will be treated as different addresses.
Addresses with consecutive sections of zeros(e.g. 2001:0db8:0000:0000:0000:ff00:0042:8329) and without consecutive sections of zeros(e.g. 2001:db8::ff00:42:8329) will be treated as different addresses.
  • Loading branch information
boris1993 authored Jan 23, 2020
1 parent d90ec58 commit 14b876b
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 32 deletions.
7 changes: 7 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Copyright 2018 boris1993

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# dnsupdater
|master| dev |
|:----:|:---:|
|[![Build Status](https://travis-ci.org/boris1993/dnsupdater.svg?branch=master)](https://travis-ci.org/boris1993/dnsupdater)|[![Build Status](https://travis-ci.org/boris1993/dnsupdater.svg?branch=dev)](https://travis-ci.org/boris1993/dnsupdater)|
[![Build Status](https://travis-ci.org/boris1993/dnsupdater.svg?branch=master)](https://travis-ci.org/boris1993/dnsupdater)
![GitHub](https://img.shields.io/github/license/boris1993/dnsupdater)
![GitHub release (latest by date)](https://img.shields.io/github/v/release/boris1993/dnsupdater)
![Total download](https://img.shields.io/github/downloads/boris1993/dnsupdater/total.svg)

Obtain your current external IP address and update to the specified DNS record on CloudFlare

Expand Down
4 changes: 2 additions & 2 deletions alidnsutil/alidnsutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import (
"strings"
)

// ProcessRecords takes the configuration and the current IP address
// ProcessRecords takes the configuration as well as the current IP address
// then check and update each DNS record in Aliyun DNS
func ProcessRecords(config *conf.Config, currentIPAddress string) {
func ProcessRecords(config conf.Config, currentIPAddress string) {
log.Println(len(config.AliDNSRecords), constants.MsgAliDNSRecordsFoundSuffix)

for _, aliDNSRecord := range config.AliDNSRecords {
Expand Down
38 changes: 27 additions & 11 deletions cfutil/cfutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import (
"github.com/boris1993/dnsupdater/model"
)

// ProcessRecords takes the configuration and the current IP address,
// ProcessRecords takes the configuration as well as the current IP address,
// then check and update each DNS record in CloudFlare
func ProcessRecords(config *conf.Config, currentIPAddress string) error {
func ProcessRecords(config conf.Config, currentIPv4Address string, currentIPv6Address string) error {

if config.System.CloudFlareAPIEndpoint == "" {
return errors.New(constants.ErrCloudFlareAPIAddressEmpty)
Expand All @@ -30,6 +30,7 @@ func ProcessRecords(config *conf.Config, currentIPAddress string) error {
if cloudFlareRecord.APIKey == "" ||
cloudFlareRecord.AuthEmail == "" ||
cloudFlareRecord.DomainName == "" ||
cloudFlareRecord.DomainType == "" ||
cloudFlareRecord.ZoneID == "" {
// Print error and skip to next record when bad configuration found
log.Errorln(constants.ErrCloudFlareRecordConfigIncomplete)
Expand All @@ -39,6 +40,15 @@ func ProcessRecords(config *conf.Config, currentIPAddress string) error {
// Prints which record is being processed
log.Println(constants.MsgHeaderDomainProcessing, cloudFlareRecord.DomainName)

var newIpAddress = ""
if cloudFlareRecord.DomainType == "A" {
newIpAddress = currentIPv4Address
} else if cloudFlareRecord.DomainType == "AAAA" {
newIpAddress = currentIPv6Address
} else {
log.Errorln(constants.ErrInvalidDomainType)
}

// Then fetch the IP address of the specified DNS record
id, recordAddress, err := getCFDnsRecordIpAddress(cloudFlareRecord)

Expand All @@ -48,12 +58,12 @@ func ProcessRecords(config *conf.Config, currentIPAddress string) error {
}

// Do nothing when the IP address didn't change.
if currentIPAddress == recordAddress {
if newIpAddress == recordAddress {
log.Println(constants.MsgIPAddrNotChanged)
continue
} else {
// Update the IP address when changed.
status, err := updateCFDNSRecord(id, currentIPAddress, cloudFlareRecord)
status, err := updateCFDNSRecord(id, cloudFlareRecord.DomainType, newIpAddress, cloudFlareRecord)

if err != nil {
log.Errorln(err)
Expand All @@ -73,9 +83,9 @@ func ProcessRecords(config *conf.Config, currentIPAddress string) error {
}

// getCFDnsRecordIpAddress gets the IP address associated with the specified DNS record,
// which is identified by the combination of the record type(hard coded as A type for now) and the domain name.
// which is identified by the combination of the record type, and the domain name.
//
// cloudFlareRecord contains the information which this process needed, and it is coming from the config.yaml.
// cloudFlareRecord contains the information, which are needed by this process, and it is coming from the config.yaml.
//
// The first value returned is the ID of this DNS record,
// the second value returned is the IP address of this record,
Expand All @@ -86,9 +96,15 @@ func getCFDnsRecordIpAddress(cloudFlareRecord conf.CloudFlare) (string, string,
client := &http.Client{}

req, err := http.NewRequest(http.MethodGet,
APIEndpoint+"/zones/"+cloudFlareRecord.ZoneID+"/dns_records?type=A&name="+cloudFlareRecord.DomainName,
APIEndpoint+"/zones/"+cloudFlareRecord.ZoneID+"/dns_records?type="+cloudFlareRecord.DomainType+"&name="+cloudFlareRecord.DomainName,
nil)

if err != nil {
return "", "", err
}

log.Debug("Request URI: \n" + req.URL.String())

req.Header.Add("X-Auth-Email", cloudFlareRecord.AuthEmail)
req.Header.Add("X-Auth-Key", cloudFlareRecord.APIKey)
req.Header.Add("Content-Type", "application/json")
Expand All @@ -102,6 +118,7 @@ func getCFDnsRecordIpAddress(cloudFlareRecord conf.CloudFlare) (string, string,
}

if resp.StatusCode != 200 {
// TODO Maybe return the corresponding error message instead of the error code
return "", "", errors.New(resp.Status)
}

Expand Down Expand Up @@ -147,18 +164,17 @@ func getCFDnsRecordIpAddress(cloudFlareRecord conf.CloudFlare) (string, string,

// updateCFDNSRecord updates the specified DNS record identified by the record ID.
//
// id is the record ID, address is the IP address to be written,
// and cloudFlareRecord contains the information corresponding to the DNS record to be updated.
// id is the record ID, address is the IP address to be written, and cloudFlareRecord contains the information corresponding to the DNS record to be updated.
//
// The return value is the status(true or false) of the update process,
// or an error will be returned if any error occurs.
func updateCFDNSRecord(id string, address string, cloudFlareRecord conf.CloudFlare) (bool, error) {
func updateCFDNSRecord(id string, recordType string, address string, cloudFlareRecord conf.CloudFlare) (bool, error) {
APIEndpoint := conf.Get().System.CloudFlareAPIEndpoint

client := &http.Client{}

updateRecordData := model.UpdateRecordData{}
updateRecordData.RecordType = "A"
updateRecordData.RecordType = recordType
updateRecordData.Name = cloudFlareRecord.DomainName
updateRecordData.Content = address

Expand Down
7 changes: 5 additions & 2 deletions conf/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var once sync.Once
var Debug bool

var Path string
var conf *Config
var conf Config

// Config describes the top-level properties in Config.yaml
type Config struct {
Expand All @@ -28,6 +28,7 @@ type Config struct {
// System describes the System properties in Config.yaml
type System struct {
IPAddrAPI string `yaml:"IPAddrAPI"`
IPv6AddrAPI string `yaml:"IPv6AddrAPI"`
CloudFlareAPIEndpoint string `yaml:"CloudFlareAPIEndpoint"`
}

Expand All @@ -38,6 +39,7 @@ type CloudFlare struct {
ZoneID string `yaml:"ZoneID"`
AuthEmail string `yaml:"AuthEmail"`
DomainName string `yaml:"DomainName"`
DomainType string `yaml:"DomainType"`
}

type AliDNS struct {
Expand All @@ -47,7 +49,7 @@ type AliDNS struct {
DomainName string `yaml:"DomainName"`
}

func Get() *Config {
func Get() Config {
once.Do(func() {
err := initConfig()

Expand Down Expand Up @@ -103,6 +105,7 @@ func printDebugInfo() {
log.Debugf("%10v: %s", "ZoneID", item.ZoneID)
log.Debugf("%10v: %s", "AuthEmail", item.AuthEmail)
log.Debugf("%10v: %s", "DomainName", item.DomainName)
log.Debugf("%10v: %s", "DomainType", item.DomainType)
log.Debugln()
}

Expand Down
5 changes: 4 additions & 1 deletion config.yaml.template
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
---
System:
IPAddrAPI: "https://api.ipify.org/"
IPv6AddrAPI: "https://api6.ipify.org/"
CloudFlareAPIEndpoint: "https://api.cloudflare.com/client/v4"
CloudFlareRecords:
- APIKey: "YOUR_CLOUDFLARE_API_KEY"
ZoneID: "ZONE_ID_OF_YOUR_DOMAIN"
AuthEmail: "EMAIL_FOR_LOGGING_INTO_CLOUDFLARE"
DomainName: "FULL_DOMAIN_YOU_WANT_TO_UPDATE. e.g.:test1.example.com"
DomainType: "Type of this domain. A for IPv4 records, and AAAA for IPv6 records"
- APIKey: "YOUR_CLOUDFLARE_API_KEY"
ZoneID: "ZONE_ID_OF_YOUR_DOMAIN"
AuthEmail: "EMAIL_FOR_LOGGING_INTO_CLOUDFLARE"
DomainName: "FULL_DOMAIN_YOU_WANT_TO_UPDATE. e.g.:test2.example.com"
DomainType: "Type of this domain. A for IPv4 records, and AAAA for IPv6 records"
AliDNSRecords:
- AccessKeyID: "YOUR_ALIYUN_ACCESS_KEY_ID"
AccessKeySecret: "YOUR_ALIYUN_ACCESS_KEY_SECRET"
RegionID: "cn-hangzhou" # Only this region is accepted so DO NOT MODIFY THIS PROPERTY
DomainName: "FULL_DOMAIN_YOU_WANT_TO_UPDATE. e.g.:test3.example.com"
DomainName: "FULL_DOMAIN_YOU_WANT_TO_UPDATE. e.g.:test3.example.com"
6 changes: 4 additions & 2 deletions constants/errormsg.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
package constants

const MsgHeaderDNSRecordUpdateSuccessful = "Successfully updated the DNS record"
const MsgHeaderCurrentIPAddr = "Current IP address is:"
const MsgHeaderCurrentIPAddr = "Current IPv4 address is:"
const MsgHeaderCurrentIPv6Addr = "Current IPv6 address is:"
const MsgHeaderFetchingIPOfDomain = "Fetching the IP address of domain"
const MsgHeaderLoadingConfig = "Loading configuration from"
const MsgHeaderLoadingConfig = "Loading configurations from"
const MsgHeaderDomainProcessing = "Processing"
const MsgCloudFlareRecordsFoundSuffix = "CloudFlare DNS record(s) found"
const MsgAliDNSRecordsFoundSuffix = "Aliyun DNS record(s) found"
Expand All @@ -26,3 +27,4 @@ const ErrIPAddressFetchingAPIEmpty = "API address for fetching current IP addres
const ErrCloudFlareAPIAddressEmpty = "CloudFlare API endpoint address cannot be empty"
const ErrCloudFlareRecordConfigIncomplete = "Incomplete CloudFlare configuration found"
const ErrAliDNSRecordConfigIncomplete = "Incomplete Aliyun DNS configuration found"
const ErrInvalidDomainType = "Invalid domain type. Only A and AAAA records are supported for now."
9 changes: 7 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ module github.com/boris1993/dnsupdater
go 1.12

require (
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190520072130-def1e914efff
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20191104061422-c78ca2ea5e12
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5 // indirect
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c // indirect
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect
github.com/json-iterator/go v1.1.6 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/satori/go.uuid v1.2.0 // indirect
github.com/sirupsen/logrus v1.4.2
github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 // indirect
golang.org/x/sys v0.0.0-20190516110030-61b9204099cb // indirect
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v2 v2.2.2
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190520072130-def1e914efff h1:ojiFYVfdKow1Q6HyWCSdBBFzEqUzKSQzo/ZVJOQqBtQ=
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190520072130-def1e914efff/go.mod h1:0nPXeXAsIm3YH7imFCamfa0u+PueDefehKCQ8dicxmc=
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20191104061422-c78ca2ea5e12 h1:muZ1bS7r8Yav9uNNtRNWzQulwu/sB1uhOG5sXgBHqh0=
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20191104061422-c78ca2ea5e12/go.mod h1:mNZkuqaeM5UCiAdkV4r+lrheu8Q5fe/487bRFrGYZ8A=
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
45 changes: 36 additions & 9 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@ func main() {
var config = conf.Get()

// Fetch the current external IP address.
currentIPAddress, err := getCurrentIPAddress(config)
ipAddress, ipv6Address, err := getCurrentIPAddress(config)

if err != nil {
log.Fatalln(err)
}

// Process CloudFlare records
err = cfutil.ProcessRecords(config, currentIPAddress)
err = cfutil.ProcessRecords(config, ipAddress, ipv6Address)

if err != nil {
log.Errorln(err)
}

alidnsutil.ProcessRecords(config, currentIPAddress)
alidnsutil.ProcessRecords(config, ipAddress)

os.Exit(0)
}
Expand All @@ -53,17 +53,18 @@ func init() {
}

// getCurrentIPAddress returns the external IP address for your network
func getCurrentIPAddress(config *conf.Config) (string, error) {
if config.System.IPAddrAPI == "" {
return "", errors.New(constants.ErrIPAddressFetchingAPIEmpty)
func getCurrentIPAddress(config conf.Config) (string, string, error) {
if config.System.IPAddrAPI == "" || config.System.IPv6AddrAPI == "" {
return "", "", errors.New(constants.ErrIPAddressFetchingAPIEmpty)
}

log.Println(constants.MsgCheckingCurrentIPAddr)

//region fetch your IPv4 address
resp, err := http.Get(config.System.IPAddrAPI)

if err != nil {
return "", err
return "", "", err
}

// Handle errors when closing the HTTP connection
Expand All @@ -78,13 +79,39 @@ func getCurrentIPAddress(config *conf.Config) (string, error) {
body, err := ioutil.ReadAll(resp.Body)

if err != nil {
return "", err
return "", "", err
}

// Body only contains the IP address
ipAddress := string(body)
//endregion

//region fetch your IPv6 address
resp, err = http.Get(config.System.IPv6AddrAPI)

if err != nil {
return "", "", err
}

defer func() {
err := resp.Body.Close()

if err != nil {
log.Errorln(constants.ErrCloseHTTPConnectionFail, err)
}
}()

body, err = ioutil.ReadAll(resp.Body)

if err != nil {
return "", "", err
}

ipv6Address := string(body)
//endregion

log.Println(constants.MsgHeaderCurrentIPAddr, ipAddress)
log.Println(constants.MsgHeaderCurrentIPv6Addr, ipv6Address)

return ipAddress, nil
return ipAddress, ipv6Address, nil
}

0 comments on commit 14b876b

Please sign in to comment.