diff --git a/Corefile b/Corefile index c11e180..a44412d 100644 --- a/Corefile +++ b/Corefile @@ -7,6 +7,7 @@ password {$OMADA_PASSWORD} refresh_minutes 1 ignore_startup_errors {$OMADA_IGNORE_STARTUP_ERRORS} + fallthrough ${FALLTHROUGH_ZONES} } forward . {$UPSTREAM_DNS} } \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index dfe38de..866b8ec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ # docker buildx use multiplatform # docker buildx inspect --bootstrap -FROM --platform=$BUILDPLATFORM golang:1.24-bookworm as builder +FROM --platform=$BUILDPLATFORM golang:1.25-bookworm as builder ARG TARGETOS TARGETARCH RUN apt update RUN apt install git curl jq -y @@ -43,4 +43,5 @@ COPY --from=builder /coredns/coredns /coredns COPY Corefile /Corefile EXPOSE 53 53/udp ENV OMADA_IGNORE_STARTUP_ERRORS=FALSE +ENV FALLTHROUGH_ZONES="." ENTRYPOINT ["/coredns"] \ No newline at end of file diff --git a/config.go b/config.go index 6c2fd45..0461fdf 100644 --- a/config.go +++ b/config.go @@ -21,6 +21,7 @@ type config struct { resolve_dhcp_reservations bool // resolve static 'dhcp reservations' stale_record_duration time.Duration // duration to keep serving stale records for clients no longer present in the controller) ignore_startup_errors bool // ignore any errors during the initial zone refresh + fallthrough_zones *[]string // list of fallthrough zones } func parse(c *caddy.Controller) (config config, err error) { @@ -126,6 +127,13 @@ func parse(c *caddy.Controller) (config config, err error) { return config, c.ArgErr() } + case "fallthrough": + fallthroughZones := c.RemainingArgs() + config.fallthrough_zones = &fallthroughZones + if err != nil { + return config, c.ArgErr() + } + default: return config, c.Errf("unknown property: %q", c.Val()) } diff --git a/config_test.go b/config_test.go index b1661b6..45c3ec3 100644 --- a/config_test.go +++ b/config_test.go @@ -22,6 +22,7 @@ func TestConfig(t *testing.T) { resolve_devices true resolve_dhcp_reservations true stale_record_duration 10m + fallthrough }`, false}, // missing required property: controller url diff --git a/corefile-examples/fallthrough/Corefile b/corefile-examples/fallthrough/Corefile new file mode 100644 index 0000000..b71ff28 --- /dev/null +++ b/corefile-examples/fallthrough/Corefile @@ -0,0 +1,12 @@ +. { + health :8080 + omada { + controller_url https://10.0.0.2 + site Home + username coredns-omada + password coredns-omada + refresh_minutes 1 + fallthrough + } + forward . 10.0.0.1 +} \ No newline at end of file diff --git a/docs/configuration.md b/docs/configuration.md index fe53a5b..b48d5c1 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -7,20 +7,20 @@ Example corefiles are located [here](../corefile-examples) ## Omada plugin configuration syntax -| Name | Required | Type | Notes | -|---------------------------|----------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------| -| controller_url | ✅ | string | address of the Omada controller. Include `https://` prefix | -| site | ✅ | string | name of the site from the Omada controller (note this is a regex pattern) | -| username | ✅ | string | Omada controller username | -| password | ✅ | string | Omada controller password | -| refresh_minutes | ❌ | int | How often to refresh the zones (default 1 minute) | -| refresh_login_hours | ❌ | int | How often to refresh the login token (default 24 hours) | -| resolve_clients | ❌ | bool | Whether to resolve client addresses (default true) | -| resolve_devices | ❌ | bool | Whether to resolve device addresses (default true) | -| resolve_dhcp_reservations | ❌ | bool | Whether to resolve device addresses (default true) | +| Name | Required | Type | Notes | +| ------------------------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| controller_url | ✅ | string | address of the Omada controller. Include `https://` prefix | +| site | ✅ | string | name of the site from the Omada controller (note this is a regex pattern) | +| username | ✅ | string | Omada controller username | +| password | ✅ | string | Omada controller password | +| refresh_minutes | ❌ | int | How often to refresh the zones (default 1 minute) | +| refresh_login_hours | ❌ | int | How often to refresh the login token (default 24 hours) | +| resolve_clients | ❌ | bool | Whether to resolve client addresses (default true) | +| resolve_devices | ❌ | bool | Whether to resolve device addresses (default true) | +| resolve_dhcp_reservations | ❌ | bool | Whether to resolve device addresses (default true) | | stale_record_duration | ❌ | duration | How long to keep serving stale records for clients/devices which are no longer present in the Omada controller. Specified in Go time [duration](https://pkg.go.dev/time#ParseDuration) format | -| ignore_startup_errors | ❌ | bool | ignore connection/configuration errors to the omada controller on startup. Set this to true if you want coredns to startup even if unable to connect to omada (default false) | - +| ignore_startup_errors | ❌ | bool | ignore connection/configuration errors to the omada controller on startup. Set this to true if you want coredns to startup even if unable to connect to omada (default false) | +| fallthrough [ZONES...] | ❌ | []string | Whether to enable fallthrough. If fallthrough statement is present but no zone is specified then defaults to all zones, equivilent to:
`fallthrough .` | ## Credentials @@ -30,6 +30,14 @@ For this service you should create a new user in the `Admin` page of the control A single Omada controller can support multiple network sites. This plugin can be configured to use multiple sites via the `site` configuration property (regex). Multiple sites can be specified using the `|` separator like this `SiteA|SiteB|SiteC` or all sites can be selected by setting it to `.*` +## Fallthrough behaviour + +The `fallthrough` option controls the behaviour for records which are not found. If fallthrough is enabled then requests for records which are not found are passed to the next plugin in the chain. If fallthrough is disabled then records which are not found are returned an `NXDOMAIN` response by the coredns_omada plugin and processing stops without being passed down the plugin chain. + +The use case for using fallthrough is to use other plugins to handle queries for the same zone as `coredns_omada` such as the [file](https://coredns.io/plugins/file/) plugin. + +Note: prior to the fallthrough option being implemented, the default behaviour enabled fallthrough. + ## HTTPS Verification This will depend on your network and configuration, but due to the lack of a suitable internal DNS resolution you may need to disable HTTPS verification to the controller, as even if you have a valid certificate on your controller you need a valid DNS record pointing to your controller where coredns is running. diff --git a/docs/getting-started.md b/docs/getting-started.md index 372f823..a1ca01e 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -27,9 +27,9 @@ CoreDNS plugins need to be compiled into CoreDNS, you can follow the [build](bui This guide provides three options on how to run CoreDNS: -- [CoreDNS binary](#coredns-binary) -- [Docker container](#docker) -- [Kubernetes](#kubernetes) + - [CoreDNS binary](#coredns-binary) + - [Docker](#docker) + - [Kubernetes](#kubernetes) ### CoreDNS binary @@ -52,8 +52,13 @@ Note: If you do not have a valid https certificate on your controller then set t * `OMADA_PASSWORD` * `UPSTREAM_DNS` +The pre-built images support these optional environment variables: +* `OMADA_IGNORE_STARTUP_ERRORS` = `true` | `false` +* `FALLTHROUGH_ZONES` - defaults to all zones `.` to maintain previous compatibility. To disable fallthrough completely either set this a a fake zone (e.g `FALLTHROUGH_ZONES=disabled`) or mount a custom Corefile. + Note: If you do not have a valid https certificate on your controller then set the `OMADA_DISABLE_HTTPS_VERIFICATION` environment variable to true + Example docker run command: ``` docker run \ diff --git a/go.mod b/go.mod index 7b4dc98..328fb38 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,14 @@ module github.com/dougbw/coredns_omada -go 1.24.0 +go 1.25.0 require ( - github.com/coredns/caddy v1.1.2-0.20241029205200-8de985351a98 - github.com/coredns/coredns v1.12.3 + github.com/coredns/caddy v1.1.4 + github.com/coredns/coredns v1.14.1 github.com/dougbw/go-omada v0.6.2 - github.com/go-playground/validator/v10 v10.27.0 - github.com/miekg/dns v1.1.68 - github.com/stretchr/testify v1.10.0 + github.com/go-playground/validator/v10 v10.30.1 + github.com/miekg/dns v1.1.72 + github.com/stretchr/testify v1.11.1 ) require ( @@ -17,7 +17,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect - github.com/gabriel-vasile/mimetype v1.4.10 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -28,21 +28,23 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.23.0 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.65.0 // indirect - github.com/prometheus/procfs v0.17.0 // indirect - github.com/quic-go/quic-go v0.54.0 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/procfs v0.19.2 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.59.0 // indirect go.uber.org/mock v0.6.0 // indirect - golang.org/x/crypto v0.41.0 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/net v0.43.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.28.0 // indirect - golang.org/x/tools v0.36.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect - google.golang.org/grpc v1.75.0 // indirect - google.golang.org/protobuf v1.36.8 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/net v0.50.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/tools v0.42.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect + google.golang.org/grpc v1.79.1 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 8ed9751..33cb8a7 100644 --- a/go.sum +++ b/go.sum @@ -4,10 +4,10 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/coredns/caddy v1.1.2-0.20241029205200-8de985351a98 h1:c+Epklw9xk6BZ1OFBPWLA2PcL8QalKvl3if8CP9x8uw= -github.com/coredns/caddy v1.1.2-0.20241029205200-8de985351a98/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4= -github.com/coredns/coredns v1.12.3 h1:jYdwHGOj5tTPA+OPEq8aR5aqWkwu3Mgvaqfy14MiDZA= -github.com/coredns/coredns v1.12.3/go.mod h1:XlzMvgNm4POEDJrNBAHwkvJKKwRjI1s4FSb7cd0zX+Y= +github.com/coredns/caddy v1.1.4 h1:+Lls5xASB0QsA2jpCroCOwpPlb5GjIGlxdjXxdX0XVo= +github.com/coredns/caddy v1.1.4/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4= +github.com/coredns/coredns v1.14.1 h1:U7ZvMsMn3IfXhaiEHKkW0wsCKG4H5dPvWyMeSLhAodM= +github.com/coredns/coredns v1.14.1/go.mod h1:oYbISnKw+U930dyDU+VVJ+VCWpRD/frU7NfHlqeqH7U= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -16,8 +16,8 @@ github.com/dougbw/go-omada v0.6.2 h1:6ZpCnPg4nYzNO/e1eONN4usmwbQKodobV2usOLvxStw github.com/dougbw/go-omada v0.6.2/go.mod h1:RQGmISwLnQjKMKRYM5IlaR1KMI6dilDdE4IRqiJthLI= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= -github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -28,8 +28,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= -github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= @@ -50,8 +50,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA= -github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= +github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= +github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= @@ -59,61 +59,65 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= -github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= -github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= -github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= -github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= -github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= -go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= -go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= -google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= -google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/omada.go b/omada.go index ea37f69..225bbab 100644 --- a/omada.go +++ b/omada.go @@ -6,6 +6,7 @@ import ( "github.com/coredns/coredns/plugin" "github.com/coredns/coredns/plugin/file" + "github.com/coredns/coredns/plugin/pkg/fall" "github.com/coredns/coredns/request" omada "github.com/dougbw/go-omada" @@ -22,6 +23,7 @@ type Omada struct { zMu sync.RWMutex records map[string]DnsRecords Next plugin.Handler + Fall fall.F } func NewOmada(ctx context.Context, url string, u string, p string) (*Omada, error) { @@ -47,7 +49,7 @@ func (o *Omada) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) qtype := state.QType() log.Debugf("query; type: %d, name: %s\n", qtype, qname) - // this plugin can only handle 'A' and 'PTR' queries + // this plugin can only handle 'A', 'PTR' and 'SOA' queries var qzone string switch qtype { case 1: // A @@ -80,7 +82,7 @@ func (o *Omada) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) o.zMu.RUnlock() // no answer - if len(m.Answer) == 0 && result != file.NoData { + if len(m.Answer) == 0 && result != file.NoData && o.Fall.Through(qname) { log.Debugf("-- ❌ answer len: %d, result: %v\n", len(m.Answer), result) return plugin.NextOrFailure(o.Name(), o.Next, ctx, w, r) } @@ -90,15 +92,19 @@ func (o *Omada) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) case file.Success: case file.NoData: case file.NameError: + log.Debugf("-- RcodeNameError") m.Rcode = dns.RcodeNameError case file.Delegation: m.Authoritative = false case file.ServerFailure: - log.Debugf("RcodeServerFailure") + log.Debugf("-- RcodeServerFailure") return dns.RcodeServerFailure, nil } - - w.WriteMsg(m) + err := w.WriteMsg(m) + if err != nil { + log.Debugf("-- error writing message: %v\n", err) + return dns.RcodeServerFailure, err + } return dns.RcodeSuccess, nil } diff --git a/omada_test.go b/omada_test.go index 9d91d73..10524bb 100644 --- a/omada_test.go +++ b/omada_test.go @@ -9,12 +9,22 @@ import ( "github.com/coredns/coredns/plugin/file" "github.com/coredns/coredns/plugin/pkg/dnstest" + "github.com/coredns/coredns/plugin/pkg/fall" "github.com/coredns/coredns/plugin/test" "github.com/coredns/coredns/request" "github.com/miekg/dns" ) +// Note to enable debug logging for the tests +// add the following import: +// +// clog "github.com/coredns/coredns/plugin/pkg/log" +// +// then add the following line inside the test function +// +// clog.D.Set() + func testZones() map[string]*file.Zone { dnsDomain := "omada.test." @@ -53,9 +63,9 @@ func testHandler() test.HandlerFunc { qname := state.Name() m := new(dns.Msg) rcode := dns.RcodeServerFailure - if qname == "example.gov." { // No records match, test fallthrough. + if qname == "fallthrough.omada.test." { // No records match, test fallthrough. m.SetReply(r) - rr := test.A("example.gov. 300 IN A 2.4.6.8") + rr := test.A("fallthrough.omada.test. 300 IN A 2.4.6.8") m.Answer = []dns.RR{rr} m.Authoritative = true rcode = dns.RcodeSuccess @@ -76,12 +86,18 @@ type testCases struct { expectedErr error } -func TestOmada(t *testing.T) { +func TestOmadaWithFallthrough(t *testing.T) { + // clog.D.Set() + + fallZones := []string{"."} + var f fall.F + f.SetZonesFromArgs(fallZones) var testOmada = &Omada{ Next: testHandler(), zoneNames: []string{"omada.test.", ptrZone}, zones: testZones(), + Fall: f, } tests := []testCases{ @@ -108,9 +124,44 @@ func TestOmada(t *testing.T) { wantAnswer: []string{"101.0.168.192.in-addr.arpa. 60 IN PTR client1.omada.test."}, }, { - qname: "omada.test.", - qtype: dns.TypeSOA, - wantAnswer: []string{"omada.test. 300 IN SOA ns.omada.test. hostmaster.omada.test. 1 7200 3600 86400 300"}, + qname: "fallthrough.omada.test.", + qtype: dns.TypeA, + wantAnswer: []string{"fallthrough.omada.test. 300 IN A 2.4.6.8"}, + }, + } + executeTestCases(t, testOmada, tests) +} +func TestOmadaWithoutFallthrough(t *testing.T) { + + // clog.D.Set() + + var f fall.F + var testOmada = &Omada{ + Next: testHandler(), + zoneNames: []string{"omada.test.", ptrZone}, + zones: testZones(), + Fall: f, + } + tests := []testCases{ + { + // expected success, since record exists in zone + qname: "client1.omada.test.", + qtype: dns.TypeA, + wantAnswer: []string{"client1.omada.test. 60 IN A 192.168.0.101"}, + }, + { + // expected NXDOMAIN, since record does not exist in zone and fallthrough is disabled + qname: "client4.omada.test.", + qtype: dns.TypeA, + wantMsgRCode: dns.RcodeNameError, + wantNS: []string{"omada.test.\t300\tIN\tSOA\tns.omada.test. hostmaster.omada.test. 1 7200 3600 86400 300"}, + }, + { + // expected NXDOMAIN, since record does not exist in zone and fallthrough is disabled + qname: "fallthrough.omada.test.", + qtype: dns.TypeA, + wantMsgRCode: dns.RcodeNameError, + wantNS: []string{"omada.test.\t300\tIN\tSOA\tns.omada.test. hostmaster.omada.test. 1 7200 3600 86400 300"}, }, } executeTestCases(t, testOmada, tests) diff --git a/setup.go b/setup.go index 095fa10..31bf2c6 100644 --- a/setup.go +++ b/setup.go @@ -8,6 +8,7 @@ import ( "github.com/coredns/caddy" "github.com/coredns/coredns/core/dnsserver" "github.com/coredns/coredns/plugin" + "github.com/coredns/coredns/plugin/pkg/fall" clog "github.com/coredns/coredns/plugin/pkg/log" ) @@ -31,6 +32,13 @@ func setup(c *caddy.Controller) error { return plugin.Error("omada", err) } o.config = config + var fall fall.F + + // check if fallthrough pointer is nil + if o.config.fallthrough_zones != nil { + fall.SetZonesFromArgs(*o.config.fallthrough_zones) + o.Fall = fall + } if o.config.ignore_startup_errors { go o.controllerInit(ctx) @@ -69,6 +77,11 @@ func (o *Omada) login() error { func (o *Omada) controllerInit(ctx context.Context) error { log.Info("starting initial omada setup...") + if o.config.fallthrough_zones == nil { + log.Debug("fallthrough disabled") + } else { + log.Debug("fallthrough zones: ", o.Fall.Zones) + } const retrySeconds = 15 duration := time.Duration(retrySeconds) * time.Second diff --git a/update_test.go b/update_test.go index 9182bb4..cebb712 100644 --- a/update_test.go +++ b/update_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/coredns/coredns/plugin/pkg/fall" "github.com/miekg/dns" "github.com/stretchr/testify/assert" ) @@ -75,6 +76,11 @@ func TestUpdate(t *testing.T) { testOmada.config.resolve_devices = true testOmada.config.resolve_dhcp_reservations = true testOmada.config.stale_record_duration, _ = time.ParseDuration("5m") + + var fall fall.F + fall.SetZonesFromArgs([]string{}) + testOmada.Fall = fall + var sites []string for s := range testOmada.controller.Sites { sites = append(sites, s)