summaryrefslogtreecommitdiff
path: root/source/source.go
diff options
context:
space:
mode:
authorMel <mel@rnrd.eu>2026-08-20 06:18:05 +0200
committerMel <mel@rnrd.eu>2026-08-20 06:18:05 +0200
commitb52c860a7b1d3f57abc442bb8c1257f228d49536 (patch)
tree524865405ba3b972d684ded62d7ab67547bb9040 /source/source.go
downloadddnnss-b52c860a7b1d3f57abc442bb8c1257f228d49536.tar.zst
ddnnss-b52c860a7b1d3f57abc442bb8c1257f228d49536.zip
ddnnss
Signed-off-by: Mel <mel@rnrd.eu>
Diffstat (limited to 'source/source.go')
-rw-r--r--source/source.go91
1 files changed, 91 insertions, 0 deletions
diff --git a/source/source.go b/source/source.go
new file mode 100644
index 0000000..6fe139c
--- /dev/null
+++ b/source/source.go
@@ -0,0 +1,91 @@
+package source
+
+import (
+ "fmt"
+ "io"
+ "log/slog"
+ "net/http"
+ "net/netip"
+ "strings"
+ "time"
+)
+
+type Kind string
+
+const (
+ // only the simple generic sources for now!
+ KindStatic Kind = "static"
+ KindURL Kind = "url"
+ KindFile Kind = "file"
+)
+
+type Family string
+
+const (
+ IPv4 Family = "ipv4"
+ IPv6 Family = "ipv6"
+)
+
+type Config struct {
+ Kind Kind `json:"kind"`
+ Family Family `json:"family,omitempty"`
+
+ URL string `json:"url,omitempty"`
+ Path string `json:"path,omitempty"`
+ Value string `json:"value,omitempty"`
+}
+
+type Source interface {
+ Supports(Family) bool
+ Lookup(Family) (netip.Addr, error)
+}
+
+func New(log *slog.Logger, cfg Config) (Source, error) {
+ if cfg.Family != IPv4 && cfg.Family != IPv6 {
+ return nil, fmt.Errorf("%s source needs family set to ipv4 or ipv6", cfg.Kind)
+ }
+
+ if cfg.Kind == KindStatic {
+ return newStatic(log, cfg)
+ }
+
+ if cfg.Kind == KindURL {
+ return newURL(log, cfg)
+ }
+
+ if cfg.Kind == KindFile {
+ return newFile(log, cfg)
+ }
+
+ return nil, fmt.Errorf("unknown source kind %q", cfg.Kind)
+}
+
+// parsing function for ipv4 and ipv6 strings
+func parse(raw string, family Family) (netip.Addr, error) {
+ addr, err := netip.ParseAddr(strings.TrimSpace(raw))
+ if err != nil {
+ return netip.Addr{}, err
+ }
+ if addr.Is6() != (family == IPv6) {
+ return netip.Addr{}, fmt.Errorf("%s is not an %s address", addr, family)
+ }
+ return addr, nil
+}
+
+// generic http fetching helper since a lot of sources will need it.
+func fetch(url string, family Family) (netip.Addr, error) {
+ client := http.Client{Timeout: 15 * time.Second}
+ resp, err := client.Get(url)
+ if err != nil {
+ return netip.Addr{}, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ return netip.Addr{}, fmt.Errorf("%s: %s", url, resp.Status)
+ }
+ raw, err := io.ReadAll(io.LimitReader(resp.Body, 256))
+ if err != nil {
+ return netip.Addr{}, err
+ }
+ return parse(string(raw), family)
+}