diff options
| author | Mel <mel@rnrd.eu> | 2026-08-20 06:18:05 +0200 |
|---|---|---|
| committer | Mel <mel@rnrd.eu> | 2026-08-20 06:18:05 +0200 |
| commit | b52c860a7b1d3f57abc442bb8c1257f228d49536 (patch) | |
| tree | 524865405ba3b972d684ded62d7ab67547bb9040 /source/source.go | |
| download | ddnnss-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.go | 91 |
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) +} |
