62 lines
1.7 KiB
Go
62 lines
1.7 KiB
Go
// Package hedns implements the Hurricane Electric dynamic DNS update protocol.
|
|
package hedns
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
updateURL = "https://dyn.dns.he.net/nic/update"
|
|
requestTimeout = 15 * time.Second
|
|
)
|
|
|
|
// Update pushes a single hostname -> ip mapping. HE picks the A or AAAA
|
|
// record based on the family of ip.
|
|
func Update(hostname, token string, ip net.IP) error {
|
|
client := http.Client{Timeout: requestTimeout}
|
|
|
|
form := url.Values{
|
|
"hostname": {hostname},
|
|
"password": {token},
|
|
"myip": {ip.String()},
|
|
}
|
|
|
|
resp, err := client.PostForm(updateURL, form)
|
|
if err != nil {
|
|
return fmt.Errorf("updating %s: %w", hostname, err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
|
if err != nil {
|
|
return fmt.Errorf("updating %s: reading response: %w", hostname, err)
|
|
}
|
|
|
|
return interpretResponse(hostname, strings.TrimSpace(string(body)))
|
|
}
|
|
|
|
func interpretResponse(hostname, body string) error {
|
|
switch {
|
|
case strings.HasPrefix(body, "good"), strings.HasPrefix(body, "nochg"):
|
|
return nil
|
|
case body == "badauth":
|
|
return fmt.Errorf("updating %s: authentication failed (bad hostname/token)", hostname)
|
|
case body == "nohost":
|
|
return fmt.Errorf("updating %s: hostname not found on account", hostname)
|
|
case body == "notfqdn":
|
|
return fmt.Errorf("updating %s: hostname is not a valid fully-qualified domain name", hostname)
|
|
case body == "abuse":
|
|
return fmt.Errorf("updating %s: blocked for abuse", hostname)
|
|
case body == "":
|
|
return fmt.Errorf("updating %s: empty response from server", hostname)
|
|
default:
|
|
return fmt.Errorf("updating %s: unexpected response %q", hostname, body)
|
|
}
|
|
}
|