-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Finished cross-platform implementation.
- Loading branch information
1 parent
dc311d2
commit 568bca2
Showing
3 changed files
with
172 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.") | ||
} |