Files
2026-09-16 00:18:01 -03:00

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