Skip to content

Commit

Permalink
Finished cross-platform implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
tyler-sommer committed Apr 28, 2017
1 parent dc311d2 commit 568bca2
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 27 deletions.
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# homedns

Updates Linode DNS with your machine's public address.
homedns is a cross-platform utility to update a Linode managed DNS A record
with the system's public IP address.

# Installation

Expand All @@ -15,9 +16,13 @@ go get github.com/veonik/homedns
```
Usage of homedns:
-domain string
DNS Domain Name
DNS Domain name, required
-key string
Linode API key.
Linode API key, required
-name string
DNS A Record Name
DNS A Record name, required
-verbose
Enable verbose logging
-help
Show this help text
```
30 changes: 30 additions & 0 deletions log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"fmt"
"os"
)

func debugf(f string, args ...interface{}) {
if !*verbose {
return
}
fmt.Printf(f, args...)
}

func debugln(s string) {
if !*verbose {
return
}
fmt.Println(s)
}

func fatalf(f string, args ...interface{}) {
fmt.Printf(f, args...)
os.Exit(1)
}

func fatalln(s string) {
fmt.Println(s)
os.Exit(1)
}
156 changes: 133 additions & 23 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,53 +1,163 @@
// Command homedns is a utility to update a Linode managed DNS A record with
// the system's public IP address.
//
// Usage of homedns:
// -domain string
// DNS Domain name, required
// -key string
// Linode API key, required
// -name string
// DNS A Record name, required
// -verbose
// Enable verbose logging
// -help
// Show this help text
package main

import (
"flag"
"log"
"fmt"
"io/ioutil"
"net"
"os/exec"
"net/http"
"regexp"
"strconv"
"strings"

"github.com/timewasted/linode"
"github.com/timewasted/linode/dns"
)

var apiKey = flag.String("key", "", "Linode API key.")
var dnsDomainName = flag.String("domain", "", "DNS Domain Name")
var dnsARecordName = flag.String("name", "", "DNS A Record Name")
var apiKey = flag.String("key", "", "Linode API key, required")
var dnsDomainName = flag.String("domain", "", "DNS Domain name, required")
var dnsARecordName = flag.String("name", "", "DNS A Record name, required")
var verbose = flag.Bool("verbose", false, "Enable verbose logging")

func main() {
var ipv4Matcher = regexp.MustCompile(`([0-9]{0,3}\.[0-9]{0,3}\.[0-9]{0,3}\.[0-9]{0,3})`)

func UpdateDomainResourceTarget(ldns *dns.DNS, r *dns.Resource, target string) error {
params := linode.Parameters{
"DomainID": strconv.Itoa(r.DomainID),
"ResourceID": strconv.Itoa(r.ResourceID),
"Target": target,
}
lin := ldns.ToLinode()
_, err := lin.Request("domain.resource.update", params, nil)

return err
}

func IsPublicIP(ip net.IP) bool {
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
return false
}
p := ip.To4()
if p == nil {
return false
}
switch true {
case p[0] == 10:
return false
case p[0] == 172 && p[1] >= 16 && p[1] <= 31:
return false
case p[0] == 192 && p[1] == 168:
return false
}
return true
}

func GetPublicIP() (net.IP, error) {
resp, err := http.Get("http://checkip.dyndns.org")
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
m := ipv4Matcher.FindAll(body, -1)
if len(m) != 1 {
return nil, fmt.Errorf("couldnt parse response: %s", string(body))
}
ip := string(m[0])
parsed := net.ParseIP(ip)
if parsed == nil {
return nil, fmt.Errorf("couldnt parse IP %s", ip)
}
if !IsPublicIP(parsed) {
return nil, fmt.Errorf("%s is a private IP", ip)
}
return parsed, nil
}

func init() {
flag.Usage = func() {
fmt.Println(`homedns is a utility to update a Linode managed DNS A record with the system's
public IP address.
Usage of homedns:`)
flag.PrintDefaults()
fmt.Println(` -help
Show this help text`)
}
flag.Parse()
var missing []string
if *apiKey == "" {
log.Fatalln("Missing required parameter: key")
missing = append(missing, "key")
}
if *dnsDomainName == "" {
log.Fatalln("Missing required parameter: domain")
missing = append(missing, "domain")
}
if *dnsARecordName == "" {
log.Fatalln("Missing required parameter: name")
missing = append(missing, "name")
}

out, err := exec.Command("dig", "TXT", "+short", "o-o.myaddr.l.google.com", "@ns1.google.com").Output()
if err != nil {
log.Fatalf("Unable to get public IP: %s\n", err.Error())
if len(missing) > 0 {
fatalf("Error: missing required parameters: %s\n", strings.Join(missing, ", "))
}
ip := strings.Trim(string(out), "\"\n")
log.Printf("Public IP: %s\n", ip)
}

publicIP := net.ParseIP(ip)
if publicIP == nil {
log.Fatalf("Invalid Public IP: %s\n", ip)
func main() {
publicIP, err := GetPublicIP()
if err != nil {
fatalf("Error getting public IP: %s\n", err.Error())
}
debugf("Public IP: %s\n", publicIP.String())

l := dns.New(*apiKey)
domain, err := l.GetDomain(*dnsDomainName)
if err != nil {
log.Fatalf("Error getting DNS settings: %s", err.Error())
fatalf("Error getting DNS settings: %s", err.Error())
}
log.Printf("Pointing %s.%s to %s\n", *dnsARecordName, domain.Domain, publicIP.String())
debugf("Pointing %s.%s to %s\n", *dnsARecordName, domain.Domain, publicIP.String())

_, err = l.CreateDomainResourceA(domain.DomainID, *dnsARecordName, publicIP.String(), 300)
recs, err := l.GetResourcesByType(domain.DomainID, "A")
if err != nil {
log.Fatalf("Error updating DNS record: %s", err.Error())
fatalf("Error getting domain records: %s", err.Error())
}

var existing *dns.Resource
for _, rec := range recs {
if rec.Name == *dnsARecordName {
existing = rec
break
}
}
if existing == nil {
_, err = l.CreateDomainResourceA(domain.DomainID, *dnsARecordName, publicIP.String(), 300)
if err != nil {
fatalf("Error creating DNS record: %s", err.Error())
}
debugln("Successfully created DNS record.")

} else if publicIP.String() != existing.Target {
err := UpdateDomainResourceTarget(l, existing, publicIP.String())
if err != nil {
fatalf("Error updating DNS record: %s", err.Error())
}
debugln("Successfully updated DNS record.")

} else {
debugln("Existing DNS record is correct, exiting successfully.")
}
log.Println("Successfully updated DNS record.")
}

0 comments on commit 568bca2

Please sign in to comment.