diff --git a/Dockerfile b/Dockerfile index d5c5142..3aff504 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,15 @@ # Both run as root; file permissions work because config dirs are in the container. # For dnsmasq on the host, use the self-contained publish (scripts/publish-self-contained.sh) # or run the app on the host; see DnsmasqOptions XML doc for permissions and ReloadCommand scope. +# +# Dnsmasq install mode for the test harness: +# - DNSMASQ_VERSION=latest (default): build the latest stable upstream dnsmasq release. +# - DNSMASQ_VERSION= : build that exact upstream release (e.g. 2.91). +# - DNSMASQ_VERSION=distro : use the distro package from apt. +# +# This keeps day-to-day harness use close to upstream while still allowing pinned-version testing. +ARG DNSMASQ_VERSION= FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base WORKDIR /app EXPOSE 8080 @@ -20,11 +28,30 @@ FROM build AS publish RUN dotnet publish "DnsmasqWebUI.csproj" -c Release -o /app/publish /p:UseAppHost=false FROM base AS final +ARG DNSMASQ_VERSION=latest WORKDIR /app ENV ASPNETCORE_URLS=http://+:8080 -# Slow, rarely-changing steps first (stay cached when only .NET code changes) -RUN apt-get update && apt-get install -y --no-install-recommends dnsmasq procps \ - && rm -rf /var/lib/apt/lists/* +# Install dnsmasq either from the distro package or by building an upstream release. +RUN if [ "$DNSMASQ_VERSION" = "distro" ]; then \ + apt-get update && apt-get install -y --no-install-recommends dnsmasq procps \ + && rm -rf /var/lib/apt/lists/*; \ + else \ + apt-get update && apt-get install -y --no-install-recommends procps curl build-essential xz-utils ca-certificates \ + && rm -rf /var/lib/apt/lists/* \ + && if [ "$DNSMASQ_VERSION" = "latest" ]; then \ + DNSMASQ_VERSION="$(curl -fsSL https://thekelleys.org.uk/dnsmasq/ | grep -o 'dnsmasq-[0-9][0-9.]*\.tar\.xz' | sed 's/^dnsmasq-//; s/\.tar\.xz$//' | sort -V | tail -n1)"; \ + fi \ + && test -n "$DNSMASQ_VERSION" \ + && echo "Building dnsmasq $DNSMASQ_VERSION from upstream source" \ + && curl -fsSL "https://thekelleys.org.uk/dnsmasq/dnsmasq-${DNSMASQ_VERSION}.tar.xz" -o /tmp/dnsmasq.tar.xz \ + && tar -C /tmp -xJf /tmp/dnsmasq.tar.xz \ + && make -C /tmp/dnsmasq-${DNSMASQ_VERSION} -j"$(nproc)" \ + && make -C /tmp/dnsmasq-${DNSMASQ_VERSION} install PREFIX=/usr \ + && rm -rf /tmp/dnsmasq-${DNSMASQ_VERSION} /tmp/dnsmasq.tar.xz \ + && apt-get purge -y build-essential curl xz-utils \ + && apt-get autoremove -y --purge \ + && rm -rf /var/lib/apt/lists/*; \ + fi COPY scripts/entrypoint.sh scripts/dnsmasq-status.sh . RUN chmod +x entrypoint.sh dnsmasq-status.sh # .NET publish last - only this layer invalidates when code changes diff --git a/README.md b/README.md index 518dbff..94b96b3 100644 --- a/README.md +++ b/README.md @@ -391,7 +391,7 @@ sudo ./scripts/install.sh --uninstall --purge --system ### scripts/prepare-test-mount.sh -**Purpose:** Prepare the testdata mount and optionally start or stop the Docker test harness (app + dnsmasq + DHCP clients). Syncs a source dir (default: `testdata`) into a mount dir (default: `testdata-mount`), cleans up previous test data, then runs `docker compose -f docker-compose.test.yml up -d` (or only prepares the mount with `--prepare-only`). +**Purpose:** Prepare the testdata mount and optionally start or stop the Docker test harness (app + dnsmasq + DHCP clients). Syncs a source dir (default: `testdata`) into a mount dir (default: `testdata-mount`), cleans up previous test data, then runs `docker compose -f docker-compose.test.yml up -d` (or only prepares the mount with `--prepare-only`). By default the script resolves and uses the **latest stable upstream dnsmasq version**; you can pin a specific version (or use the distro package) for compatibility testing. **Usage:** `./scripts/prepare-test-mount.sh [OPTIONS] [--]` @@ -401,9 +401,11 @@ sudo ./scripts/install.sh --uninstall --purge --system |--------|-------------| | `--source DIR` | Source to copy from (default: `testdata`). | | `--mount DIR` | Target mount directory (default: `testdata-mount`). Compose uses `TESTDATA_MOUNT`; script exports it when you use `--mount`. | -| `--no-clear` | Do not clear mount dir; only sync over existing contents (e.g. preserve leases between runs). | +| `--dnsmasq-version V` | Dnsmasq version for the harness image: `latest` (default), `distro`, or an exact upstream version like `2.91`. | +| `--clear` | Clear the mount dir completely before sync for a clean run. Default: preserve existing contents and sync over them. | | `--prepare-only` | Only prepare the mount; do not run docker compose. | | `--build` | Pass `--build` to docker compose (rebuild images). Default: use existing images. | +| `--no-cache-build` | Run `docker compose build --pull --no-cache` before start. Use to force a fresh image build and refresh the resolved latest dnsmasq version. | | `--no-build` | Do not rebuild (default). | | `--recreate` | Pass `--force-recreate` to docker compose. | | `--stop` | Stop the harness: `docker compose down` (no prepare, no start). | @@ -419,12 +421,22 @@ sudo ./scripts/install.sh --uninstall --purge --system # Rebuild images then start ./scripts/prepare-test-mount.sh --build +# Force a fresh image build with no Docker build cache +./scripts/prepare-test-mount.sh --no-cache-build + +# Rebuild with a specific dnsmasq version +./scripts/prepare-test-mount.sh --dnsmasq-version 2.91 --build + +# Use the distro package instead of an upstream build +./scripts/prepare-test-mount.sh --dnsmasq-version distro --build + # Only prepare the mount; start manually later ./scripts/prepare-test-mount.sh --prepare-only -# Then: TESTDATA_MOUNT=./testdata-mount docker compose -f docker-compose.test.yml up -d +# Then use the version printed by the script, e.g.: +# TESTDATA_MOUNT=./testdata-mount DNSMASQ_VERSION= docker compose -f docker-compose.test.yml up -d -# Preserve mount contents, sync over it, start -./scripts/prepare-test-mount.sh --no-clear +# Preserve mount contents, sync over it, start (default behavior) +./scripts/prepare-test-mount.sh # Stop the harness ./scripts/prepare-test-mount.sh --stop @@ -449,6 +461,21 @@ sudo ./scripts/install.sh --uninstall --purge --system **Usage:** Set `DNSMASQ_CONF` to the path of the dnsmasq config file inside the container (e.g. `/data/dnsmasq-test.conf`). No CLI arguments. +--- + +### scripts/extract-option-help.sh + +**Purpose:** Fetch the dnsmasq man page HTML and extract per-option help fragments into `src/DnsmasqWebUI/wwwroot/option-help/*.html`. The Effective Config UI loads these when the user clicks an option label (tooltip is from code; full help is from these files). Requires Docker (uses a temporary `python:3-slim` container). When adding new options to the UI, add the option key to the script’s `OPTION_KEYS` list (keep in sync with `EffectiveConfigSections` / `DnsmasqOptionTooltips`), then run this script so help is available for the new option (if it exists in the man page). + +**Usage:** `./scripts/extract-option-help.sh [--url URL] [--output-dir DIR]` + +| Option | Description | +|--------|-------------| +| `--url URL` | Man page URL (default: https://thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html). | +| `--output-dir DIR` | Output directory (default: `src/DnsmasqWebUI/wwwroot/option-help`). | + +Run from the repo root. Writes one `.html` file per option that appears in the man page OPTIONS section; options in `OPTION_KEYS` but not in the man page are skipped. + --- diff --git a/agents.md b/agents.md index 172f10e..badba67 100644 --- a/agents.md +++ b/agents.md @@ -17,15 +17,18 @@ From the repo root: This will: -1. Clear `testdata-mount/` (unless you pass `--no-clear`) -2. Sync `testdata/` → `testdata-mount/` -3. Remove any `*dnsmasq-webui*.conf` in the mount so dnsmasq starts clean +1. Sync `testdata/` to `testdata-mount/` (preserving existing contents unless you pass `--clear`) +2. Remove any `*dnsmasq-webui*.conf` in the mount so dnsmasq starts clean +3. Resolve the requested dnsmasq version (`latest` by default) to a concrete value when needed 4. Run `docker compose -f docker-compose.test.yml up -d --build` - **Quick restart (no image rebuild):** `./scripts/prepare-test-mount.sh --no-build` +- **Force a fresh image build:** `./scripts/prepare-test-mount.sh --no-cache-build` +- **Pin dnsmasq version:** `./scripts/prepare-test-mount.sh --dnsmasq-version 2.91 --build` +- **Use distro package:** `./scripts/prepare-test-mount.sh --dnsmasq-version distro --build` - **Prepare mount only (no start):** `./scripts/prepare-test-mount.sh --prepare-only` - Then start manually: `TESTDATA_MOUNT=./testdata-mount docker compose -f docker-compose.test.yml up -d [--build]` -- **Preserve mount (e.g. keep leases):** `./scripts/prepare-test-mount.sh --no-clear --no-build` + Then start manually with the version printed by the script: `TESTDATA_MOUNT=./testdata-mount DNSMASQ_VERSION= docker compose -f docker-compose.test.yml up -d [--build]` +- **Preserve mount (e.g. keep leases):** `./scripts/prepare-test-mount.sh --no-build` (default mount behavior) App is at **http://localhost:8080**. Main config path in the container is `/data/dnsmasq-test.conf`; managed file is `zz-dnsmasq-webui.conf` in the same directory. @@ -49,6 +52,8 @@ Stops the harness and deletes the contents of `testdata-mount/` so the next run - `--source DIR` — Source to sync from (default: `testdata`) - `--mount DIR` — Mount directory (default: `testdata-mount`). The script exports `TESTDATA_MOUNT=./DIR` when you use `--mount`, so compose uses it. +- `--dnsmasq-version V` — Dnsmasq version for the harness image: `latest` (default), `distro`, or an exact upstream version like `2.91`. +- `--no-cache-build` — Force a fresh image build with `docker compose build --pull --no-cache` before start. Example: sync from a custom dir and start: @@ -76,5 +81,12 @@ Build only: `dotnet build` - **testdata/** — Source for the harness mount. Synced to `testdata-mount/` by `prepare-test-mount.sh`. Unit tests read from the copy in the test output dir. See **testdata/README** for the full layout. - **testdata/dnsmasq-test.conf** — Harness main config (`/data/dnsmasq-test.conf`). Richer config: multiple `server=`, `addn-hosts=`, `address=`, `listen-address=`, `conf-dir=/data/dnsmasq.d`. -- **testdata/dnsmasq.d/** — Included configs (01-other.conf, 02-servers.conf, dhcp.conf). App creates `zz-dnsmasq-webui.conf` here when running. +- **testdata/dnsmasq.d/** — Included configs (01-other.conf, 02-servers.conf, 03-more.conf, dhcp.conf). App creates `zz-dnsmasq-webui.conf` here when running. - **testdata/hosts**, **testdata/hosts.extra** — Addn-hosts files so effective config has multiple addn-hosts; UI can show source per path. + +## When adding new effective-config options + +When adding a new dnsmasq option to the Effective Config UI (new key in `DnsmasqConfKeys`, section, kind map, parser/write behavior, tooltip, etc.): + +1. **Option help:** Add the option key (lowercase, e.g. `no-round-robin`) to the `OPTION_KEYS` list in `scripts/extract-option-help.sh` (keep in sync with `EffectiveConfigSections`). Then run `./scripts/extract-option-help.sh` so `wwwroot/option-help/.html` is generated if the option exists in the dnsmasq man page. +2. **Testdata (optional):** To show the option in the UI when using the harness or testdata, add a line to e.g. `testdata/dnsmasq.d/03-more.conf` and update `testdata/README` if needed. diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 4dd636e..4ee3dd5 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -14,6 +14,8 @@ services: app: build: context: . + args: + DNSMASQ_VERSION: ${DNSMASQ_VERSION:-latest} ports: - "8080:8080" cap_add: diff --git a/scripts/extract-option-help.sh b/scripts/extract-option-help.sh index 0213b68..2c77e51 100755 --- a/scripts/extract-option-help.sh +++ b/scripts/extract-option-help.sh @@ -171,6 +171,57 @@ OPTION_KEYS = [ "keep-in-foreground", "no-daemon", "conntrack", + "connmark-allowlist-enable", + "no-round-robin", + "dns-forward-max", + "connmark-allowlist", + "do-0x20-encode", + "caa-record", + "dns-rr", + "synth-domain", + "auth-zone", + "auth-soa", + "auth-sec-servers", + "auth-peer", + "leasequery", + "dhcp-generate-names", + "dhcp-broadcast", + "dhcp-sequential-ip", + "dhcp-ignore-clid", + "bootp-dynamic", + "no-ping", + "script-arp", + "script-on-renewal", + "dhcp-no-override", + "dhcp-alternate-port", + "dhcp-duid", + "dhcp-luascript", + "dhcp-scriptuser", + "dhcp-relay", + "dhcp-circuitid", + "dhcp-remoteid", + "dhcp-subscrid", + "dhcp-proxy", + "tag-if", + "bridge-interface", + "shared-network", + "dhcp-pxe-vendor", + "dhcp-option-pxe", + "dnssec-no-timecheck", + "dnssec-debug", + "add-cpe-id", + "dnssec-timestamp", + "dnssec-limits", + "dumpfile", + "dumpmask", + "use-stale-cache", + "add-mac", + "add-subnet", + "umbrella", + "quiet-dhcp", + "quiet-dhcp6", + "quiet-ra", + "quiet-tftp", ] long_opt_re = re.compile(r"--([a-z][a-z0-9-]*)(?:=[^,\s]*)?", re.IGNORECASE) diff --git a/scripts/prepare-test-mount.sh b/scripts/prepare-test-mount.sh index 258eeec..2993837 100755 --- a/scripts/prepare-test-mount.sh +++ b/scripts/prepare-test-mount.sh @@ -15,8 +15,10 @@ COMPOSE_FILE="docker-compose.test.yml" SOURCE_DIR="" MOUNT_DIR="" +DNSMASQ_VERSION="" PREPARE_ONLY=false BUILD=false +NO_CACHE_BUILD=false RECREATE=false CLEAR=false STOP=false @@ -38,6 +40,9 @@ usage() { echo " --source DIR Source to copy from (default: testdata)" echo " --mount DIR Target mount directory (default: testdata-mount)" echo " Compose uses TESTDATA_MOUNT; script exports it if you use --mount." + echo " --dnsmasq-version V Dnsmasq version for the harness image:" + echo " latest (default), distro, or an exact upstream version like 2.91." + echo " Non-default values require --build or --no-cache-build; script fails with --no-build." echo "" echo "Mount behaviour:" echo " (default) Preserve mount dir; sync source over existing contents." @@ -50,7 +55,10 @@ usage() { echo " Use to inspect or edit the mount before starting containers." echo " --build Pass --build to docker compose (rebuild images before starting)." echo " Use after changing the app or Dockerfile. Default: use existing images." + echo " --no-cache-build Rebuild images with --pull --no-cache before starting." + echo " Use to force a fresh image build and refresh 'latest' dnsmasq." echo " --no-build Do not rebuild (default). Use existing images for a quick restart." + echo " With --no-build, 'latest' is not resolved (no network)." echo " --recreate Pass --force-recreate to docker compose (recreate containers)." echo " Use to ensure fresh container state and mounts." echo "" @@ -72,6 +80,9 @@ usage() { echo " $0 --recreate" echo " Sync, then up -d --force-recreate (fresh containers)." echo "" + echo " $0 --no-cache-build" + echo " Force a fresh image build (no Docker build cache), then start containers." + echo "" echo " $0 --clear" echo " Clear mount, sync testdata, start (clean run, no rebuild)." echo "" @@ -91,6 +102,35 @@ usage() { echo "" echo " $0 --minimal-conf" echo " Use dnsmasq-test-minimal.conf (single file, few options) and start." + echo "" + echo " $0 --dnsmasq-version 2.91 --build" + echo " Rebuild the harness image with dnsmasq 2.91." +} + +fetch_url() { + if command -v curl >/dev/null 2>&1; then + curl -fsSL "$1" + return $? + fi + if command -v wget >/dev/null 2>&1; then + wget -qO- "$1" + return $? + fi + echo "Error: need curl or wget to resolve the latest upstream dnsmasq version." >&2 + return 1 +} + +resolve_dnsmasq_version() { + if [ "$DNSMASQ_VERSION" != "latest" ]; then + return 0 + fi + + RESOLVED_VERSION="$(fetch_url "https://thekelleys.org.uk/dnsmasq/" | sed -n 's/.*LATEST_IS_\([0-9][0-9.]*\).*/\1/p' | head -n1)" + if [ -z "$RESOLVED_VERSION" ]; then + echo "Error: could not resolve the latest upstream dnsmasq version." >&2 + exit 1 + fi + DNSMASQ_VERSION="$RESOLVED_VERSION" } while [ $# -gt 0 ]; do @@ -111,6 +151,12 @@ while [ $# -gt 0 ]; do MOUNT_DIR="$1" shift ;; + --dnsmasq-version) + shift + [ $# -gt 0 ] || { echo "Error: --dnsmasq-version requires VERSION" >&2; exit 1; } + DNSMASQ_VERSION="$1" + shift + ;; --clear) CLEAR=true shift @@ -123,8 +169,14 @@ while [ $# -gt 0 ]; do BUILD=true shift ;; + --no-cache-build) + NO_CACHE_BUILD=true + BUILD=true + shift + ;; --no-build) BUILD=false + NO_CACHE_BUILD=false shift ;; --recreate) @@ -170,6 +222,7 @@ cd "$REPO_ROOT" : "${SOURCE_DIR:=testdata}" : "${MOUNT_DIR:=testdata-mount}" +: "${DNSMASQ_VERSION:=latest}" # Stop and/or tidy: no prepare, no start if [ "$STOP" = true ] || [ "$TIDY" = true ]; then @@ -192,6 +245,17 @@ if [ ! -d "$SOURCE_DIR" ]; then exit 1 fi +# Resolve "latest" only when we will build; avoid network when doing a no-build run. +WILL_BUILD=false +[ "$BUILD" = true ] || [ "$NO_CACHE_BUILD" = true ] && WILL_BUILD=true +if [ "$WILL_BUILD" = false ] && [ "$DNSMASQ_VERSION" != "latest" ] && [ "$DNSMASQ_VERSION" != "distro" ]; then + echo "Error: --dnsmasq-version $DNSMASQ_VERSION has no effect without --build or --no-cache-build. Add --build or omit --dnsmasq-version for a no-build run." >&2 + exit 1 +fi +if [ "$WILL_BUILD" = true ]; then + resolve_dnsmasq_version +fi + # Take the harness down so all containers (including one-shot DHCP clients) are recreated on up. echo "Stopping test harness: docker compose -f $COMPOSE_FILE down" docker compose -f "$COMPOSE_FILE" down @@ -220,9 +284,14 @@ if [ "$PREPARE_ONLY" = true ]; then /*) MOUNT_EXPORT="$MOUNT_DIR" ;; *) MOUNT_EXPORT="./$MOUNT_DIR" ;; esac - START_CMD="TESTDATA_MOUNT=$MOUNT_EXPORT docker compose -f $COMPOSE_FILE up -d [--build]" + START_PREFIX="TESTDATA_MOUNT=$MOUNT_EXPORT DNSMASQ_VERSION=$DNSMASQ_VERSION" if [ "$MINIMAL_CONF" = true ]; then - START_CMD="TESTDATA_MOUNT=$MOUNT_EXPORT TEST_DNSMASQ_CONF=/data/dnsmasq-test-minimal.conf docker compose -f $COMPOSE_FILE up -d [--build]" + START_PREFIX="$START_PREFIX TEST_DNSMASQ_CONF=/data/dnsmasq-test-minimal.conf" + fi + if [ "$NO_CACHE_BUILD" = true ]; then + START_CMD="$START_PREFIX docker compose -f $COMPOSE_FILE build --pull --no-cache && $START_PREFIX docker compose -f $COMPOSE_FILE up -d" + else + START_CMD="$START_PREFIX docker compose -f $COMPOSE_FILE up -d [--build]" fi echo "To start the harness: $START_CMD" exit 0 @@ -234,13 +303,30 @@ case "$MOUNT_DIR" in *) export TESTDATA_MOUNT="./$MOUNT_DIR" ;; esac +export DNSMASQ_VERSION + +case "$DNSMASQ_VERSION" in + distro) + echo "Using distro-packaged dnsmasq for the harness image." + ;; + *) + echo "Using dnsmasq $DNSMASQ_VERSION for the harness image." + ;; +esac + if [ "$MINIMAL_CONF" = true ]; then export TEST_DNSMASQ_CONF="/data/dnsmasq-test-minimal.conf" echo "Using minimal config: $TEST_DNSMASQ_CONF" fi +if [ "$NO_CACHE_BUILD" = true ]; then + BUILD_CMD="docker compose -f $COMPOSE_FILE build --pull --no-cache" + echo "Running: $BUILD_CMD" + $BUILD_CMD +fi + COMPOSE_CMD="docker compose -f $COMPOSE_FILE up -d" -if [ "$BUILD" = true ]; then +if [ "$BUILD" = true ] && [ "$NO_CACHE_BUILD" != true ]; then COMPOSE_CMD="$COMPOSE_CMD --build" fi if [ "$RECREATE" = true ]; then diff --git a/src/DnsmasqWebUI.Tests/DnsmasqConfIncludeParserNewOptionsTests.cs b/src/DnsmasqWebUI.Tests/DnsmasqConfIncludeParserNewOptionsTests.cs index 78345cd..b665b94 100644 --- a/src/DnsmasqWebUI.Tests/DnsmasqConfIncludeParserNewOptionsTests.cs +++ b/src/DnsmasqWebUI.Tests/DnsmasqConfIncludeParserNewOptionsTests.cs @@ -79,6 +79,114 @@ public class DnsmasqConfIncludeParserNewOptionsTests WriteFlagAndAssertTrue(DnsmasqConfKeys.ProxyDnssec); } + [Fact] + public void GetFlagFromConfigFiles_ConnmarkAllowlistEnable_WhenPresent_ReturnsTrue() + { + WriteFlagAndAssertTrue(DnsmasqConfKeys.ConnmarkAllowlistEnable); + } + + [Fact] + public void GetFlagFromConfigFiles_NoRoundRobin_WhenPresent_ReturnsTrue() + { + WriteFlagAndAssertTrue(DnsmasqConfKeys.NoRoundRobin); + } + + [Fact] + public void GetFlagFromConfigFiles_DnssecNoTimecheck_WhenPresent_ReturnsTrue() + { + WriteFlagAndAssertTrue(DnsmasqConfKeys.DnssecNoTimecheck); + } + + [Fact] + public void GetFlagFromConfigFiles_DnssecDebug_WhenPresent_ReturnsTrue() + { + WriteFlagAndAssertTrue(DnsmasqConfKeys.DnssecDebug); + } + + [Fact] + public void GetFlagFromConfigFiles_Leasequery_WhenPresent_ReturnsTrue() + { + WriteFlagAndAssertTrue(DnsmasqConfKeys.Leasequery); + } + + [Fact] + public void GetFlagFromConfigFiles_QuietDhcp_WhenPresent_ReturnsTrue() + { + WriteFlagAndAssertTrue(DnsmasqConfKeys.QuietDhcp); + } + + [Fact] + public void GetFlagFromConfigFiles_QuietTftp_WhenPresent_ReturnsTrue() + { + WriteFlagAndAssertTrue(DnsmasqConfKeys.QuietTftp); + } + + [Fact] + public void GetFlagFromConfigFiles_DhcpGenerateNames_WhenPresent_ReturnsTrue() + { + WriteFlagAndAssertTrue(DnsmasqConfKeys.DhcpGenerateNames); + } + + [Fact] + public void GetFlagFromConfigFiles_DhcpBroadcast_WhenPresent_ReturnsTrue() + { + WriteFlagAndAssertTrue(DnsmasqConfKeys.DhcpBroadcast); + } + + [Fact] + public void GetFlagFromConfigFiles_DhcpSequentialIp_WhenPresent_ReturnsTrue() + { + WriteFlagAndAssertTrue(DnsmasqConfKeys.DhcpSequentialIp); + } + + [Fact] + public void GetFlagFromConfigFiles_DhcpIgnoreClid_WhenPresent_ReturnsTrue() + { + WriteFlagAndAssertTrue(DnsmasqConfKeys.DhcpIgnoreClid); + } + + [Fact] + public void GetFlagFromConfigFiles_BootpDynamic_WhenPresent_ReturnsTrue() + { + WriteFlagAndAssertTrue(DnsmasqConfKeys.BootpDynamic); + } + + [Fact] + public void GetFlagFromConfigFiles_NoPing_WhenPresent_ReturnsTrue() + { + WriteFlagAndAssertTrue(DnsmasqConfKeys.NoPing); + } + + [Fact] + public void GetFlagFromConfigFiles_ScriptArp_WhenPresent_ReturnsTrue() + { + WriteFlagAndAssertTrue(DnsmasqConfKeys.ScriptArp); + } + + [Fact] + public void GetFlagFromConfigFiles_ScriptOnRenewal_WhenPresent_ReturnsTrue() + { + WriteFlagAndAssertTrue(DnsmasqConfKeys.ScriptOnRenewal); + } + + [Fact] + public void GetFlagFromConfigFiles_DhcpNoOverride_WhenPresent_ReturnsTrue() + { + WriteFlagAndAssertTrue(DnsmasqConfKeys.DhcpNoOverride); + } + + [Fact] + public void GetFlagFromConfigFiles_QuietDhcp6_WhenPresent_ReturnsTrue() + { + WriteFlagAndAssertTrue(DnsmasqConfKeys.QuietDhcp6); + } + + [Fact] + public void GetFlagFromConfigFiles_QuietRa_WhenPresent_ReturnsTrue() + { + WriteFlagAndAssertTrue(DnsmasqConfKeys.QuietRa); + } + // --- Multi-value (6): key=value line(s) → GetMultiValue returns value(s) --- [Fact] @@ -258,16 +366,27 @@ public class DnsmasqConfIncludeParserNewOptionsTests } [Fact] - public void GetLastValueFromConfigFiles_Conntrack_WhenSet_ReturnsValue() + public void GetFlagFromConfigFiles_Conntrack_WhenPresent_ReturnsTrue() { var dir = CreateTempDir(); var conf = Path.Combine(dir, "dnsmasq.conf"); - var value = "42"; try { - File.WriteAllText(conf, $"conntrack={value}\n"); - var (result, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(new[] { conf }, DnsmasqConfKeys.Conntrack); - Assert.Equal(value, result); + File.WriteAllText(conf, "conntrack\n"); + Assert.True(DnsmasqConfIncludeParser.GetFlagFromConfigFiles(new[] { conf }, DnsmasqConfKeys.Conntrack)); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void GetFlagFromConfigFiles_Conntrack_WhenAbsent_ReturnsFalse() + { + var dir = CreateTempDir(); + var conf = Path.Combine(dir, "dnsmasq.conf"); + try + { + File.WriteAllText(conf, "port=53\n"); + Assert.False(DnsmasqConfIncludeParser.GetFlagFromConfigFiles(new[] { conf }, DnsmasqConfKeys.Conntrack)); } finally { Directory.Delete(dir, recursive: true); } } diff --git a/src/DnsmasqWebUI.Tests/DnsmasqConfigServiceApplyChangesTests.cs b/src/DnsmasqWebUI.Tests/DnsmasqConfigServiceApplyChangesTests.cs new file mode 100644 index 0000000..5321def --- /dev/null +++ b/src/DnsmasqWebUI.Tests/DnsmasqConfigServiceApplyChangesTests.cs @@ -0,0 +1,160 @@ +using DnsmasqWebUI.Infrastructure.Helpers.Config; +using DnsmasqWebUI.Infrastructure.Services.Dnsmasq.Config; +using DnsmasqWebUI.Models.Config; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; + +namespace DnsmasqWebUI.Tests; + +/// +/// Save-path tests for DnsmasqConfigService.ApplyEffectiveConfigChangesAsync. +/// Verifies that pending changes produce the correct managed config lines. +/// +public class DnsmasqConfigServiceApplyChangesTests +{ + [Fact] + public async Task ApplyEffectiveConfigChangesAsync_ConntrackTrue_WritesBareFlagLine() + { + var dir = Path.Combine(Path.GetTempPath(), "dnsmasq-apply-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(dir); + var mainPath = Path.Combine(dir, "dnsmasq.conf"); + var managedName = "zz-managed.conf"; + var managedPath = Path.Combine(dir, managedName); + ConfigSetCache? cache = null; + try + { + File.WriteAllText(mainPath, "port=53\n"); + var options = Options.Create(new DnsmasqOptions { MainConfigPath = mainPath, ManagedFileName = managedName }); + cache = new ConfigSetCache(options, NullLogger.Instance); + var setService = new DnsmasqConfigSetService(cache); + var configService = new DnsmasqConfigService(setService, cache, NullLogger.Instance); + + var changes = new List + { + new(EffectiveConfigSections.SectionResolver, DnsmasqConfKeys.Conntrack, false, true, null) + }; + await configService.ApplyEffectiveConfigChangesAsync(changes); + + Assert.True(File.Exists(managedPath)); + var content = await File.ReadAllTextAsync(managedPath); + var lines = content.TrimEnd().Split('\n').Select(l => l.Trim()).Where(l => l.Length > 0).ToList(); + Assert.Contains(lines, l => l == "conntrack"); + } + finally + { + cache?.Dispose(); + if (Directory.Exists(dir)) + Directory.Delete(dir, recursive: true); + } + } + + [Fact] + public async Task ApplyEffectiveConfigChangesAsync_ConntrackFalse_RemovesFlagLine() + { + var dir = Path.Combine(Path.GetTempPath(), "dnsmasq-apply-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(dir); + var mainPath = Path.Combine(dir, "dnsmasq.conf"); + var managedName = "zz-managed.conf"; + var managedPath = Path.Combine(dir, managedName); + ConfigSetCache? cache = null; + try + { + File.WriteAllText(mainPath, "port=53\n"); + File.WriteAllText(managedPath, "conntrack\n"); + var options = Options.Create(new DnsmasqOptions { MainConfigPath = mainPath, ManagedFileName = managedName }); + cache = new ConfigSetCache(options, NullLogger.Instance); + cache.Invalidate(); + var setService = new DnsmasqConfigSetService(cache); + var configService = new DnsmasqConfigService(setService, cache, NullLogger.Instance); + + var changes = new List + { + new(EffectiveConfigSections.SectionResolver, DnsmasqConfKeys.Conntrack, true, false, null) + }; + await configService.ApplyEffectiveConfigChangesAsync(changes); + + var content = await File.ReadAllTextAsync(managedPath); + var lines = content.TrimEnd().Split('\n').Select(l => l.Trim()).Where(l => l.Length > 0).ToList(); + Assert.DoesNotContain(lines, l => l == "conntrack" || l.StartsWith("conntrack=", StringComparison.Ordinal)); + } + finally + { + cache?.Dispose(); + if (Directory.Exists(dir)) + Directory.Delete(dir, recursive: true); + } + } + + [Fact] + public async Task ApplyEffectiveConfigChangesAsync_KeyOnlyOrValue_KeyOnly_WritesBareKey() + { + var dir = Path.Combine(Path.GetTempPath(), "dnsmasq-apply-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(dir); + var mainPath = Path.Combine(dir, "dnsmasq.conf"); + var managedName = "zz-managed.conf"; + var managedPath = Path.Combine(dir, managedName); + ConfigSetCache? cache = null; + try + { + File.WriteAllText(mainPath, "port=53\n"); + var options = Options.Create(new DnsmasqOptions { MainConfigPath = mainPath, ManagedFileName = managedName }); + cache = new ConfigSetCache(options, NullLogger.Instance); + var setService = new DnsmasqConfigSetService(cache); + var configService = new DnsmasqConfigService(setService, cache, NullLogger.Instance); + + var changes = new List + { + new(EffectiveConfigSections.SectionCache, DnsmasqConfKeys.UseStaleCache, null, "", null) + }; + await configService.ApplyEffectiveConfigChangesAsync(changes); + + Assert.True(File.Exists(managedPath)); + var content = await File.ReadAllTextAsync(managedPath); + Assert.Contains("use-stale-cache", content); + Assert.DoesNotContain("use-stale-cache=", content); + } + finally + { + cache?.Dispose(); + if (Directory.Exists(dir)) + Directory.Delete(dir, recursive: true); + } + } + + [Fact] + public async Task ApplyEffectiveConfigChangesAsync_InversePair_Enabled_WritesDo0x20Encode() + { + var dir = Path.Combine(Path.GetTempPath(), "dnsmasq-apply-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(dir); + var mainPath = Path.Combine(dir, "dnsmasq.conf"); + var managedName = "zz-managed.conf"; + var managedPath = Path.Combine(dir, managedName); + ConfigSetCache? cache = null; + try + { + File.WriteAllText(mainPath, "port=53\n"); + var options = Options.Create(new DnsmasqOptions { MainConfigPath = mainPath, ManagedFileName = managedName }); + cache = new ConfigSetCache(options, NullLogger.Instance); + var setService = new DnsmasqConfigSetService(cache); + var configService = new DnsmasqConfigService(setService, cache, NullLogger.Instance); + + var changes = new List + { + new(EffectiveConfigSections.SectionResolver, DnsmasqConfKeys.Do0x20Encode, ExplicitToggleState.Default, ExplicitToggleState.Enabled, null) + }; + await configService.ApplyEffectiveConfigChangesAsync(changes); + + Assert.True(File.Exists(managedPath)); + var content = await File.ReadAllTextAsync(managedPath); + Assert.Contains("do-0x20-encode", content); + Assert.DoesNotContain("no-0x20-encode", content); + } + finally + { + cache?.Dispose(); + if (Directory.Exists(dir)) + Directory.Delete(dir, recursive: true); + } + } +} diff --git a/src/DnsmasqWebUI.Tests/EffectiveDnsmasqConfigTests.cs b/src/DnsmasqWebUI.Tests/EffectiveDnsmasqConfigTests.cs index 315d067..fc4032f 100644 --- a/src/DnsmasqWebUI.Tests/EffectiveDnsmasqConfigTests.cs +++ b/src/DnsmasqWebUI.Tests/EffectiveDnsmasqConfigTests.cs @@ -54,6 +54,23 @@ public class EffectiveDnsmasqConfigTests DhcpHostsfilePaths: Array.Empty(), DhcpOptsfilePaths: Array.Empty(), DhcpHostsdirPaths: Array.Empty(), + ConnmarkAllowlistValues: Array.Empty(), + CaaRecordValues: Array.Empty(), + DnsRrValues: Array.Empty(), + SynthDomainValues: Array.Empty(), + AuthZoneValues: Array.Empty(), + AuthSoaValues: Array.Empty(), + AuthSecServersValues: Array.Empty(), + AuthPeerValues: Array.Empty(), + DhcpRelayValues: Array.Empty(), + DhcpCircuitidValues: Array.Empty(), + DhcpRemoteidValues: Array.Empty(), + DhcpSubscridValues: Array.Empty(), + DhcpProxyValues: Array.Empty(), + TagIfValues: Array.Empty(), + BridgeInterfaceValues: Array.Empty(), + SharedNetworkValues: Array.Empty(), + DhcpOptionPxeValues: Array.Empty(), ExpandHosts: false, BogusPriv: false, StrictOrder: false, @@ -90,6 +107,24 @@ public class EffectiveDnsmasqConfigTests KeepInForeground: false, NoDaemon: false, ProxyDnssec: false, + ConnmarkAllowlistEnable: false, + NoRoundRobin: false, + DnssecNoTimecheck: false, + DnssecDebug: false, + Leasequery: false, + DhcpGenerateNames: false, + DhcpBroadcast: false, + DhcpSequentialIp: false, + DhcpIgnoreClid: false, + BootpDynamic: false, + NoPing: false, + ScriptArp: false, + ScriptOnRenewal: false, + DhcpNoOverride: false, + QuietDhcp: false, + QuietDhcp6: false, + QuietRa: false, + QuietTftp: false, DhcpLeaseFilePath: null, CacheSize: null, Port: null, @@ -120,6 +155,22 @@ public class EffectiveDnsmasqConfigTests FastDnsRetry: null, DhcpScriptPath: null, MxTarget: null, + DnsForwardMax: null, + DumpfilePath: null, + Dumpmask: null, + AddCpeId: null, + DnssecTimestamp: null, + DnssecLimits: null, + DhcpAlternatePort: null, + DhcpDuid: null, + DhcpLuascriptPath: null, + DhcpScriptuser: null, + DhcpPxeVendor: null, + UseStaleCache: null, + AddMac: null, + AddSubnet: null, + Umbrella: null, + Do0x20EncodeState: ExplicitToggleState.Default, Conntrack: false ); diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/Status/StatusSection.razor b/src/DnsmasqWebUI/Components/Dnsmasq/Status/StatusSection.razor index 3238f0d..fe84236 100644 --- a/src/DnsmasqWebUI/Components/Dnsmasq/Status/StatusSection.razor +++ b/src/DnsmasqWebUI/Components/Dnsmasq/Status/StatusSection.razor @@ -24,7 +24,21 @@ {
@_status.StatusShowOutput
} -
+
+ @if (ReloadCommandConfigured && OnReloadRequested.HasDelegate) + { + + }
} diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/Status/StatusSection.razor.cs b/src/DnsmasqWebUI/Components/Dnsmasq/Status/StatusSection.razor.cs index 99cc96d..bbe6c16 100644 --- a/src/DnsmasqWebUI/Components/Dnsmasq/Status/StatusSection.razor.cs +++ b/src/DnsmasqWebUI/Components/Dnsmasq/Status/StatusSection.razor.cs @@ -19,6 +19,11 @@ public partial class StatusSection : IDisposable [Parameter] public bool IsReloading { get; set; } + /// When set and reload is configured, show a "Reload config" button next to Refresh. + [Parameter] public EventCallback OnReloadRequested { get; set; } + + [Parameter] public bool ReloadCommandConfigured { get; set; } + [Parameter] public EventCallback OnOpenSettings { get; set; } [Inject] private IStatusClient StatusClient { get; set; } = null!; @@ -55,6 +60,8 @@ public partial class StatusSection : IDisposable private static readonly TimeSpan MinUpdatingDuration = TimeSpan.FromSeconds(1); + private Task InvokeReloadAsync() => OnReloadRequested.InvokeAsync(); + private async Task RefreshAsync() { var started = DateTime.UtcNow; diff --git a/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSection.razor b/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSection.razor index 06979dc..f0aae32 100644 --- a/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSection.razor +++ b/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSection.razor @@ -18,7 +18,7 @@ } @@ -28,7 +28,7 @@ + OnClose="CloseSaveModal" OnRevertedNotify="OnRevertedFromSaveModal" OnSaveCompleted="OnSaveCompleted" OnContinueEditingAfterSavedFailure="ContinueEditingAfterSavedFailureAsync" OnCloseAndEndEdit="CloseSaveModalAndEndEdit" />
} @@ -42,9 +42,6 @@ private HashSet _openPanels = new(); private bool _showSaveModal; - private int _validationErrorCount => Session.GetValidationSummary().Count(i => i.Severity == FieldIssueSeverity.Error); - private int _validationWarningCount => Session.GetValidationSummary().Count(i => i.Severity == FieldIssueSeverity.Warning); - private string? _optionHelpKey; private string? _optionHelpAnchorId; private string _optionHelpTitle = "Option help"; @@ -199,6 +196,12 @@ Session.SetCrossOptionIssues(issues); } + private void OnRevertedFromSaveModal() + { + RunCrossOptionEvaluator(); + StateHasChanged(); + } + private void OpenSaveModal() { if (Session.PendingChanges.Count == 0) return; diff --git a/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSection.razor.css b/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSection.razor.css index 56e7222..f38ce53 100644 --- a/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSection.razor.css +++ b/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSection.razor.css @@ -187,11 +187,23 @@ gap: 0.25rem; } -/* Edit wrapper (checkbox/input) shrinks to content so the editable badge stays on the same line */ +/* When a field is active: value + badges in one block so any click inside (checkbox, input, ok/cancel) doesn't deactivate */ +::deep .ec-field-value-when-editing { + display: inline-flex; + flex-wrap: wrap; + align-items: center; + gap: 0.25rem; + min-width: 0; + max-width: 100%; +} + +/* Edit wrapper (checkbox/input) shrinks to content */ ::deep .ec-field-edit-wrapper { - flex: 0 0 auto; + flex: 0 1 auto; display: inline-flex; align-items: center; + min-width: 0; + max-width: 100%; } /* Input/select in value area can shrink so the editable badge stays on the same line */ @@ -253,6 +265,21 @@ line-height: 1; } +/* Only when field is in edit mode: push ok/cancel/revert to end and allow them to wrap first on narrow widths */ +::deep .ec-field-value-when-editing > .status-badge-inline { + flex: 0 0 auto; + white-space: nowrap; + margin-left: auto; +} + +@media (max-width: 991.98px) { + ::deep .ec-field-value-when-editing > .status-badge-inline { + flex-basis: 100%; + margin-left: 0; + margin-top: 0.15rem; + } +} + @media (max-width: 767.98px) { ::deep .ec-section-header { padding-left: 0.75rem; diff --git a/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigToolbar.razor b/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigToolbar.razor index 7614d7f..fd88180 100644 --- a/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigToolbar.razor +++ b/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigToolbar.razor @@ -35,12 +35,24 @@ @if (ValidationErrorCount > 0) { - errors: @ValidationErrorCount + + @ValidationErrorCount error(s) + } @if (ValidationErrorCount > 0 && ValidationWarningCount > 0) { · } @if (ValidationWarningCount > 0) { - warnings: @ValidationWarningCount + + @ValidationWarningCount warning(s) + } } @@ -60,8 +72,11 @@ [Parameter] public EventCallback SearchTermChanged { get; set; } [Parameter] public bool IsEditMode { get; set; } [Parameter] public int PendingChangesCount { get; set; } - [Parameter] public int ValidationErrorCount { get; set; } - [Parameter] public int ValidationWarningCount { get; set; } + [Parameter] public IReadOnlyList? ValidationSummary { get; set; } + private int ValidationErrorCount => ValidationSummary?.Count(i => i.Severity == FieldIssueSeverity.Error) ?? 0; + private int ValidationWarningCount => ValidationSummary?.Count(i => i.Severity == FieldIssueSeverity.Warning) ?? 0; + private string ValidationErrorTooltip => ValidationSummary == null ? "" : string.Join("\n", ValidationSummary.Where(i => i.Severity == FieldIssueSeverity.Error).Select(i => i.Message)); + private string ValidationWarningTooltip => ValidationSummary == null ? "" : string.Join("\n", ValidationSummary.Where(i => i.Severity == FieldIssueSeverity.Warning).Select(i => i.Message)); [Parameter] public bool AllSectionsExpanded { get; set; } [Parameter] public EventCallback OnExpandAll { get; set; } [Parameter] public EventCallback OnCollapseAll { get; set; } @@ -85,8 +100,12 @@ private async Task OnChangesLinkKeyDown(KeyboardEventArgs e) { if (e.Key == "Enter" || e.Key == " ") - { await OnOpenSaveModal.InvokeAsync(); - } + } + + private async Task OnValidationLinkKeyDown(KeyboardEventArgs e) + { + if (e.Key == "Enter" || e.Key == " ") + await OnOpenSaveModal.InvokeAsync(); } } diff --git a/src/DnsmasqWebUI/Components/EffectiveConfig/CustomDisplays/Do0x20EncodeDisplay.razor b/src/DnsmasqWebUI/Components/EffectiveConfig/CustomDisplays/Do0x20EncodeDisplay.razor new file mode 100644 index 0000000..b85974b --- /dev/null +++ b/src/DnsmasqWebUI/Components/EffectiveConfig/CustomDisplays/Do0x20EncodeDisplay.razor @@ -0,0 +1,59 @@ +@namespace DnsmasqWebUI.Components.EffectiveConfig.CustomDisplays +@using DnsmasqWebUI.Models.Dnsmasq +@using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig +@inherits EffectiveConfigValueDisplayBase + +@* do-0x20-encode / no-0x20-encode: tri-state. Checkbox shows Enabled vs not-Enabled; uncheck maps back to Default when that was the value at activation, else Disabled. *@ +@if (IsActiveEditor && Descriptor.GetSource()?.IsReadOnly != true) +{ + var isOn = GetState() == ExplicitToggleState.Enabled; + +} +else +{ + @(GetState() == ExplicitToggleState.Enabled ? "Enabled" : "Disabled") +} + +@code { + private ExplicitToggleState _stateAtActivation; + private bool _wasActiveEditor; + + private ExplicitToggleState GetState() + { + var v = GetEffectiveDisplayValue(); + if (v is ExplicitToggleState s) return s; + return ExplicitToggleState.Default; + } + + protected override void OnParametersSet() + { + if (!IsActiveEditor) + _stateAtActivation = GetState(); + else if (!_wasActiveEditor) + _stateAtActivation = GetState(); + _wasActiveEditor = IsActiveEditor; + } + + private async Task OnCheckedChange(ChangeEventArgs e) + { + var isChecked = e.Value is true; + if (isChecked) + { + await NotifyValueChanged(ExplicitToggleState.Enabled); + return; + } + + var pending = Session?.PendingChanges.FirstOrDefault(c => + string.Equals(c.SectionId, Descriptor.SectionId, StringComparison.OrdinalIgnoreCase) && + string.Equals(c.OptionName, Descriptor.OptionName, StringComparison.OrdinalIgnoreCase)); + if (pending?.OldValue is ExplicitToggleState previousState) + { + await NotifyValueChanged(previousState); + return; + } + + // No existing pending edit for this field: map unchecked based on the state at activation. + // Default and Disabled both display as "Disabled" in view mode, so we preserve true intent here. + await NotifyValueChanged(_stateAtActivation == ExplicitToggleState.Default ? ExplicitToggleState.Default : ExplicitToggleState.Disabled); + } +} diff --git a/src/DnsmasqWebUI/Components/EffectiveConfig/CustomDisplays/KeyOnlyOrValueDisplay.razor b/src/DnsmasqWebUI/Components/EffectiveConfig/CustomDisplays/KeyOnlyOrValueDisplay.razor new file mode 100644 index 0000000..011cb2c --- /dev/null +++ b/src/DnsmasqWebUI/Components/EffectiveConfig/CustomDisplays/KeyOnlyOrValueDisplay.razor @@ -0,0 +1,84 @@ +@namespace DnsmasqWebUI.Components.EffectiveConfig.CustomDisplays +@using DnsmasqWebUI.Models.Dnsmasq +@inherits EffectiveConfigValueDisplayBase + +@* Options that can be: not set, key-only (e.g. use-stale-cache), or key=value (e.g. use-stale-cache=60). Checkbox to enable; when enabled, optional value input. *@ +@if (IsActiveEditor && Descriptor.GetSource()?.IsReadOnly != true) +{ + var isEnabled = _editorValue != null; + var valuePart = _editorValue is { Length: > 0 } ? _editorValue : ""; +
+
+ +
+ @if (isEnabled) + { + + } +
+} +else +{ + var v = GetEffectiveDisplayValue() as string; + if (v is null) + { + @("(not set)") + } + else if (v.Length == 0) + { + @("Enabled") + } + else + { + @v + } +} + +@code { + private string? _editorValue; + private bool _wasActiveEditor; + + protected override void OnParametersSet() + { + var effective = GetEffectiveDisplayValue() as string; + if (!IsActiveEditor) + { + _editorValue = effective; + } + else if (!_wasActiveEditor) + { + _editorValue = effective; + } + + _wasActiveEditor = IsActiveEditor; + } + + private async Task OnCheckedChange(ChangeEventArgs e) + { + var isChecked = e.Value switch + { + bool b => b, + string s when bool.TryParse(s, out var parsed) => parsed, + _ => false + }; + + _editorValue = isChecked ? "" : null; + await NotifyValueChanged(_editorValue); + } + + private async Task OnValueInput(ChangeEventArgs e) + { + var raw = e.Value?.ToString()?.Trim(); + _editorValue = string.IsNullOrEmpty(raw) ? "" : raw; + await NotifyValueChanged(_editorValue); + } +} diff --git a/src/DnsmasqWebUI/Components/EffectiveConfig/CustomDisplays/KeyOnlyOrValueDisplay.razor.css b/src/DnsmasqWebUI/Components/EffectiveConfig/CustomDisplays/KeyOnlyOrValueDisplay.razor.css new file mode 100644 index 0000000..74557fc --- /dev/null +++ b/src/DnsmasqWebUI/Components/EffectiveConfig/CustomDisplays/KeyOnlyOrValueDisplay.razor.css @@ -0,0 +1,31 @@ +.ec-key-only-or-value-editor { + display: inline-flex; + align-items: center; + gap: 0.5rem; + min-width: 0; + max-width: 100%; + white-space: nowrap; +} + +.ec-key-only-or-value-switch { + display: inline-flex; + align-items: center; + flex: 0 0 auto; + min-height: 1.25rem; + padding-left: 0; +} + +.ec-key-only-or-value-switch .form-check-input { + float: none; + margin: 0; + cursor: pointer; + position: relative; + top: 2px; +} + +.ec-key-only-or-value-input { + flex: 1 1 7rem; + min-width: 0; + width: 7rem; + max-width: 9rem; +} diff --git a/src/DnsmasqWebUI/Components/EffectiveConfig/Fields/EffectiveConfigFieldDisplay.razor b/src/DnsmasqWebUI/Components/EffectiveConfig/Fields/EffectiveConfigFieldDisplay.razor index d55180e..a188cec 100644 --- a/src/DnsmasqWebUI/Components/EffectiveConfig/Fields/EffectiveConfigFieldDisplay.razor +++ b/src/DnsmasqWebUI/Components/EffectiveConfig/Fields/EffectiveConfigFieldDisplay.razor @@ -22,12 +22,15 @@ @if (isActiveEditor) { -
- - - @((fragmentWithCallback ?? fragment)(Descriptor)) +
+
+ + + @((fragmentWithCallback ?? fragment)(Descriptor)) + - +
+
} else @@ -35,8 +38,8 @@ @fragment(Descriptor) + } - } @@ -111,6 +114,7 @@ else private bool _hasPendingChange; private object? _valueAtActivation; private object? _draftValue; + private bool _hasDraftValue; private bool _wasActive; private ExitAction _exitAction = ExitAction.None; @@ -140,7 +144,12 @@ else if (Descriptor.IsMultiValue) ComputeMultiValueState(Descriptor.GetItems(), pending?.NewValue as IReadOnlyList, _draftValue as IReadOnlyList); if (!_wasActive && IsActive) - _valueAtActivation = descriptorValue; + { + // Use pending.OldValue when present so "revert to original" clears the pending change (e.g. do-0x20-encode: Disabled → Enable → Disable). + _valueAtActivation = pending != null ? pending.OldValue : descriptorValue; + _hasDraftValue = false; + _draftValue = null; + } if (_wasActive && !IsActive && _exitAction == ExitAction.None) _ = InvokeAsync(CommitIfChangedAsync); if (_wasActive && !IsActive) @@ -190,8 +199,7 @@ else private void HandleValueChanged(object? value) { _draftValue = value; - if (_hasPendingChange && ValuesEqual(_valueAtActivation, value)) - _ = InvokeAsync(CommitIfChangedAsync); + _hasDraftValue = true; } private async Task OnBlur(FocusEventArgs _) @@ -202,10 +210,11 @@ else private async Task CommitIfChangedAsync() { - var newValue = _draftValue ?? _effectiveValue; + var newValue = _hasDraftValue ? _draftValue : _effectiveValue; if (ValuesEqual(_valueAtActivation, newValue)) { _draftValue = null; + _hasDraftValue = false; if (_hasPendingChange && Ui?.RevertFieldAsync != null) await Ui.RevertFieldAsync(Descriptor.SectionId, Descriptor.OptionName); return true; @@ -228,6 +237,7 @@ else if (Ui != null) await Ui.CommitFieldAsync(args); _draftValue = null; + _hasDraftValue = false; return true; } @@ -236,12 +246,14 @@ else private void HandleDefaultValueChanged(string value) { _draftValue = value; + _hasDraftValue = true; } private async Task HandleDefaultBlur(string? value) { if (!IsActive) return; _draftValue = value; + _hasDraftValue = true; if (!await CommitIfChangedAsync() && Ui != null) await Ui.ActivateFieldAsync(FieldKey); } @@ -249,6 +261,7 @@ else private void HandleMultiValueChanged(IReadOnlyList values) { _draftValue = values.ToList(); + _hasDraftValue = true; if (Descriptor.IsMultiValue) ComputeMultiValueState(Descriptor.GetItems(), _effectiveValue as IReadOnlyList, _draftValue as IReadOnlyList); StateHasChanged(); @@ -266,6 +279,7 @@ else private async Task HandleCancelEdit() { _draftValue = null; + _hasDraftValue = false; _exitAction = ExitAction.Cancel; if (Ui != null) await Ui.DeactivateFieldAsync(); @@ -274,6 +288,7 @@ else private async Task HandleRevertEdit() { _draftValue = null; + _hasDraftValue = false; Session?.RevertChange(Descriptor.SectionId, Descriptor.OptionName); _exitAction = ExitAction.Revert; if (Ui != null) diff --git a/src/DnsmasqWebUI/Components/EffectiveConfig/Modals/EffectiveConfigSaveFlow.razor b/src/DnsmasqWebUI/Components/EffectiveConfig/Modals/EffectiveConfigSaveFlow.razor index 3265893..484f652 100644 --- a/src/DnsmasqWebUI/Components/EffectiveConfig/Modals/EffectiveConfigSaveFlow.razor +++ b/src/DnsmasqWebUI/Components/EffectiveConfig/Modals/EffectiveConfigSaveFlow.razor @@ -39,6 +39,8 @@ [Parameter] public IReadOnlyList PendingChanges { get; set; } = null!; [Parameter] public DnsmasqServiceStatus? Status { get; set; } [Parameter] public EventCallback OnClose { get; set; } + /// Invoked when the modal reverts a change so the parent can re-run validation (e.g. cross-option evaluator). + [Parameter] public EventCallback OnRevertedNotify { get; set; } [Parameter] public EventCallback OnSaveCompleted { get; set; } [Parameter] public EventCallback OnContinueEditingAfterSavedFailure { get; set; } [Parameter] public EventCallback OnCloseAndEndEdit { get; set; } @@ -60,6 +62,8 @@ private async Task HandleReverted() { + if (OnRevertedNotify.HasDelegate) + await OnRevertedNotify.InvokeAsync(); await InvokeAsync(StateHasChanged); } diff --git a/src/DnsmasqWebUI/Components/EffectiveConfig/Modals/EffectiveConfigSaveModal.razor b/src/DnsmasqWebUI/Components/EffectiveConfig/Modals/EffectiveConfigSaveModal.razor index 998b883..e017558 100644 --- a/src/DnsmasqWebUI/Components/EffectiveConfig/Modals/EffectiveConfigSaveModal.razor +++ b/src/DnsmasqWebUI/Components/EffectiveConfig/Modals/EffectiveConfigSaveModal.razor @@ -2,6 +2,7 @@ @using DnsmasqWebUI.Models.Dnsmasq @using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig @using DnsmasqWebUI.Infrastructure.Services.EffectiveConfig.Abstractions +@using DnsmasqWebUI.Infrastructure.Helpers.Config
@@ -206,6 +236,10 @@ private string? ManagedFilePath => Status?.ManagedFilePath; + private IReadOnlyList ValidationErrors => Session.GetValidationSummary().Where(i => i.Severity == FieldIssueSeverity.Error).ToList(); + private IReadOnlyList ValidationWarnings => Session.GetValidationSummary().Where(i => i.Severity == FieldIssueSeverity.Warning).ToList(); + private bool HasBlockingValidationErrors => Session.HasBlockingValidationErrors(); + private static string GetValidationFailedOutput(EffectiveConfigSaveResult result) { var err = (result.ValidationStdErr ?? "").Trim(); @@ -248,8 +282,10 @@ return null; } - private static string FormatValue(object? v) + private static string FormatValue(object? v, string? optionName = null) { + if (optionName != null && EffectiveConfigWriteBehaviorMap.GetBehavior(optionName) == EffectiveConfigWriteBehavior.KeyOnlyOrValue) + return FormatKeyOnlyOrValue(v); if (v == null) return "(not set)"; if (v is bool b) return b ? "on" : "off"; var list = AsStringList(v); @@ -257,6 +293,14 @@ return v.ToString() ?? "(not set)"; } + private static string FormatKeyOnlyOrValue(object? v) + { + if (v == null) return "(not set)"; + var s = v.ToString(); + if (string.IsNullOrEmpty(s)) return "(set)"; + return s; + } + private async Task Revert(PendingEffectiveConfigChange change) { Session.RevertChange(change.SectionId, change.OptionName); diff --git a/src/DnsmasqWebUI/Components/Pages/Dnsmasq.razor b/src/DnsmasqWebUI/Components/Pages/Dnsmasq.razor index 042c6fd..39b03a1 100644 --- a/src/DnsmasqWebUI/Components/Pages/Dnsmasq.razor +++ b/src/DnsmasqWebUI/Components/Pages/Dnsmasq.razor @@ -76,7 +76,7 @@ else if (_status != null) } - + diff --git a/src/DnsmasqWebUI/Controllers/ReloadController.cs b/src/DnsmasqWebUI/Controllers/ReloadController.cs index 28518b9..67c0eac 100644 --- a/src/DnsmasqWebUI/Controllers/ReloadController.cs +++ b/src/DnsmasqWebUI/Controllers/ReloadController.cs @@ -1,3 +1,4 @@ +using DnsmasqWebUI.Infrastructure.Services.Dnsmasq.Config.Abstractions; using DnsmasqWebUI.Infrastructure.Services.Dnsmasq.Reload.Abstractions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -9,11 +10,13 @@ namespace DnsmasqWebUI.Controllers; public class ReloadController : ControllerBase { private readonly IReloadService _reloadService; + private readonly IConfigSetCache _configSetCache; private readonly ILogger _logger; - public ReloadController(IReloadService reloadService, ILogger logger) + public ReloadController(IReloadService reloadService, IConfigSetCache configSetCache, ILogger logger) { _reloadService = reloadService; + _configSetCache = configSetCache; _logger = logger; } @@ -24,6 +27,8 @@ public class ReloadController : ControllerBase { var result = await _reloadService.ReloadAsync(ct); _logger.LogInformation("Reload requested, success={Success}", result.Success); + if (result.Success) + _configSetCache.Invalidate(); return Ok(result); } catch (Exception ex) diff --git a/src/DnsmasqWebUI/Infrastructure/Helpers/Config/DnsmasqConfKeys.cs b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/DnsmasqConfKeys.cs index 170c8b8..87e6bf1 100644 --- a/src/DnsmasqWebUI/Infrastructure/Helpers/Config/DnsmasqConfKeys.cs +++ b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/DnsmasqConfKeys.cs @@ -148,4 +148,63 @@ public static class DnsmasqConfKeys // --- Niche / platform (Linux conntrack mark for UBus/query filtering) --- public const string Conntrack = "conntrack"; + public const string ConnmarkAllowlistEnable = "connmark-allowlist-enable"; + public const string NoRoundRobin = "no-round-robin"; + public const string DnssecNoTimecheck = "dnssec-no-timecheck"; + public const string DnssecDebug = "dnssec-debug"; + public const string Leasequery = "leasequery"; + public const string DhcpGenerateNames = "dhcp-generate-names"; + public const string DhcpBroadcast = "dhcp-broadcast"; + public const string DhcpSequentialIp = "dhcp-sequential-ip"; + public const string DhcpIgnoreClid = "dhcp-ignore-clid"; + public const string BootpDynamic = "bootp-dynamic"; + public const string NoPing = "no-ping"; + public const string ScriptArp = "script-arp"; + public const string ScriptOnRenewal = "script-on-renewal"; + public const string DhcpNoOverride = "dhcp-no-override"; + public const string QuietDhcp = "quiet-dhcp"; + public const string QuietDhcp6 = "quiet-dhcp6"; + public const string QuietRa = "quiet-ra"; + public const string QuietTftp = "quiet-tftp"; + + public const string DnsForwardMax = "dns-forward-max"; + public const string Dumpfile = "dumpfile"; + public const string Dumpmask = "dumpmask"; + public const string AddCpeId = "add-cpe-id"; + public const string DnssecTimestamp = "dnssec-timestamp"; + public const string DnssecLimits = "dnssec-limits"; + public const string DhcpAlternatePort = "dhcp-alternate-port"; + public const string DhcpDuid = "dhcp-duid"; + public const string DhcpLuascript = "dhcp-luascript"; + public const string DhcpScriptuser = "dhcp-scriptuser"; + public const string DhcpPxeVendor = "dhcp-pxe-vendor"; + + public const string ConnmarkAllowlist = "connmark-allowlist"; + public const string CaaRecord = "caa-record"; + public const string DnsRr = "dns-rr"; + public const string SynthDomain = "synth-domain"; + public const string AuthZone = "auth-zone"; + public const string AuthSoa = "auth-soa"; + public const string AuthSecServers = "auth-sec-servers"; + public const string AuthPeer = "auth-peer"; + public const string DhcpRelay = "dhcp-relay"; + public const string DhcpCircuitid = "dhcp-circuitid"; + public const string DhcpRemoteid = "dhcp-remoteid"; + public const string DhcpSubscrid = "dhcp-subscrid"; + public const string DhcpProxy = "dhcp-proxy"; + public const string TagIf = "tag-if"; + public const string BridgeInterface = "bridge-interface"; + public const string SharedNetwork = "shared-network"; + public const string DhcpOptionPxe = "dhcp-option-pxe"; + + public const string UseStaleCache = "use-stale-cache"; + public const string AddMac = "add-mac"; + public const string StripMac = "strip-mac"; + public const string AddSubnet = "add-subnet"; + public const string StripSubnet = "strip-subnet"; + public const string Umbrella = "umbrella"; + + public const string Do0x20Encode = "do-0x20-encode"; + public const string No0x20Encode = "no-0x20-encode"; + } diff --git a/src/DnsmasqWebUI/Infrastructure/Helpers/Config/DnsmasqOptionTooltips.cs b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/DnsmasqOptionTooltips.cs index 8ccef15..b09ed56 100644 --- a/src/DnsmasqWebUI/Infrastructure/Helpers/Config/DnsmasqOptionTooltips.cs +++ b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/DnsmasqOptionTooltips.cs @@ -57,6 +57,11 @@ public static class DnsmasqOptionTooltips [DnsmasqConfKeys.NoDhcpv6Interface] = "No DHCPv6/RA on these interfaces. Can repeat.", [DnsmasqConfKeys.Ipset] = "Add resolved IPs to ipset (domain, set names). Can repeat.", [DnsmasqConfKeys.Nftset] = "Add resolved IPs to nftables sets. Can repeat.", + [DnsmasqConfKeys.ConnmarkAllowlistEnable] = "Use conntrack marks to allowlist which queries are forwarded (with connmark-allowlist).", + [DnsmasqConfKeys.NoRoundRobin] = "Do not rotate the order of upstream servers for each query.", + [DnsmasqConfKeys.DnsForwardMax] = "Maximum number of concurrent DNS forwards (default 150).", + [DnsmasqConfKeys.ConnmarkAllowlist] = "Conntrack marks that allowlist which queries are forwarded (with connmark-allowlist-enable). Can repeat.", + [DnsmasqConfKeys.Do0x20Encode] = "Use 0x20 encoding in queries to detect DNS poisoning (Enabled), disable it (Disabled), or default (not set).", // --- DNS records --- [DnsmasqConfKeys.Domain] = "Local domain(s); optional IP. Can repeat (e.g. domain=home,192.168.1.1).", @@ -72,12 +77,33 @@ public static class DnsmasqOptionTooltips [DnsmasqConfKeys.HostRecord] = "A/AAAA host records (name, IP). Can repeat.", [DnsmasqConfKeys.DynamicHost] = "Dynamic host entries. Can repeat.", [DnsmasqConfKeys.InterfaceName] = "Name per interface (for localise-queries). Can repeat.", + [DnsmasqConfKeys.CaaRecord] = "CAA record (domain, flags, tag, value). Can repeat.", + [DnsmasqConfKeys.DnsRr] = "Arbitrary DNS resource record. Can repeat.", + [DnsmasqConfKeys.SynthDomain] = "Synthesize answers for a domain. Can repeat.", + [DnsmasqConfKeys.AuthZone] = "Authoritative zone (domain, subnet). Can repeat.", + [DnsmasqConfKeys.AuthSoa] = "SOA record for authoritative zone. Can repeat.", + [DnsmasqConfKeys.AuthSecServers] = "Secondary servers for zone transfer. Can repeat.", + [DnsmasqConfKeys.AuthPeer] = "Peers for zone transfer. Can repeat.", // --- DHCP --- [DnsmasqConfKeys.DhcpAuthoritative] = "DHCP server is authoritative; required for DHCP to work.", [DnsmasqConfKeys.DhcpRapidCommit] = "DHCPv4 Rapid Commit (RFC 4039); use only if single server or all commit.", + [DnsmasqConfKeys.Leasequery] = "Respond to DHCPLEASEQUERY messages (e.g. from relay).", + [DnsmasqConfKeys.DhcpGenerateNames] = "Generate names for DHCP clients from their hostname or MAC.", + [DnsmasqConfKeys.DhcpBroadcast] = "Use broadcast to communicate with DHCP clients when unicast is not possible.", + [DnsmasqConfKeys.DhcpSequentialIp] = "Allocate DHCP addresses sequentially from the range.", + [DnsmasqConfKeys.DhcpIgnoreClid] = "Ignore client identifier (use MAC only) for DHCP.", + [DnsmasqConfKeys.BootpDynamic] = "Allow dynamic allocation for BOOTP clients.", + [DnsmasqConfKeys.NoPing] = "Do not ping the gateway before offering a DHCP lease.", + [DnsmasqConfKeys.ScriptArp] = "Call dhcp-script with extra 'arp' or 'arp-old arp-new' for ARP table updates.", + [DnsmasqConfKeys.ScriptOnRenewal] = "Call dhcp-script on lease renewal (in addition to first assignment).", + [DnsmasqConfKeys.DhcpNoOverride] = "Do not replace existing DNS/DHCP data for a client (e.g. from /etc/hosts).", [DnsmasqConfKeys.DhcpScript] = "Script run on lease add/delete (add|del, MAC, IP, hostname).", [DnsmasqConfKeys.LeasefileRo] = "Do not create or truncate the DHCP lease file.", + [DnsmasqConfKeys.DhcpAlternatePort] = "Use alternate port for DHCP (e.g. 67).", + [DnsmasqConfKeys.DhcpDuid] = "DHCP unique identifier (DUID) for the server.", + [DnsmasqConfKeys.DhcpLuascript] = "Lua script for DHCP (e.g. for custom logic).", + [DnsmasqConfKeys.DhcpScriptuser] = "User to run dhcp-script as (requires root).", [DnsmasqConfKeys.DhcpLeasefile] = "Path to the DHCP lease file.", [DnsmasqConfKeys.DhcpLeaseMax] = "Maximum number of DHCP leases to hold.", [DnsmasqConfKeys.DhcpTtl] = "TTL for DHCP names in the DNS cache.", @@ -98,6 +124,14 @@ public static class DnsmasqOptionTooltips [DnsmasqConfKeys.DhcpUserclass] = "Match by DHCP user class. Can repeat.", [DnsmasqConfKeys.RaParam] = "Router advertisement parameters (DHCPv6/RA). Can repeat.", [DnsmasqConfKeys.Slaac] = "SLAAC host part for DHCPv6/RA. Can repeat.", + [DnsmasqConfKeys.DhcpRelay] = "Relay DHCP to another server. Can repeat.", + [DnsmasqConfKeys.DhcpCircuitid] = "Circuit ID for DHCP relay. Can repeat.", + [DnsmasqConfKeys.DhcpRemoteid] = "Remote ID for DHCP relay. Can repeat.", + [DnsmasqConfKeys.DhcpSubscrid] = "Subscriber ID for DHCP relay. Can repeat.", + [DnsmasqConfKeys.DhcpProxy] = "DHCP proxy (relay) configuration. Can repeat.", + [DnsmasqConfKeys.TagIf] = "Set tag when request from interface. Can repeat.", + [DnsmasqConfKeys.BridgeInterface] = "Bridge interfaces for DHCP. Can repeat.", + [DnsmasqConfKeys.SharedNetwork] = "Shared network for DHCP. Can repeat.", // --- TFTP / PXE --- [DnsmasqConfKeys.EnableTftp] = "Enable the built-in TFTP server.", @@ -107,10 +141,17 @@ public static class DnsmasqOptionTooltips [DnsmasqConfKeys.TftpRoot] = "Root directory for TFTP files.", [DnsmasqConfKeys.PxePrompt] = "PXE boot prompt (e.g. timeout,0 to disable).", [DnsmasqConfKeys.PxeService] = "PXE service definitions. Can repeat.", + [DnsmasqConfKeys.DhcpPxeVendor] = "PXE vendor class string (e.g. for PXE matching).", + [DnsmasqConfKeys.DhcpOptionPxe] = "PXE option (e.g. option 43). Can repeat.", // --- DNSSEC --- [DnsmasqConfKeys.Dnssec] = "Validate DNSSEC on replies from upstream.", [DnsmasqConfKeys.DnssecCheckUnsigned] = "Treat unsigned zones as bogus.", + [DnsmasqConfKeys.DnssecNoTimecheck] = "Do not validate DNSSEC signatures against the current time (e.g. for testing).", + [DnsmasqConfKeys.DnssecDebug] = "Log DNSSEC validation failures at debug level.", + [DnsmasqConfKeys.AddCpeId] = "Add CPE-ID to DHCP requests (for identification).", + [DnsmasqConfKeys.DnssecTimestamp] = "Path to file for DNSSEC timestamp (e.g. for validation).", + [DnsmasqConfKeys.DnssecLimits] = "DNSSEC validation limits (e.g. max signed zones).", [DnsmasqConfKeys.TrustAnchor] = "DNSSEC trust anchors. Can repeat.", // --- Cache --- @@ -121,6 +162,12 @@ public static class DnsmasqOptionTooltips [DnsmasqConfKeys.MaxTtl] = "Maximum TTL for cached responses (cap on upstream TTL).", [DnsmasqConfKeys.MaxCacheTtl] = "Maximum TTL for positive cache entries.", [DnsmasqConfKeys.MinCacheTtl] = "Minimum TTL for positive cache entries.", + [DnsmasqConfKeys.Dumpfile] = "Dump cache to this file (for debugging).", + [DnsmasqConfKeys.Dumpmask] = "Mask for cache dump (which data to include).", + [DnsmasqConfKeys.UseStaleCache] = "Use stale cache when upstream is unavailable. Off, On (key-only), or custom seconds.", + [DnsmasqConfKeys.AddMac] = "Add MAC address to DNS queries. Off, On (key-only), or custom value.", + [DnsmasqConfKeys.AddSubnet] = "Add subnet to DNS queries. Off, On (key-only), or custom value.", + [DnsmasqConfKeys.Umbrella] = "Cisco Umbrella device identity. Off, On (key-only), or custom value.", // --- Process & networking --- [DnsmasqConfKeys.NoPoll] = "Do not poll /etc/resolv.conf for changes.", @@ -143,6 +190,10 @@ public static class DnsmasqOptionTooltips [DnsmasqConfKeys.KeepInForeground] = "Do not daemonise; stay in foreground (e.g. under systemd).", [DnsmasqConfKeys.NoDaemon] = "Debug mode: no fork, no pid file, log to stderr.", [DnsmasqConfKeys.Conntrack] = "Linux connection-tracking mark for UBus/query filtering (platform-dependent).", + [DnsmasqConfKeys.QuietDhcp] = "Suppress routine DHCP logging.", + [DnsmasqConfKeys.QuietDhcp6] = "Suppress routine DHCPv6 logging.", + [DnsmasqConfKeys.QuietRa] = "Suppress routine router advertisement logging.", + [DnsmasqConfKeys.QuietTftp] = "Suppress routine TFTP logging.", }.ToFrozenDictionary(); /// Returns a short tooltip for the option, or null if none is defined. diff --git a/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigFieldBuilder.cs b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigFieldBuilder.cs index 22e0d08..82fd737 100644 --- a/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigFieldBuilder.cs +++ b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigFieldBuilder.cs @@ -87,6 +87,11 @@ public static class EffectiveConfigFieldBuilder list.AddDescriptor(registry, DnsmasqConfKeys.FastDnsRetry, status, s => Config(s)?.FastDnsRetry, s => Sources(s)?.FastDnsRetry, null); list.AddDescriptor(registry, DnsmasqConfKeys.Ipset, status, null, null, Items(ec => ec?.IpsetValues, src => src?.IpsetValues)); list.AddDescriptor(registry, DnsmasqConfKeys.Nftset, status, null, null, Items(ec => ec?.NftsetValues, src => src?.NftsetValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.ConnmarkAllowlistEnable, status, s => Config(s)?.ConnmarkAllowlistEnable, s => Sources(s)?.ConnmarkAllowlistEnable, null); + list.AddDescriptor(registry, DnsmasqConfKeys.NoRoundRobin, status, s => Config(s)?.NoRoundRobin, s => Sources(s)?.NoRoundRobin, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DnsForwardMax, status, s => Config(s)?.DnsForwardMax, s => Sources(s)?.DnsForwardMax, null); + list.AddDescriptor(registry, DnsmasqConfKeys.ConnmarkAllowlist, status, null, null, Items(ec => ec?.ConnmarkAllowlistValues, src => src?.ConnmarkAllowlistValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.Do0x20Encode, status, s => Config(s)?.Do0x20EncodeState, s => Sources(s)?.Do0x20Encode, null); // DNS records list.AddDescriptor(registry, DnsmasqConfKeys.Domain, status, null, null, Items(ec => ec?.DomainValues, src => src?.DomainValues)); @@ -99,6 +104,13 @@ public static class EffectiveConfigFieldBuilder list.AddDescriptor(registry, DnsmasqConfKeys.HostRecord, status, null, null, Items(ec => ec?.HostRecordValues, src => src?.HostRecordValues)); list.AddDescriptor(registry, DnsmasqConfKeys.DynamicHost, status, null, null, Items(ec => ec?.DynamicHostValues, src => src?.DynamicHostValues)); list.AddDescriptor(registry, DnsmasqConfKeys.InterfaceName, status, null, null, Items(ec => ec?.InterfaceNameValues, src => src?.InterfaceNameValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.CaaRecord, status, null, null, Items(ec => ec?.CaaRecordValues, src => src?.CaaRecordValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.DnsRr, status, null, null, Items(ec => ec?.DnsRrValues, src => src?.DnsRrValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.SynthDomain, status, null, null, Items(ec => ec?.SynthDomainValues, src => src?.SynthDomainValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.AuthZone, status, null, null, Items(ec => ec?.AuthZoneValues, src => src?.AuthZoneValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.AuthSoa, status, null, null, Items(ec => ec?.AuthSoaValues, src => src?.AuthSoaValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.AuthSecServers, status, null, null, Items(ec => ec?.AuthSecServersValues, src => src?.AuthSecServersValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.AuthPeer, status, null, null, Items(ec => ec?.AuthPeerValues, src => src?.AuthPeerValues)); list.AddDescriptor(registry, DnsmasqConfKeys.MxTarget, status, s => Config(s)?.MxTarget, s => Sources(s)?.MxTarget, null); list.AddDescriptor(registry, DnsmasqConfKeys.Localmx, status, s => Config(s)?.Localmx, s => Sources(s)?.Localmx, null); list.AddDescriptor(registry, DnsmasqConfKeys.Selfmx, status, s => Config(s)?.Selfmx, s => Sources(s)?.Selfmx, null); @@ -107,7 +119,21 @@ public static class EffectiveConfigFieldBuilder list.AddDescriptor(registry, DnsmasqConfKeys.DhcpAuthoritative, status, s => Config(s)?.DhcpAuthoritative, s => Sources(s)?.DhcpAuthoritative, null); list.AddDescriptor(registry, DnsmasqConfKeys.DhcpRapidCommit, status, s => Config(s)?.DhcpRapidCommit, s => Sources(s)?.DhcpRapidCommit, null); list.AddDescriptor(registry, DnsmasqConfKeys.LeasefileRo, status, s => Config(s)?.LeasefileRo, s => Sources(s)?.LeasefileRo, null); + list.AddDescriptor(registry, DnsmasqConfKeys.Leasequery, status, s => Config(s)?.Leasequery, s => Sources(s)?.Leasequery, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpGenerateNames, status, s => Config(s)?.DhcpGenerateNames, s => Sources(s)?.DhcpGenerateNames, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpBroadcast, status, s => Config(s)?.DhcpBroadcast, s => Sources(s)?.DhcpBroadcast, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpSequentialIp, status, s => Config(s)?.DhcpSequentialIp, s => Sources(s)?.DhcpSequentialIp, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpIgnoreClid, status, s => Config(s)?.DhcpIgnoreClid, s => Sources(s)?.DhcpIgnoreClid, null); + list.AddDescriptor(registry, DnsmasqConfKeys.BootpDynamic, status, s => Config(s)?.BootpDynamic, s => Sources(s)?.BootpDynamic, null); + list.AddDescriptor(registry, DnsmasqConfKeys.NoPing, status, s => Config(s)?.NoPing, s => Sources(s)?.NoPing, null); + list.AddDescriptor(registry, DnsmasqConfKeys.ScriptArp, status, s => Config(s)?.ScriptArp, s => Sources(s)?.ScriptArp, null); + list.AddDescriptor(registry, DnsmasqConfKeys.ScriptOnRenewal, status, s => Config(s)?.ScriptOnRenewal, s => Sources(s)?.ScriptOnRenewal, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpNoOverride, status, s => Config(s)?.DhcpNoOverride, s => Sources(s)?.DhcpNoOverride, null); list.AddDescriptor(registry, DnsmasqConfKeys.DhcpScript, status, s => Config(s)?.DhcpScriptPath, s => Sources(s)?.DhcpScriptPath, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpAlternatePort, status, s => Config(s)?.DhcpAlternatePort, s => Sources(s)?.DhcpAlternatePort, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpDuid, status, s => Config(s)?.DhcpDuid, s => Sources(s)?.DhcpDuid, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpLuascript, status, s => Config(s)?.DhcpLuascriptPath, s => Sources(s)?.DhcpLuascriptPath, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpScriptuser, status, s => Config(s)?.DhcpScriptuser, s => Sources(s)?.DhcpScriptuser, null); list.AddDescriptor(registry, DnsmasqConfKeys.DhcpLeasefile, status, s => Config(s)?.DhcpLeaseFilePath, s => Sources(s)?.DhcpLeaseFilePath, null); list.AddDescriptor(registry, DnsmasqConfKeys.DhcpLeaseMax, status, s => Config(s)?.DhcpLeaseMax, s => Sources(s)?.DhcpLeaseMax, null); list.AddDescriptor(registry, DnsmasqConfKeys.DhcpTtl, status, s => Config(s)?.DhcpTtl, s => Sources(s)?.DhcpTtl, null); @@ -122,6 +148,14 @@ public static class EffectiveConfigFieldBuilder list.AddDescriptor(registry, DnsmasqConfKeys.DhcpHostsfile, status, null, null, Items(ec => ec?.DhcpHostsfilePaths, src => src?.DhcpHostsfilePaths)); list.AddDescriptor(registry, DnsmasqConfKeys.DhcpOptsfile, status, null, null, Items(ec => ec?.DhcpOptsfilePaths, src => src?.DhcpOptsfilePaths)); list.AddDescriptor(registry, DnsmasqConfKeys.DhcpHostsdir, status, null, null, Items(ec => ec?.DhcpHostsdirPaths, src => src?.DhcpHostsdirPaths)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpRelay, status, null, null, Items(ec => ec?.DhcpRelayValues, src => src?.DhcpRelayValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpCircuitid, status, null, null, Items(ec => ec?.DhcpCircuitidValues, src => src?.DhcpCircuitidValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpRemoteid, status, null, null, Items(ec => ec?.DhcpRemoteidValues, src => src?.DhcpRemoteidValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpSubscrid, status, null, null, Items(ec => ec?.DhcpSubscridValues, src => src?.DhcpSubscridValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpProxy, status, null, null, Items(ec => ec?.DhcpProxyValues, src => src?.DhcpProxyValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.TagIf, status, null, null, Items(ec => ec?.TagIfValues, src => src?.TagIfValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.BridgeInterface, status, null, null, Items(ec => ec?.BridgeInterfaceValues, src => src?.BridgeInterfaceValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.SharedNetwork, status, null, null, Items(ec => ec?.SharedNetworkValues, src => src?.SharedNetworkValues)); list.AddDescriptor(registry, DnsmasqConfKeys.DhcpBoot, status, null, null, Items(ec => ec?.DhcpBootValues, src => src?.DhcpBootValues)); list.AddDescriptor(registry, DnsmasqConfKeys.DhcpIgnore, status, null, null, Items(ec => ec?.DhcpIgnoreValues, src => src?.DhcpIgnoreValues)); list.AddDescriptor(registry, DnsmasqConfKeys.DhcpVendorclass, status, null, null, Items(ec => ec?.DhcpVendorclassValues, src => src?.DhcpVendorclassValues)); @@ -137,11 +171,18 @@ public static class EffectiveConfigFieldBuilder list.AddDescriptor(registry, DnsmasqConfKeys.TftpRoot, status, s => Config(s)?.TftpRootPath, s => Sources(s)?.TftpRootPath, null); list.AddDescriptor(registry, DnsmasqConfKeys.PxePrompt, status, s => Config(s)?.PxePrompt, s => Sources(s)?.PxePrompt, null); list.AddDescriptor(registry, DnsmasqConfKeys.PxeService, status, null, null, Items(ec => ec?.PxeServiceValues, src => src?.PxeServiceValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpPxeVendor, status, s => Config(s)?.DhcpPxeVendor, s => Sources(s)?.DhcpPxeVendor, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpOptionPxe, status, null, null, Items(ec => ec?.DhcpOptionPxeValues, src => src?.DhcpOptionPxeValues)); // DNSSEC list.AddDescriptor(registry, DnsmasqConfKeys.Dnssec, status, s => Config(s)?.Dnssec, s => Sources(s)?.Dnssec, null); list.AddDescriptor(registry, DnsmasqConfKeys.DnssecCheckUnsigned, status, s => Config(s)?.DnssecCheckUnsigned, s => Sources(s)?.DnssecCheckUnsigned, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DnssecNoTimecheck, status, s => Config(s)?.DnssecNoTimecheck, s => Sources(s)?.DnssecNoTimecheck, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DnssecDebug, status, s => Config(s)?.DnssecDebug, s => Sources(s)?.DnssecDebug, null); list.AddDescriptor(registry, DnsmasqConfKeys.ProxyDnssec, status, s => Config(s)?.ProxyDnssec, s => Sources(s)?.ProxyDnssec, null); + list.AddDescriptor(registry, DnsmasqConfKeys.AddCpeId, status, s => Config(s)?.AddCpeId, s => Sources(s)?.AddCpeId, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DnssecTimestamp, status, s => Config(s)?.DnssecTimestamp, s => Sources(s)?.DnssecTimestamp, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DnssecLimits, status, s => Config(s)?.DnssecLimits, s => Sources(s)?.DnssecLimits, null); list.AddDescriptor(registry, DnsmasqConfKeys.TrustAnchor, status, null, null, Items(ec => ec?.TrustAnchorValues, src => src?.TrustAnchorValues)); // Cache @@ -149,6 +190,12 @@ public static class EffectiveConfigFieldBuilder list.AddDescriptor(registry, DnsmasqConfKeys.CacheSize, status, s => Config(s)?.CacheSize, s => Sources(s)?.CacheSize, null); list.AddDescriptor(registry, DnsmasqConfKeys.LocalTtl, status, s => Config(s)?.LocalTtl, s => Sources(s)?.LocalTtl, null); list.AddDescriptor(registry, DnsmasqConfKeys.NoNegcache, status, s => Config(s)?.NoNegcache, s => Sources(s)?.NoNegcache, null); + list.AddDescriptor(registry, DnsmasqConfKeys.Dumpfile, status, s => Config(s)?.DumpfilePath, s => Sources(s)?.DumpfilePath, null); + list.AddDescriptor(registry, DnsmasqConfKeys.Dumpmask, status, s => Config(s)?.Dumpmask, s => Sources(s)?.Dumpmask, null); + list.AddDescriptor(registry, DnsmasqConfKeys.UseStaleCache, status, s => Config(s)?.UseStaleCache, s => Sources(s)?.UseStaleCache, null); + list.AddDescriptor(registry, DnsmasqConfKeys.AddMac, status, s => Config(s)?.AddMac, s => Sources(s)?.AddMac, null); + list.AddDescriptor(registry, DnsmasqConfKeys.AddSubnet, status, s => Config(s)?.AddSubnet, s => Sources(s)?.AddSubnet, null); + list.AddDescriptor(registry, DnsmasqConfKeys.Umbrella, status, s => Config(s)?.Umbrella, s => Sources(s)?.Umbrella, null); list.AddDescriptor(registry, DnsmasqConfKeys.NegTtl, status, s => Config(s)?.NegTtl, s => Sources(s)?.NegTtl, null); list.AddDescriptor(registry, DnsmasqConfKeys.MaxTtl, status, s => Config(s)?.MaxTtl, s => Sources(s)?.MaxTtl, null); list.AddDescriptor(registry, DnsmasqConfKeys.MaxCacheTtl, status, s => Config(s)?.MaxCacheTtl, s => Sources(s)?.MaxCacheTtl, null); @@ -179,6 +226,10 @@ public static class EffectiveConfigFieldBuilder list.AddDescriptor(registry, DnsmasqConfKeys.KeepInForeground, status, s => Config(s)?.KeepInForeground, s => Sources(s)?.KeepInForeground, null); list.AddDescriptor(registry, DnsmasqConfKeys.NoDaemon, status, s => Config(s)?.NoDaemon, s => Sources(s)?.NoDaemon, null); list.AddDescriptor(registry, DnsmasqConfKeys.Conntrack, status, s => Config(s)?.Conntrack, s => Sources(s)?.Conntrack, null); + list.AddDescriptor(registry, DnsmasqConfKeys.QuietDhcp, status, s => Config(s)?.QuietDhcp, s => Sources(s)?.QuietDhcp, null); + list.AddDescriptor(registry, DnsmasqConfKeys.QuietDhcp6, status, s => Config(s)?.QuietDhcp6, s => Sources(s)?.QuietDhcp6, null); + list.AddDescriptor(registry, DnsmasqConfKeys.QuietRa, status, s => Config(s)?.QuietRa, s => Sources(s)?.QuietRa, null); + list.AddDescriptor(registry, DnsmasqConfKeys.QuietTftp, status, s => Config(s)?.QuietTftp, s => Sources(s)?.QuietTftp, null); return list; } diff --git a/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigOptionKindMap.cs b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigOptionKindMap.cs index 447859b..a57e90e 100644 --- a/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigOptionKindMap.cs +++ b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigOptionKindMap.cs @@ -151,6 +151,57 @@ public static class EffectiveConfigOptionKindMap [DnsmasqConfKeys.KeepInForeground] = EffectiveConfigFieldKind.Single, [DnsmasqConfKeys.NoDaemon] = EffectiveConfigFieldKind.Single, [DnsmasqConfKeys.Conntrack] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.ConnmarkAllowlistEnable] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.NoRoundRobin] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DnssecNoTimecheck] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DnssecDebug] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.Leasequery] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DhcpGenerateNames] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DhcpBroadcast] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DhcpSequentialIp] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DhcpIgnoreClid] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.BootpDynamic] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.NoPing] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.ScriptArp] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.ScriptOnRenewal] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DhcpNoOverride] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.QuietDhcp] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.QuietDhcp6] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.QuietRa] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.QuietTftp] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DnsForwardMax] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.Dumpfile] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.Dumpmask] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.AddCpeId] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DnssecTimestamp] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DnssecLimits] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DhcpAlternatePort] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DhcpDuid] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DhcpLuascript] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DhcpScriptuser] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DhcpPxeVendor] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.ConnmarkAllowlist] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.CaaRecord] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DnsRr] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.SynthDomain] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.AuthZone] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.AuthSoa] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.AuthSecServers] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.AuthPeer] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpRelay] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpCircuitid] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpRemoteid] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpSubscrid] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpProxy] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.TagIf] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.BridgeInterface] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.SharedNetwork] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpOptionPxe] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.UseStaleCache] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.AddMac] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.AddSubnet] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.Umbrella] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.Do0x20Encode] = EffectiveConfigFieldKind.Single, }; /// Returns Single or Multi for the given option name; defaults to Single if unknown. @@ -287,9 +338,107 @@ public static class EffectiveConfigParserBehaviorMap [DnsmasqConfKeys.KeepInForeground] = EffectiveConfigParserBehavior.Flag, [DnsmasqConfKeys.NoDaemon] = EffectiveConfigParserBehavior.Flag, [DnsmasqConfKeys.Conntrack] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.ConnmarkAllowlistEnable] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.NoRoundRobin] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.DnssecNoTimecheck] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.DnssecDebug] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.Leasequery] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.DhcpGenerateNames] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.DhcpBroadcast] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.DhcpSequentialIp] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.DhcpIgnoreClid] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.BootpDynamic] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.NoPing] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.ScriptArp] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.ScriptOnRenewal] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.DhcpNoOverride] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.QuietDhcp] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.QuietDhcp6] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.QuietRa] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.QuietTftp] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.DnsForwardMax] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.Dumpfile] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.Dumpmask] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.AddCpeId] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.DnssecTimestamp] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.DnssecLimits] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.DhcpAlternatePort] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.DhcpDuid] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.DhcpLuascript] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.DhcpScriptuser] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.DhcpPxeVendor] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.ConnmarkAllowlist] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.CaaRecord] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DnsRr] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.SynthDomain] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.AuthZone] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.AuthSoa] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.AuthSecServers] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.AuthPeer] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpRelay] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpCircuitid] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpRemoteid] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpSubscrid] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpProxy] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.TagIf] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.BridgeInterface] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.SharedNetwork] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpOptionPxe] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.UseStaleCache] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.AddMac] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.AddSubnet] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.Umbrella] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.Do0x20Encode] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.No0x20Encode] = EffectiveConfigParserBehavior.Flag, }; /// Returns LastWins, Flag, or Multi for the given option name; defaults to LastWins if unknown. public static EffectiveConfigParserBehavior GetBehavior(string optionName) => BehaviorByOptionName.TryGetValue(optionName, out var b) ? b : EffectiveConfigParserBehavior.LastWins; } + +/// +/// How to serialize an option when writing the managed config file. +/// Used by DnsmasqConfigService so KeyOnlyOrValue and InversePair options serialize correctly. +/// +public enum EffectiveConfigWriteBehavior +{ + Flag, + SingleValue, + MultiValue, + KeyOnlyOrValue, + InversePair, +} + +/// +/// Maps dnsmasq option names to write behavior. Options not in the map default to SingleValue +/// (or are inferred from parser behavior in the service). Add entries for KeyOnlyOrValue and InversePair options. +/// +public static class EffectiveConfigWriteBehaviorMap +{ + private static readonly IReadOnlyDictionary BehaviorByOptionName = + new Dictionary(StringComparer.Ordinal) + { + [DnsmasqConfKeys.Conntrack] = EffectiveConfigWriteBehavior.Flag, + [DnsmasqConfKeys.UseStaleCache] = EffectiveConfigWriteBehavior.KeyOnlyOrValue, + [DnsmasqConfKeys.AddMac] = EffectiveConfigWriteBehavior.KeyOnlyOrValue, + [DnsmasqConfKeys.AddSubnet] = EffectiveConfigWriteBehavior.KeyOnlyOrValue, + [DnsmasqConfKeys.Umbrella] = EffectiveConfigWriteBehavior.KeyOnlyOrValue, + [DnsmasqConfKeys.Do0x20Encode] = EffectiveConfigWriteBehavior.InversePair, + }; + + /// For InversePair options: (key for "enabled", key for "disabled"). Null if not an InversePair or unknown. + private static readonly IReadOnlyDictionary InversePairKeysByOptionName = + new Dictionary(StringComparer.Ordinal) + { + [DnsmasqConfKeys.Do0x20Encode] = (DnsmasqConfKeys.Do0x20Encode, DnsmasqConfKeys.No0x20Encode), + }; + + /// Returns write behavior for the option; defaults to SingleValue if unknown. + public static EffectiveConfigWriteBehavior GetBehavior(string optionName) => + BehaviorByOptionName.TryGetValue(optionName, out var b) ? b : EffectiveConfigWriteBehavior.SingleValue; + + /// Returns the pair of config keys (enabled, disabled) for an InversePair option; null otherwise. + public static (string KeyA, string KeyB)? GetInversePairKeys(string optionName) => + InversePairKeysByOptionName.TryGetValue(optionName, out var pair) ? pair : null; +} diff --git a/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigSections.cs b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigSections.cs index 8aa2b79..a6a8358 100644 --- a/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigSections.cs +++ b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigSections.cs @@ -67,6 +67,11 @@ public static class EffectiveConfigSections DnsmasqConfKeys.FastDnsRetry, DnsmasqConfKeys.Ipset, DnsmasqConfKeys.Nftset, + DnsmasqConfKeys.ConnmarkAllowlistEnable, + DnsmasqConfKeys.NoRoundRobin, + DnsmasqConfKeys.DnsForwardMax, + DnsmasqConfKeys.ConnmarkAllowlist, + DnsmasqConfKeys.Do0x20Encode, ]), new SectionDef(SectionDnsRecords, "DNS records", [ DnsmasqConfKeys.Domain, @@ -79,6 +84,13 @@ public static class EffectiveConfigSections DnsmasqConfKeys.HostRecord, DnsmasqConfKeys.DynamicHost, DnsmasqConfKeys.InterfaceName, + DnsmasqConfKeys.CaaRecord, + DnsmasqConfKeys.DnsRr, + DnsmasqConfKeys.SynthDomain, + DnsmasqConfKeys.AuthZone, + DnsmasqConfKeys.AuthSoa, + DnsmasqConfKeys.AuthSecServers, + DnsmasqConfKeys.AuthPeer, DnsmasqConfKeys.MxTarget, DnsmasqConfKeys.Localmx, DnsmasqConfKeys.Selfmx, @@ -87,7 +99,21 @@ public static class EffectiveConfigSections DnsmasqConfKeys.DhcpAuthoritative, DnsmasqConfKeys.DhcpRapidCommit, DnsmasqConfKeys.LeasefileRo, + DnsmasqConfKeys.Leasequery, + DnsmasqConfKeys.DhcpGenerateNames, + DnsmasqConfKeys.DhcpBroadcast, + DnsmasqConfKeys.DhcpSequentialIp, + DnsmasqConfKeys.DhcpIgnoreClid, + DnsmasqConfKeys.BootpDynamic, + DnsmasqConfKeys.NoPing, + DnsmasqConfKeys.ScriptArp, + DnsmasqConfKeys.ScriptOnRenewal, + DnsmasqConfKeys.DhcpNoOverride, DnsmasqConfKeys.DhcpScript, + DnsmasqConfKeys.DhcpAlternatePort, + DnsmasqConfKeys.DhcpDuid, + DnsmasqConfKeys.DhcpLuascript, + DnsmasqConfKeys.DhcpScriptuser, DnsmasqConfKeys.DhcpLeasefile, DnsmasqConfKeys.DhcpLeaseMax, DnsmasqConfKeys.DhcpTtl, @@ -106,6 +132,14 @@ public static class EffectiveConfigSections DnsmasqConfKeys.DhcpIgnore, DnsmasqConfKeys.DhcpVendorclass, DnsmasqConfKeys.DhcpUserclass, + DnsmasqConfKeys.DhcpRelay, + DnsmasqConfKeys.DhcpCircuitid, + DnsmasqConfKeys.DhcpRemoteid, + DnsmasqConfKeys.DhcpSubscrid, + DnsmasqConfKeys.DhcpProxy, + DnsmasqConfKeys.TagIf, + DnsmasqConfKeys.BridgeInterface, + DnsmasqConfKeys.SharedNetwork, DnsmasqConfKeys.RaParam, DnsmasqConfKeys.Slaac, ]), @@ -117,11 +151,18 @@ public static class EffectiveConfigSections DnsmasqConfKeys.TftpRoot, DnsmasqConfKeys.PxePrompt, DnsmasqConfKeys.PxeService, + DnsmasqConfKeys.DhcpPxeVendor, + DnsmasqConfKeys.DhcpOptionPxe, ]), new SectionDef(SectionDnssec, "DNSSEC", [ DnsmasqConfKeys.Dnssec, DnsmasqConfKeys.DnssecCheckUnsigned, + DnsmasqConfKeys.DnssecNoTimecheck, + DnsmasqConfKeys.DnssecDebug, DnsmasqConfKeys.ProxyDnssec, + DnsmasqConfKeys.AddCpeId, + DnsmasqConfKeys.DnssecTimestamp, + DnsmasqConfKeys.DnssecLimits, DnsmasqConfKeys.TrustAnchor, ]), new SectionDef(SectionCache, "Cache", [ @@ -133,6 +174,12 @@ public static class EffectiveConfigSections DnsmasqConfKeys.MaxTtl, DnsmasqConfKeys.MaxCacheTtl, DnsmasqConfKeys.MinCacheTtl, + DnsmasqConfKeys.Dumpfile, + DnsmasqConfKeys.Dumpmask, + DnsmasqConfKeys.UseStaleCache, + DnsmasqConfKeys.AddMac, + DnsmasqConfKeys.AddSubnet, + DnsmasqConfKeys.Umbrella, ]), new SectionDef(SectionProcess, "Process & networking", [ DnsmasqConfKeys.NoPoll, @@ -159,6 +206,10 @@ public static class EffectiveConfigSections DnsmasqConfKeys.KeepInForeground, DnsmasqConfKeys.NoDaemon, DnsmasqConfKeys.Conntrack, + DnsmasqConfKeys.QuietDhcp, + DnsmasqConfKeys.QuietDhcp6, + DnsmasqConfKeys.QuietRa, + DnsmasqConfKeys.QuietTftp, ]), ]; diff --git a/src/DnsmasqWebUI/Infrastructure/Services/Dnsmasq/Config/ConfigSetCache.cs b/src/DnsmasqWebUI/Infrastructure/Services/Dnsmasq/Config/ConfigSetCache.cs index a9ea0e9..8b681ec 100644 --- a/src/DnsmasqWebUI/Infrastructure/Services/Dnsmasq/Config/ConfigSetCache.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/Dnsmasq/Config/ConfigSetCache.cs @@ -293,6 +293,23 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable var dhcpHostsfilePaths = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpHostsfile); var dhcpOptsfilePaths = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpOptsfile); var dhcpHostsdirPaths = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpHostsdir); + var connmarkAllowlistValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.ConnmarkAllowlist); + var caaRecordValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.CaaRecord); + var dnsRrValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DnsRr); + var synthDomainValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.SynthDomain); + var authZoneValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.AuthZone); + var authSoaValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.AuthSoa); + var authSecServersValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.AuthSecServers); + var authPeerValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.AuthPeer); + var dhcpRelayValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpRelay); + var dhcpCircuitidValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpCircuitid); + var dhcpRemoteidValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpRemoteid); + var dhcpSubscridValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpSubscrid); + var dhcpProxyValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpProxy); + var tagIfValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.TagIf); + var bridgeInterfaceValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.BridgeInterface); + var sharedNetworkValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.SharedNetwork); + var dhcpOptionPxeValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpOptionPxe); var expandHosts = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.ExpandHosts); var bogusPriv = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.BogusPriv); @@ -330,6 +347,24 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable var keepInForeground = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.KeepInForeground); var noDaemon = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.NoDaemon); var proxyDnssec = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.ProxyDnssec); + var connmarkAllowlistEnable = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.ConnmarkAllowlistEnable); + var noRoundRobin = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.NoRoundRobin); + var dnssecNoTimecheck = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DnssecNoTimecheck); + var dnssecDebug = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DnssecDebug); + var leasequery = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Leasequery); + var dhcpGenerateNames = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpGenerateNames); + var dhcpBroadcast = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpBroadcast); + var dhcpSequentialIp = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpSequentialIp); + var dhcpIgnoreClid = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpIgnoreClid); + var bootpDynamic = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.BootpDynamic); + var noPing = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.NoPing); + var scriptArp = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.ScriptArp); + var scriptOnRenewal = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.ScriptOnRenewal); + var dhcpNoOverride = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpNoOverride); + var quietDhcp = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.QuietDhcp); + var quietDhcp6 = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.QuietDhcp6); + var quietRa = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.QuietRa); + var quietTftp = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.QuietTftp); var dhcpLeaseFilePath = DnsmasqConfIncludeParser.GetDhcpLeaseFilePathFromConfigFiles(paths, pathToLines); @@ -391,6 +426,39 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable var dhcpScriptPath = string.IsNullOrWhiteSpace(dhcpScriptVal) ? null : DnsmasqConfIncludeParser.ResolvePath(dhcpScriptVal?.Trim(), dhcpScriptDir); var (mxTargetVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.MxTarget); var mxTarget = string.IsNullOrWhiteSpace(mxTargetVal) ? null : mxTargetVal.Trim(); + var (dnsForwardMaxVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DnsForwardMax); + var dnsForwardMax = TryParseInt(dnsForwardMaxVal); + var (dumpfileVal, dumpfileDir) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Dumpfile); + var dumpfilePath = string.IsNullOrWhiteSpace(dumpfileVal) ? null : DnsmasqConfIncludeParser.ResolvePath(dumpfileVal.Trim(), dumpfileDir); + var (dumpmaskVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Dumpmask); + var dumpmask = string.IsNullOrWhiteSpace(dumpmaskVal) ? null : dumpmaskVal.Trim(); + var (addCpeIdVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.AddCpeId); + var addCpeId = string.IsNullOrWhiteSpace(addCpeIdVal) ? null : addCpeIdVal.Trim(); + var (dnssecTimestampVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DnssecTimestamp); + var dnssecTimestamp = string.IsNullOrWhiteSpace(dnssecTimestampVal) ? null : dnssecTimestampVal.Trim(); + var (dnssecLimitsVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DnssecLimits); + var dnssecLimits = string.IsNullOrWhiteSpace(dnssecLimitsVal) ? null : dnssecLimitsVal.Trim(); + var (dhcpAlternatePortVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpAlternatePort); + var dhcpAlternatePort = string.IsNullOrWhiteSpace(dhcpAlternatePortVal) ? null : dhcpAlternatePortVal.Trim(); + var (dhcpDuidVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpDuid); + var dhcpDuid = string.IsNullOrWhiteSpace(dhcpDuidVal) ? null : dhcpDuidVal.Trim(); + var (dhcpLuascriptVal, dhcpLuascriptDir) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpLuascript); + var dhcpLuascriptPath = string.IsNullOrWhiteSpace(dhcpLuascriptVal) ? null : DnsmasqConfIncludeParser.ResolvePath(dhcpLuascriptVal?.Trim(), dhcpLuascriptDir); + var (dhcpScriptuserVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpScriptuser); + var dhcpScriptuser = string.IsNullOrWhiteSpace(dhcpScriptuserVal) ? null : dhcpScriptuserVal.Trim(); + var (dhcpPxeVendorVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpPxeVendor); + var dhcpPxeVendor = string.IsNullOrWhiteSpace(dhcpPxeVendorVal) ? null : dhcpPxeVendorVal.Trim(); + var (useStaleCacheVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.UseStaleCache); + var useStaleCache = useStaleCacheVal == null ? null : (string.IsNullOrWhiteSpace(useStaleCacheVal) ? "" : useStaleCacheVal.Trim()); + var (addMacVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.AddMac); + var addMac = addMacVal == null ? null : (string.IsNullOrWhiteSpace(addMacVal) ? "" : addMacVal.Trim()); + var (addSubnetVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.AddSubnet); + var addSubnet = addSubnetVal == null ? null : (string.IsNullOrWhiteSpace(addSubnetVal) ? "" : addSubnetVal.Trim()); + var (umbrellaVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Umbrella); + var umbrella = umbrellaVal == null ? null : (string.IsNullOrWhiteSpace(umbrellaVal) ? "" : umbrellaVal.Trim()); + var do0x20 = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Do0x20Encode); + var no0x20 = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.No0x20Encode); + var do0x20EncodeState = do0x20 ? ExplicitToggleState.Enabled : (no0x20 ? ExplicitToggleState.Disabled : ExplicitToggleState.Default); var conntrack = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Conntrack); return new EffectiveDnsmasqConfig( @@ -401,14 +469,18 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable domainValues, cnameValues, mxHostValues, srvValues, ptrRecordValues, txtRecordValues, naptrRecordValues, hostRecordValues, dynamicHostValues, interfaceNameValues, dhcpOptionForceLines, ipsetValues, nftsetValues, dhcpMacValues, dhcpNameMatchValues, dhcpIgnoreNamesValues, dhcpHostsfilePaths, dhcpOptsfilePaths, dhcpHostsdirPaths, + connmarkAllowlistValues, caaRecordValues, dnsRrValues, synthDomainValues, authZoneValues, authSoaValues, authSecServersValues, authPeerValues, + dhcpRelayValues, dhcpCircuitidValues, dhcpRemoteidValues, dhcpSubscridValues, dhcpProxyValues, tagIfValues, bridgeInterfaceValues, sharedNetworkValues, dhcpOptionPxeValues, expandHosts, bogusPriv, strictOrder, allServers, noResolv, domainNeeded, noPoll, bindInterfaces, bindDynamic, noNegcache, dnsLoopDetect, stopDnsRebind, rebindLocalhostOk, clearOnReload, filterwin2k, filterA, filterAaaa, localiseQueries, logDebug, dhcpAuthoritative, leasefileRo, enableTftp, tftpSecure, tftpNoFail, tftpNoBlocksize, dnssec, dnssecCheckUnsigned, readEthers, dhcpRapidCommit, localmx, selfmx, enableRa, logDhcp, keepInForeground, noDaemon, proxyDnssec, + connmarkAllowlistEnable, noRoundRobin, dnssecNoTimecheck, dnssecDebug, leasequery, dhcpGenerateNames, dhcpBroadcast, dhcpSequentialIp, dhcpIgnoreClid, bootpDynamic, noPing, scriptArp, scriptOnRenewal, dhcpNoOverride, quietDhcp, quietDhcp6, quietRa, quietTftp, dhcpLeaseFilePath, cacheSize, port, localTtl, pidFilePath, user, group, logFacility, logQueries, authTtl, ednsPacketMax, queryPort, portLimit, minPort, maxPort, logAsync, localService, dhcpLeaseMax, negTtl, maxTtl, maxCacheTtl, minCacheTtl, dhcpTtl, tftpRootPath, pxePrompt, enableDbus, enableUbus, fastDnsRetry, - dhcpScriptPath, mxTarget, conntrack + dhcpScriptPath, mxTarget, dnsForwardMax, dumpfilePath, dumpmask, addCpeId, dnssecTimestamp, dnssecLimits, dhcpAlternatePort, dhcpDuid, dhcpLuascriptPath, dhcpScriptuser, dhcpPxeVendor, + useStaleCache, addMac, addSubnet, umbrella, do0x20EncodeState, conntrack ); } @@ -466,6 +538,23 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable var dhcpHostsfileWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpHostsfile, managedFilePath); var dhcpOptsfileWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpOptsfile, managedFilePath); var dhcpHostsdirWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpHostsdir, managedFilePath); + var connmarkAllowlistWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.ConnmarkAllowlist, managedFilePath); + var caaRecordWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.CaaRecord, managedFilePath); + var dnsRrWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DnsRr, managedFilePath); + var synthDomainWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.SynthDomain, managedFilePath); + var authZoneWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.AuthZone, managedFilePath); + var authSoaWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.AuthSoa, managedFilePath); + var authSecServersWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.AuthSecServers, managedFilePath); + var authPeerWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.AuthPeer, managedFilePath); + var dhcpRelayWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpRelay, managedFilePath); + var dhcpCircuitidWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpCircuitid, managedFilePath); + var dhcpRemoteidWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpRemoteid, managedFilePath); + var dhcpSubscridWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpSubscrid, managedFilePath); + var dhcpProxyWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpProxy, managedFilePath); + var tagIfWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.TagIf, managedFilePath); + var bridgeInterfaceWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.BridgeInterface, managedFilePath); + var sharedNetworkWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.SharedNetwork, managedFilePath); + var dhcpOptionPxeWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpOptionPxe, managedFilePath); var (_, expandHostsSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.ExpandHosts, managedFilePath); var (_, bogusPrivSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.BogusPriv, managedFilePath); @@ -503,6 +592,24 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable var (_, keepInForegroundSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.KeepInForeground, managedFilePath); var (_, noDaemonSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.NoDaemon, managedFilePath); var (_, proxyDnssecSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.ProxyDnssec, managedFilePath); + var (_, connmarkAllowlistEnableSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.ConnmarkAllowlistEnable, managedFilePath); + var (_, noRoundRobinSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.NoRoundRobin, managedFilePath); + var (_, dnssecNoTimecheckSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DnssecNoTimecheck, managedFilePath); + var (_, dnssecDebugSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DnssecDebug, managedFilePath); + var (_, leasequerySource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Leasequery, managedFilePath); + var (_, dhcpGenerateNamesSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpGenerateNames, managedFilePath); + var (_, dhcpBroadcastSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpBroadcast, managedFilePath); + var (_, dhcpSequentialIpSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpSequentialIp, managedFilePath); + var (_, dhcpIgnoreClidSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpIgnoreClid, managedFilePath); + var (_, bootpDynamicSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.BootpDynamic, managedFilePath); + var (_, noPingSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.NoPing, managedFilePath); + var (_, scriptArpSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.ScriptArp, managedFilePath); + var (_, scriptOnRenewalSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.ScriptOnRenewal, managedFilePath); + var (_, dhcpNoOverrideSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpNoOverride, managedFilePath); + var (_, quietDhcpSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.QuietDhcp, managedFilePath); + var (_, quietDhcp6Source) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.QuietDhcp6, managedFilePath); + var (_, quietRaSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.QuietRa, managedFilePath); + var (_, quietTftpSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.QuietTftp, managedFilePath); var (_, dhcpLeaseFilePathSource) = DnsmasqConfIncludeParser.GetDhcpLeaseFilePathFromConfigFilesWithSource(paths, pathToLines, managedFilePath); var (_, cacheSizeSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.CacheSize, managedFilePath); @@ -534,6 +641,24 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable var (_, fastDnsRetrySource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.FastDnsRetry, managedFilePath); var (_, dhcpScriptPathSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpScript, managedFilePath); var (_, mxTargetSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.MxTarget, managedFilePath); + var (_, dnsForwardMaxSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DnsForwardMax, managedFilePath); + var (_, dumpfilePathSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Dumpfile, managedFilePath); + var (_, dumpmaskSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Dumpmask, managedFilePath); + var (_, addCpeIdSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.AddCpeId, managedFilePath); + var (_, dnssecTimestampSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DnssecTimestamp, managedFilePath); + var (_, dnssecLimitsSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DnssecLimits, managedFilePath); + var (_, dhcpAlternatePortSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpAlternatePort, managedFilePath); + var (_, dhcpDuidSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpDuid, managedFilePath); + var (_, dhcpLuascriptPathSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpLuascript, managedFilePath); + var (_, dhcpScriptuserSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpScriptuser, managedFilePath); + var (_, dhcpPxeVendorSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpPxeVendor, managedFilePath); + var (_, useStaleCacheSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.UseStaleCache, managedFilePath); + var (_, addMacSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.AddMac, managedFilePath); + var (_, addSubnetSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.AddSubnet, managedFilePath); + var (_, umbrellaSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Umbrella, managedFilePath); + var (do0x20Set, do0x20EncodeSourceA) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Do0x20Encode, managedFilePath); + var (no0x20Set, no0x20EncodeSourceB) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.No0x20Encode, managedFilePath); + var do0x20EncodeSource = do0x20Set ? do0x20EncodeSourceA : (no0x20Set ? no0x20EncodeSourceB : null); var (_, conntrackSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Conntrack, managedFilePath); return new EffectiveConfigSources( @@ -587,6 +712,23 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable dhcpHostsfileWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), dhcpOptsfileWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), dhcpHostsdirWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), + connmarkAllowlistWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), + caaRecordWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), + dnsRrWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), + synthDomainWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), + authZoneWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), + authSoaWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), + authSecServersWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), + authPeerWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), + dhcpRelayWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), + dhcpCircuitidWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), + dhcpRemoteidWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), + dhcpSubscridWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), + dhcpProxyWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), + tagIfWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), + bridgeInterfaceWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), + sharedNetworkWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), + dhcpOptionPxeWithSource.Select(t => new ValueWithSource(t.Value, t.Source)).ToList(), expandHostsSource, bogusPrivSource, strictOrderSource, @@ -623,6 +765,24 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable keepInForegroundSource, noDaemonSource, proxyDnssecSource, + connmarkAllowlistEnableSource, + noRoundRobinSource, + dnssecNoTimecheckSource, + dnssecDebugSource, + leasequerySource, + dhcpGenerateNamesSource, + dhcpBroadcastSource, + dhcpSequentialIpSource, + dhcpIgnoreClidSource, + bootpDynamicSource, + noPingSource, + scriptArpSource, + scriptOnRenewalSource, + dhcpNoOverrideSource, + quietDhcpSource, + quietDhcp6Source, + quietRaSource, + quietTftpSource, dhcpLeaseFilePathSource, cacheSizeSource, portSource, @@ -653,6 +813,22 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable fastDnsRetrySource, dhcpScriptPathSource, mxTargetSource, + dnsForwardMaxSource, + dumpfilePathSource, + dumpmaskSource, + addCpeIdSource, + dnssecTimestampSource, + dnssecLimitsSource, + dhcpAlternatePortSource, + dhcpDuidSource, + dhcpLuascriptPathSource, + dhcpScriptuserSource, + dhcpPxeVendorSource, + useStaleCacheSource, + addMacSource, + addSubnetSource, + umbrellaSource, + do0x20EncodeSource, conntrackSource ); } @@ -707,17 +883,23 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable DhcpOptionForceLines: Array.Empty(), IpsetValues: Array.Empty(), NftsetValues: Array.Empty(), DhcpMacValues: Array.Empty(), DhcpNameMatchValues: Array.Empty(), DhcpIgnoreNamesValues: Array.Empty(), DhcpHostsfilePaths: Array.Empty(), DhcpOptsfilePaths: Array.Empty(), DhcpHostsdirPaths: Array.Empty(), + ConnmarkAllowlistValues: Array.Empty(), CaaRecordValues: Array.Empty(), DnsRrValues: Array.Empty(), SynthDomainValues: Array.Empty(), + AuthZoneValues: Array.Empty(), AuthSoaValues: Array.Empty(), AuthSecServersValues: Array.Empty(), AuthPeerValues: Array.Empty(), + DhcpRelayValues: Array.Empty(), DhcpCircuitidValues: Array.Empty(), DhcpRemoteidValues: Array.Empty(), DhcpSubscridValues: Array.Empty(), DhcpProxyValues: Array.Empty(), + TagIfValues: Array.Empty(), BridgeInterfaceValues: Array.Empty(), SharedNetworkValues: Array.Empty(), DhcpOptionPxeValues: Array.Empty(), ExpandHosts: false, BogusPriv: false, StrictOrder: false, AllServers: false, NoResolv: false, DomainNeeded: false, NoPoll: false, BindInterfaces: false, BindDynamic: false, NoNegcache: false, DnsLoopDetect: false, StopDnsRebind: false, RebindLocalhostOk: false, ClearOnReload: false, Filterwin2k: false, FilterA: false, FilterAaaa: false, LocaliseQueries: false, LogDebug: false, DhcpAuthoritative: false, LeasefileRo: false, EnableTftp: false, TftpSecure: false, TftpNoFail: false, TftpNoBlocksize: false, Dnssec: false, DnssecCheckUnsigned: false, ReadEthers: false, DhcpRapidCommit: false, Localmx: false, Selfmx: false, EnableRa: false, LogDhcp: false, KeepInForeground: false, NoDaemon: false, ProxyDnssec: false, + ConnmarkAllowlistEnable: false, NoRoundRobin: false, DnssecNoTimecheck: false, DnssecDebug: false, Leasequery: false, DhcpGenerateNames: false, DhcpBroadcast: false, DhcpSequentialIp: false, DhcpIgnoreClid: false, BootpDynamic: false, NoPing: false, ScriptArp: false, ScriptOnRenewal: false, DhcpNoOverride: false, QuietDhcp: false, QuietDhcp6: false, QuietRa: false, QuietTftp: false, DhcpLeaseFilePath: null, CacheSize: null, Port: null, LocalTtl: null, PidFilePath: null, User: null, Group: null, LogFacility: null, LogQueries: null, AuthTtl: null, EdnsPacketMax: null, QueryPort: null, PortLimit: null, MinPort: null, MaxPort: null, LogAsync: null, LocalService: null, DhcpLeaseMax: null, NegTtl: null, MaxTtl: null, MaxCacheTtl: null, MinCacheTtl: null, DhcpTtl: null, TftpRootPath: null, PxePrompt: null, EnableDbus: null, EnableUbus: null, FastDnsRetry: null, - DhcpScriptPath: null, MxTarget: null, Conntrack: false + DhcpScriptPath: null, MxTarget: null, DnsForwardMax: null, DumpfilePath: null, Dumpmask: null, AddCpeId: null, DnssecTimestamp: null, DnssecLimits: null, DhcpAlternatePort: null, DhcpDuid: null, DhcpLuascriptPath: null, DhcpScriptuser: null, DhcpPxeVendor: null, + UseStaleCache: null, AddMac: null, AddSubnet: null, Umbrella: null, Do0x20EncodeState: ExplicitToggleState.Default, Conntrack: false ); private static EffectiveConfigSources CreateDefaultEffectiveConfigSources() => @@ -742,17 +924,23 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable DhcpOptionForceLines: Array.Empty(), IpsetValues: Array.Empty(), NftsetValues: Array.Empty(), DhcpMacValues: Array.Empty(), DhcpNameMatchValues: Array.Empty(), DhcpIgnoreNamesValues: Array.Empty(), DhcpHostsfilePaths: Array.Empty(), DhcpOptsfilePaths: Array.Empty(), DhcpHostsdirPaths: Array.Empty(), + ConnmarkAllowlistValues: Array.Empty(), CaaRecordValues: Array.Empty(), DnsRrValues: Array.Empty(), SynthDomainValues: Array.Empty(), + AuthZoneValues: Array.Empty(), AuthSoaValues: Array.Empty(), AuthSecServersValues: Array.Empty(), AuthPeerValues: Array.Empty(), + DhcpRelayValues: Array.Empty(), DhcpCircuitidValues: Array.Empty(), DhcpRemoteidValues: Array.Empty(), DhcpSubscridValues: Array.Empty(), DhcpProxyValues: Array.Empty(), + TagIfValues: Array.Empty(), BridgeInterfaceValues: Array.Empty(), SharedNetworkValues: Array.Empty(), DhcpOptionPxeValues: Array.Empty(), ExpandHosts: null, BogusPriv: null, StrictOrder: null, AllServers: null, NoResolv: null, DomainNeeded: null, NoPoll: null, BindInterfaces: null, BindDynamic: null, NoNegcache: null, DnsLoopDetect: null, StopDnsRebind: null, RebindLocalhostOk: null, ClearOnReload: null, Filterwin2k: null, FilterA: null, FilterAaaa: null, LocaliseQueries: null, LogDebug: null, DhcpAuthoritative: null, LeasefileRo: null, EnableTftp: null, TftpSecure: null, TftpNoFail: null, TftpNoBlocksize: null, Dnssec: null, DnssecCheckUnsigned: null, ReadEthers: null, DhcpRapidCommit: null, Localmx: null, Selfmx: null, EnableRa: null, LogDhcp: null, KeepInForeground: null, NoDaemon: null, ProxyDnssec: null, + ConnmarkAllowlistEnable: null, NoRoundRobin: null, DnssecNoTimecheck: null, DnssecDebug: null, Leasequery: null, DhcpGenerateNames: null, DhcpBroadcast: null, DhcpSequentialIp: null, DhcpIgnoreClid: null, BootpDynamic: null, NoPing: null, ScriptArp: null, ScriptOnRenewal: null, DhcpNoOverride: null, QuietDhcp: null, QuietDhcp6: null, QuietRa: null, QuietTftp: null, DhcpLeaseFilePath: null, CacheSize: null, Port: null, LocalTtl: null, PidFilePath: null, User: null, Group: null, LogFacility: null, LogQueries: null, AuthTtl: null, EdnsPacketMax: null, QueryPort: null, PortLimit: null, MinPort: null, MaxPort: null, LogAsync: null, LocalService: null, DhcpLeaseMax: null, NegTtl: null, MaxTtl: null, MaxCacheTtl: null, MinCacheTtl: null, DhcpTtl: null, TftpRootPath: null, PxePrompt: null, EnableDbus: null, EnableUbus: null, FastDnsRetry: null, - DhcpScriptPath: null, MxTarget: null, Conntrack: null + DhcpScriptPath: null, MxTarget: null, DnsForwardMax: null, DumpfilePath: null, Dumpmask: null, AddCpeId: null, DnssecTimestamp: null, DnssecLimits: null, DhcpAlternatePort: null, DhcpDuid: null, DhcpLuascriptPath: null, DhcpScriptuser: null, DhcpPxeVendor: null, + UseStaleCache: null, AddMac: null, AddSubnet: null, Umbrella: null, Do0x20Encode: null, Conntrack: null ); private static int? TryParseInt(string? value) diff --git a/src/DnsmasqWebUI/Infrastructure/Services/Dnsmasq/Config/DnsmasqConfigService.cs b/src/DnsmasqWebUI/Infrastructure/Services/Dnsmasq/Config/DnsmasqConfigService.cs index 391aef8..ffa1040 100644 --- a/src/DnsmasqWebUI/Infrastructure/Services/Dnsmasq/Config/DnsmasqConfigService.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/Dnsmasq/Config/DnsmasqConfigService.cs @@ -231,8 +231,7 @@ public class DnsmasqConfigService : IDnsmasqConfigService var maxLineNumber = list.Count > 0 ? list.Max(l => l.LineNumber) : 0; foreach (var c in changes) { - var behavior = EffectiveConfigParserBehaviorMap.GetBehavior(c.OptionName); - var isFlag = behavior == EffectiveConfigParserBehavior.Flag; + var writeBehavior = EffectiveConfigWriteBehaviorMap.GetBehavior(c.OptionName); var confKey = c.OptionName; bool MatchesOption(DnsmasqConfLine line) @@ -242,6 +241,43 @@ public class DnsmasqConfigService : IDnsmasqConfigService return kv != null && string.Equals(kv.Value.key, confKey, StringComparison.Ordinal); } + if (writeBehavior == EffectiveConfigWriteBehavior.KeyOnlyOrValue) + { + RemoveAllMatchingLines(list, MatchesOption); + var value = c.NewValue as string; + if (value is null) + continue; + var lineText = value.Length == 0 ? confKey : $"{confKey}={value.Trim()}"; + list.Add(new OtherLine { LineNumber = maxLineNumber + 1, RawLine = lineText }); + maxLineNumber++; + continue; + } + + if (writeBehavior == EffectiveConfigWriteBehavior.InversePair) + { + var pair = EffectiveConfigWriteBehaviorMap.GetInversePairKeys(confKey); + if (pair is null) + continue; + var (pairKeyA, pairKeyB) = pair.Value; + bool MatchesPair(DnsmasqConfLine line) + { + var raw = DnsmasqConfFileLineParser.ToLine(line); + var kv = DnsmasqConfDirectiveParser.TryParseKeyValue(raw); + return kv != null && (string.Equals(kv.Value.key, pairKeyA, StringComparison.Ordinal) || string.Equals(kv.Value.key, pairKeyB, StringComparison.Ordinal)); + } + RemoveAllMatchingLines(list, MatchesPair); + if (c.NewValue is ExplicitToggleState state && state != ExplicitToggleState.Default) + { + var lineKey = state == ExplicitToggleState.Enabled ? pairKeyA : pairKeyB; + list.Add(new OtherLine { LineNumber = maxLineNumber + 1, RawLine = lineKey }); + maxLineNumber++; + } + continue; + } + + var behavior = EffectiveConfigParserBehaviorMap.GetBehavior(c.OptionName); + var isFlag = behavior == EffectiveConfigParserBehavior.Flag; + if (behavior == EffectiveConfigParserBehavior.Multi && TryGetMultiValues(c.NewValue, out var values)) { IReadOnlyList readonlyValues = readonlyByOption.TryGetValue(confKey, out var listValues) ? listValues : Array.Empty(); @@ -299,6 +335,15 @@ public class DnsmasqConfigService : IDnsmasqConfigService await WriteManagedConfigAsync(list, ct); } + private static void RemoveAllMatchingLines(List list, Predicate match) + { + for (var i = list.Count - 1; i >= 0; i--) + { + if (match(list[i])) + list.RemoveAt(i); + } + } + /// /// Keeps only values that should be written to managed config by subtracting values provided by /// non-managed files (multiset subtraction, order preserved). diff --git a/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfig/EffectiveConfigRenderFragmentRegistry.cs b/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfig/EffectiveConfigRenderFragmentRegistry.cs index 960e104..a835547 100644 --- a/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfig/EffectiveConfigRenderFragmentRegistry.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfig/EffectiveConfigRenderFragmentRegistry.cs @@ -34,6 +34,7 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm RegisterInteger(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.PortLimit, max: 65535); RegisterInteger(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.MinPort, min: 1, max: 65535); RegisterInteger(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.MaxPort, min: 1, max: 65535); + RegisterInteger(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.DnsForwardMax, min: 1); RegisterInteger(EffectiveConfigFieldBuilder.SectionCache, DnsmasqConfKeys.CacheSize, min: 0); RegisterInteger(EffectiveConfigFieldBuilder.SectionCache, DnsmasqConfKeys.LocalTtl, unit: "seconds"); RegisterInteger(EffectiveConfigFieldBuilder.SectionCache, DnsmasqConfKeys.NegTtl, unit: "seconds"); @@ -82,6 +83,33 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm RegisterFlag(EffectiveConfigFieldBuilder.SectionProcess, DnsmasqConfKeys.NoDaemon); RegisterFlag(EffectiveConfigFieldBuilder.SectionProcess, DnsmasqConfKeys.Conntrack); RegisterFlag(EffectiveConfigFieldBuilder.SectionDnssec, DnsmasqConfKeys.ProxyDnssec); + RegisterFlag(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.ConnmarkAllowlistEnable); + RegisterFlag(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.NoRoundRobin); + RegisterFlag(EffectiveConfigFieldBuilder.SectionDnssec, DnsmasqConfKeys.DnssecNoTimecheck); + RegisterFlag(EffectiveConfigFieldBuilder.SectionDnssec, DnsmasqConfKeys.DnssecDebug); + RegisterFlag(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.Leasequery); + RegisterFlag(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpGenerateNames); + RegisterFlag(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpBroadcast); + RegisterFlag(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpSequentialIp); + RegisterFlag(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpIgnoreClid); + RegisterFlag(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.BootpDynamic); + RegisterFlag(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.NoPing); + RegisterFlag(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.ScriptArp); + RegisterFlag(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.ScriptOnRenewal); + RegisterFlag(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpNoOverride); + RegisterFlag(EffectiveConfigFieldBuilder.SectionProcess, DnsmasqConfKeys.QuietDhcp); + RegisterFlag(EffectiveConfigFieldBuilder.SectionProcess, DnsmasqConfKeys.QuietDhcp6); + RegisterFlag(EffectiveConfigFieldBuilder.SectionProcess, DnsmasqConfKeys.QuietRa); + RegisterFlag(EffectiveConfigFieldBuilder.SectionProcess, DnsmasqConfKeys.QuietTftp); + + // do-0x20-encode / no-0x20-encode: tri-state dropdown (Default / Enabled / Disabled). + RegisterComponent(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.Do0x20Encode, typeof(Do0x20EncodeDisplay)); + + // Key-only or key=value options: checkbox (On) + optional value input. + RegisterComponent(EffectiveConfigFieldBuilder.SectionCache, DnsmasqConfKeys.UseStaleCache, typeof(KeyOnlyOrValueDisplay)); + RegisterComponent(EffectiveConfigFieldBuilder.SectionCache, DnsmasqConfKeys.AddMac, typeof(KeyOnlyOrValueDisplay)); + RegisterComponent(EffectiveConfigFieldBuilder.SectionCache, DnsmasqConfKeys.AddSubnet, typeof(KeyOnlyOrValueDisplay)); + RegisterComponent(EffectiveConfigFieldBuilder.SectionCache, DnsmasqConfKeys.Umbrella, typeof(KeyOnlyOrValueDisplay)); // log-queries: dropdown (Off / On / extra / proto / auth). RegisterComponent(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.LogQueries, typeof(LogQueriesDisplay)); @@ -113,6 +141,7 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.FilterRr); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.Ipset); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.Nftset); + RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.ConnmarkAllowlist); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDnsRecords, DnsmasqConfKeys.Domain); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDnsRecords, DnsmasqConfKeys.Cname); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDnsRecords, DnsmasqConfKeys.MxHost); @@ -123,6 +152,13 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDnsRecords, DnsmasqConfKeys.HostRecord); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDnsRecords, DnsmasqConfKeys.DynamicHost); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDnsRecords, DnsmasqConfKeys.InterfaceName); + RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDnsRecords, DnsmasqConfKeys.CaaRecord); + RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDnsRecords, DnsmasqConfKeys.DnsRr); + RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDnsRecords, DnsmasqConfKeys.SynthDomain); + RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDnsRecords, DnsmasqConfKeys.AuthZone); + RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDnsRecords, DnsmasqConfKeys.AuthSoa); + RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDnsRecords, DnsmasqConfKeys.AuthSecServers); + RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDnsRecords, DnsmasqConfKeys.AuthPeer); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpRange); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpHost); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpOption); @@ -134,6 +170,14 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpHostsfile); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpOptsfile); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpHostsdir); + RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpRelay); + RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpCircuitid); + RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpRemoteid); + RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpSubscrid); + RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpProxy); + RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.TagIf); + RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.BridgeInterface); + RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.SharedNetwork); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpBoot); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpIgnore); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpVendorclass); @@ -141,6 +185,7 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.RaParam); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.Slaac); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionTftpPxe, DnsmasqConfKeys.PxeService); + RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionTftpPxe, DnsmasqConfKeys.DhcpOptionPxe); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionDnssec, DnsmasqConfKeys.TrustAnchor); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionCache, DnsmasqConfKeys.CacheRr); RegisterMultiDescriptor(EffectiveConfigFieldBuilder.SectionProcess, DnsmasqConfKeys.Interface); diff --git a/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfig/Validation/EffectiveConfigCrossOptionEvaluator.cs b/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfig/Validation/EffectiveConfigCrossOptionEvaluator.cs index e8fe1ec..2557e51 100644 --- a/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfig/Validation/EffectiveConfigCrossOptionEvaluator.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfig/Validation/EffectiveConfigCrossOptionEvaluator.cs @@ -19,7 +19,7 @@ public static class EffectiveConfigCrossOptionEvaluator { var issues = new List(); - var noResolv = GetEffectiveNoResolv(status, pending); + var noResolv = GetEffectiveBool(status, pending, DnsmasqConfKeys.NoResolv, s => s?.EffectiveConfig?.NoResolv ?? false); var serverValues = GetEffectiveServerValues(status, pending); // no-resolv set with no upstream servers: DNS may not work @@ -33,17 +33,51 @@ public static class EffectiveConfigCrossOptionEvaluator ItemIndex: null)); } + // conntrack cannot be combined with query-port (dnsmasq man page) + var conntrack = GetEffectiveBool(status, pending, DnsmasqConfKeys.Conntrack, s => s?.EffectiveConfig?.Conntrack ?? false); + var queryPort = GetEffectiveInt(status, pending, DnsmasqConfKeys.QueryPort, s => s?.EffectiveConfig?.QueryPort); + if (conntrack && queryPort is not null) + { + issues.Add(new FieldIssue( + $"{EffectiveConfigSections.SectionResolver}:{DnsmasqConfKeys.QueryPort}", + "query-port cannot be combined with conntrack.", + FieldIssueSeverity.Error, + null)); + } + return issues; } - private static bool GetEffectiveNoResolv(DnsmasqServiceStatus? status, IReadOnlyList pending) + private static bool GetEffectiveBool( + DnsmasqServiceStatus? status, + IReadOnlyList? pending, + string optionName, + Func fromConfig) { - var fromConfig = status?.EffectiveConfig?.NoResolv ?? false; - var pendingChange = pending?.FirstOrDefault(c => - string.Equals(c.OptionName, DnsmasqConfKeys.NoResolv, StringComparison.Ordinal)); - if (pendingChange?.NewValue is bool b) - return b; - return fromConfig; + var pendingChange = pending?.FirstOrDefault(c => string.Equals(c.OptionName, optionName, StringComparison.Ordinal)); + if (pendingChange != null) + { + if (pendingChange.NewValue is bool b) + return b; + return false; // pending change to clear or invalid; treat as off for cross-option purposes + } + return fromConfig(status); + } + + private static int? GetEffectiveInt( + DnsmasqServiceStatus? status, + IReadOnlyList? pending, + string optionName, + Func fromConfig) + { + var pendingChange = pending?.FirstOrDefault(c => string.Equals(c.OptionName, optionName, StringComparison.Ordinal)); + if (pendingChange != null) + { + if (pendingChange.NewValue is int i) + return i; + return null; // pending change to clear the value; use null so conntrack+query-port rule no longer blocks + } + return fromConfig(status); } private static IReadOnlyList? GetEffectiveServerValues(DnsmasqServiceStatus? status, IReadOnlyList pending) diff --git a/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigSources.cs b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigSources.cs index 06b609d..4d726d8 100644 --- a/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigSources.cs +++ b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigSources.cs @@ -65,6 +65,23 @@ public record EffectiveConfigSources( IReadOnlyList DhcpHostsfilePaths, IReadOnlyList DhcpOptsfilePaths, IReadOnlyList DhcpHostsdirPaths, + IReadOnlyList ConnmarkAllowlistValues, + IReadOnlyList CaaRecordValues, + IReadOnlyList DnsRrValues, + IReadOnlyList SynthDomainValues, + IReadOnlyList AuthZoneValues, + IReadOnlyList AuthSoaValues, + IReadOnlyList AuthSecServersValues, + IReadOnlyList AuthPeerValues, + IReadOnlyList DhcpRelayValues, + IReadOnlyList DhcpCircuitidValues, + IReadOnlyList DhcpRemoteidValues, + IReadOnlyList DhcpSubscridValues, + IReadOnlyList DhcpProxyValues, + IReadOnlyList TagIfValues, + IReadOnlyList BridgeInterfaceValues, + IReadOnlyList SharedNetworkValues, + IReadOnlyList DhcpOptionPxeValues, // --- Flags (set if any occurrence; source = first file that set it, so we know if readonly) --- ConfigValueSource? ExpandHosts, @@ -103,6 +120,24 @@ public record EffectiveConfigSources( ConfigValueSource? KeepInForeground, ConfigValueSource? NoDaemon, ConfigValueSource? ProxyDnssec, + ConfigValueSource? ConnmarkAllowlistEnable, + ConfigValueSource? NoRoundRobin, + ConfigValueSource? DnssecNoTimecheck, + ConfigValueSource? DnssecDebug, + ConfigValueSource? Leasequery, + ConfigValueSource? DhcpGenerateNames, + ConfigValueSource? DhcpBroadcast, + ConfigValueSource? DhcpSequentialIp, + ConfigValueSource? DhcpIgnoreClid, + ConfigValueSource? BootpDynamic, + ConfigValueSource? NoPing, + ConfigValueSource? ScriptArp, + ConfigValueSource? ScriptOnRenewal, + ConfigValueSource? DhcpNoOverride, + ConfigValueSource? QuietDhcp, + ConfigValueSource? QuietDhcp6, + ConfigValueSource? QuietRa, + ConfigValueSource? QuietTftp, // --- Single-value (last occurrence wins; source = file that set the last value) --- ConfigValueSource? DhcpLeaseFilePath, @@ -135,5 +170,21 @@ public record EffectiveConfigSources( ConfigValueSource? FastDnsRetry, ConfigValueSource? DhcpScriptPath, ConfigValueSource? MxTarget, + ConfigValueSource? DnsForwardMax, + ConfigValueSource? DumpfilePath, + ConfigValueSource? Dumpmask, + ConfigValueSource? AddCpeId, + ConfigValueSource? DnssecTimestamp, + ConfigValueSource? DnssecLimits, + ConfigValueSource? DhcpAlternatePort, + ConfigValueSource? DhcpDuid, + ConfigValueSource? DhcpLuascriptPath, + ConfigValueSource? DhcpScriptuser, + ConfigValueSource? DhcpPxeVendor, + ConfigValueSource? UseStaleCache, + ConfigValueSource? AddMac, + ConfigValueSource? AddSubnet, + ConfigValueSource? Umbrella, + ConfigValueSource? Do0x20Encode, ConfigValueSource? Conntrack ); diff --git a/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveDnsmasqConfig.cs b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveDnsmasqConfig.cs index d4823cb..5d968eb 100644 --- a/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveDnsmasqConfig.cs +++ b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveDnsmasqConfig.cs @@ -69,6 +69,23 @@ public record EffectiveDnsmasqConfig( IReadOnlyList DhcpHostsfilePaths, IReadOnlyList DhcpOptsfilePaths, IReadOnlyList DhcpHostsdirPaths, + IReadOnlyList ConnmarkAllowlistValues, + IReadOnlyList CaaRecordValues, + IReadOnlyList DnsRrValues, + IReadOnlyList SynthDomainValues, + IReadOnlyList AuthZoneValues, + IReadOnlyList AuthSoaValues, + IReadOnlyList AuthSecServersValues, + IReadOnlyList AuthPeerValues, + IReadOnlyList DhcpRelayValues, + IReadOnlyList DhcpCircuitidValues, + IReadOnlyList DhcpRemoteidValues, + IReadOnlyList DhcpSubscridValues, + IReadOnlyList DhcpProxyValues, + IReadOnlyList TagIfValues, + IReadOnlyList BridgeInterfaceValues, + IReadOnlyList SharedNetworkValues, + IReadOnlyList DhcpOptionPxeValues, // --- Boolean flags (set if any file contains the option) --- bool ExpandHosts, @@ -107,6 +124,24 @@ public record EffectiveDnsmasqConfig( bool KeepInForeground, bool NoDaemon, bool ProxyDnssec, + bool ConnmarkAllowlistEnable, + bool NoRoundRobin, + bool DnssecNoTimecheck, + bool DnssecDebug, + bool Leasequery, + bool DhcpGenerateNames, + bool DhcpBroadcast, + bool DhcpSequentialIp, + bool DhcpIgnoreClid, + bool BootpDynamic, + bool NoPing, + bool ScriptArp, + bool ScriptOnRenewal, + bool DhcpNoOverride, + bool QuietDhcp, + bool QuietDhcp6, + bool QuietRa, + bool QuietTftp, // --- Single-value options (last occurrence wins; null = not set, dnsmasq default) --- string? DhcpLeaseFilePath, @@ -139,6 +174,22 @@ public record EffectiveDnsmasqConfig( string? FastDnsRetry, string? DhcpScriptPath, string? MxTarget, + int? DnsForwardMax, + string? DumpfilePath, + string? Dumpmask, + string? AddCpeId, + string? DnssecTimestamp, + string? DnssecLimits, + string? DhcpAlternatePort, + string? DhcpDuid, + string? DhcpLuascriptPath, + string? DhcpScriptuser, + string? DhcpPxeVendor, + string? UseStaleCache, + string? AddMac, + string? AddSubnet, + string? Umbrella, + ExplicitToggleState Do0x20EncodeState, bool Conntrack ) { diff --git a/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/ExplicitToggleState.cs b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/ExplicitToggleState.cs new file mode 100644 index 0000000..b0e9d1f --- /dev/null +++ b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/ExplicitToggleState.cs @@ -0,0 +1,12 @@ +namespace DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; + +/// +/// Tri-state for inverse-pair options (e.g. do-0x20-encode / no-0x20-encode). +/// Default = not set (remove both lines); Enabled = write the positive key; Disabled = write the negative key. +/// +public enum ExplicitToggleState +{ + Default, + Enabled, + Disabled, +} diff --git a/src/DnsmasqWebUI/wwwroot/option-help/add-cpe-id.html b/src/DnsmasqWebUI/wwwroot/option-help/add-cpe-id.html new file mode 100644 index 0000000..af2f1f5 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/add-cpe-id.html @@ -0,0 +1,3 @@ +
--add-cpe-id=<string>
+
Add an arbitrary identifying string to DNS queries which are +forwarded upstream.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/add-mac.html b/src/DnsmasqWebUI/wwwroot/option-help/add-mac.html new file mode 100644 index 0000000..1d17ddc --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/add-mac.html @@ -0,0 +1,10 @@ +
--add-mac[=base64|text]
+
Add the MAC address of the requestor to DNS queries which are +forwarded upstream. This may be used to DNS filtering by the upstream +server. The MAC address can only be added if the requestor is on the same +subnet as the dnsmasq server. Note that the mechanism used to achieve this (an EDNS0 option) +is not yet standardised, so this should be considered +experimental. Also note that exposing MAC addresses in this way may +have security and privacy implications. The warning about caching +given for --add-subnet applies to --add-mac too. An alternative encoding of the +MAC, as base64, is enabled by adding the "base64" parameter and a human-readable encoding of hex-and-colons is enabled by added the "text" parameter.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/add-subnet.html b/src/DnsmasqWebUI/wwwroot/option-help/add-subnet.html new file mode 100644 index 0000000..77b54e5 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/add-subnet.html @@ -0,0 +1,23 @@ +
--add-subnet[[=[<IPv4 address>/]<IPv4 prefix length>][,[<IPv6 address>/]<IPv6 prefix length>]]
+
Add a subnet address to the DNS queries which are forwarded +upstream. If an address is specified in the flag, it will be used, +otherwise, the address of the requestor will be used. The amount of +the address forwarded depends on the prefix length parameter: 32 (128 +for IPv6) forwards the whole address, zero forwards none of it but +still marks the request so that no upstream nameserver will add client +address information either. The default is zero for both IPv4 and +IPv6. Note that upstream nameservers may be configured to return +different results based on this information, but the dnsmasq cache +does not take account. Caching is therefore disabled for such replies, +unless the subnet address being added is constant. +

+For example, +--add-subnet=24,96 + +will add the /24 and /96 subnets of the requestor for IPv4 and IPv6 requestors, respectively. +--add-subnet=1.2.3.4/24 + +will add 1.2.3.0/24 for IPv4 requestors and ::/0 for IPv6 requestors. +--add-subnet=1.2.3.4/24,1.2.3.4/24 + +will add 1.2.3.0/24 for both IPv4 and IPv6 requestors.

\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/auth-peer.html b/src/DnsmasqWebUI/wwwroot/option-help/auth-peer.html new file mode 100644 index 0000000..b1c299e --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/auth-peer.html @@ -0,0 +1,12 @@ +
--auth-peer=<ip-address>[,<ip-address>[,<ip-address>...]]
+
Specify the addresses of secondary servers which are allowed to +initiate zone transfer (AXFR) requests for zones for which dnsmasq is +authoritative. If this option is not given but --auth-sec-servers is, +then AXFR requests will be +accepted from any secondary. Specifying +--auth-peer + +without +--auth-sec-servers + +enables zone transfer but does not advertise the secondary in NS records returned by dnsmasq.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/auth-sec-servers.html b/src/DnsmasqWebUI/wwwroot/option-help/auth-sec-servers.html new file mode 100644 index 0000000..ca068c8 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/auth-sec-servers.html @@ -0,0 +1,5 @@ +
--auth-sec-servers=<domain>[,<domain>[,<domain>...]]
+
Specify any secondary servers for a zone for which dnsmasq is +authoritative. These servers must be configured to get zone data from +dnsmasq by zone transfer, and answer queries for the same +authoritative zones as dnsmasq.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/auth-soa.html b/src/DnsmasqWebUI/wwwroot/option-help/auth-soa.html new file mode 100644 index 0000000..efc1597 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/auth-soa.html @@ -0,0 +1,3 @@ +
--auth-soa=<serial>[,<hostmaster>[,<refresh>[,<retry>[,<expiry>]]]]
+
Specify fields in the SOA record associated with authoritative +zones. Note that this is optional, all the values are set to sane defaults.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/auth-zone.html b/src/DnsmasqWebUI/wwwroot/option-help/auth-zone.html new file mode 100644 index 0000000..3d28807 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/auth-zone.html @@ -0,0 +1,28 @@ +
--auth-zone=<domain>[,<subnet>[/<prefix length>][,<subnet>[/<prefix length>].....][,exclude:<subnet>[/<prefix length>]].....]
+
Define a DNS zone for which dnsmasq acts as authoritative server. Locally defined DNS records which are in the domain +will be served. If subnet(s) are given, A and AAAA records must be in one of the +specified subnets. +

+As alternative to directly specifying the subnets, it's possible to +give the name of an interface, in which case the subnets implied by +that interface's configured addresses and netmask/prefix-length are +used; this is useful when using constructed DHCP ranges as the actual +address is dynamic and not known when configuring dnsmasq. The +interface addresses may be confined to only IPv6 addresses using +<interface>/6 or to only IPv4 using <interface>/4. This is useful when +an interface has dynamically determined global IPv6 addresses which should +appear in the zone, but RFC1918 IPv4 addresses which should not. +Interface-name and address-literal subnet specifications may be used +freely in the same --auth-zone declaration. +

+It's possible to exclude certain IP addresses from responses. It can be +used, to make sure that answers contain only global routeable IP +addresses (by excluding loopback, RFC1918 and ULA addresses). +

+The subnet(s) are also used to define in-addr.arpa and +ip6.arpa domains which are served for reverse-DNS queries. If not +specified, the prefix length defaults to 24 for IPv4 and 64 for IPv6. +For IPv4 subnets, the prefix length should be have the value 8, 16 or 24 +unless you are familiar with RFC 2317 and have arranged the +in-addr.arpa delegation accordingly. Note that if no subnets are +specified, then no reverse queries are answered.

\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/bootp-dynamic.html b/src/DnsmasqWebUI/wwwroot/option-help/bootp-dynamic.html new file mode 100644 index 0000000..ac6c62a --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/bootp-dynamic.html @@ -0,0 +1,7 @@ +
-3, --bootp-dynamic[=<network-id>[,<network-id>]]
+
(IPv4 only) Enable dynamic allocation of IP addresses to BOOTP clients. Use this +with care, since each address allocated to a BOOTP client is leased +forever, and therefore becomes permanently unavailable for re-use by +other hosts. if this is given without tags, then it unconditionally +enables dynamic allocation. With tags, only when the tags are all +set. It may be repeated with different tag sets.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/bridge-interface.html b/src/DnsmasqWebUI/wwwroot/option-help/bridge-interface.html new file mode 100644 index 0000000..e1793b5 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/bridge-interface.html @@ -0,0 +1,12 @@ +
--bridge-interface=<interface>,<alias>[,<alias>]
+
Treat DHCP (v4 and v6) requests and IPv6 Router Solicit packets +arriving at any of the <alias> interfaces as if they had arrived at +<interface>. This option allows dnsmasq to provide DHCP and RA +service over unaddressed and unbridged Ethernet interfaces, e.g. on an +OpenStack compute host where each such interface is a TAP interface to +a VM, or as in "old style bridging" on BSD platforms. A trailing '*' +wildcard can be used in each <alias>. +

+It is permissible to add more than one alias using more than one --bridge-interface option since +--bridge-interface=int1,alias1,alias2 is exactly equivalent to +--bridge-interface=int1,alias1 --bridge-interface=int1,alias2

\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/caa-record.html b/src/DnsmasqWebUI/wwwroot/option-help/caa-record.html new file mode 100644 index 0000000..269b44d --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/caa-record.html @@ -0,0 +1,2 @@ +
--caa-record=<name>,<flags>,<tag>,<value>
+
Return a CAA DNS record, as specified in RFC6844.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/connmark-allowlist-enable.html b/src/DnsmasqWebUI/wwwroot/option-help/connmark-allowlist-enable.html new file mode 100644 index 0000000..8bc821a --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/connmark-allowlist-enable.html @@ -0,0 +1,10 @@ +
--connmark-allowlist-enable[=<mask>]
+
Enables filtering of incoming DNS queries with associated Linux connection track marks +according to individual allowlists configured via a series of --connmark-allowlist +options. Disallowed queries are not forwarded; they are rejected with a REFUSED error code. +DNS queries are only allowed if they do not have an associated Linux connection +track mark, or if the queried domains match the configured DNS patterns for the +associated Linux connection track mark. If no allowlist is configured for a +Linux connection track mark, all DNS queries associated with that mark are rejected. +If a mask is specified, Linux connection track marks are first bitwise ANDed +with the given mask before being processed.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/connmark-allowlist.html b/src/DnsmasqWebUI/wwwroot/option-help/connmark-allowlist.html new file mode 100644 index 0000000..a5896c4 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/connmark-allowlist.html @@ -0,0 +1,14 @@ +
--connmark-allowlist=<connmark>[/<mask>][,<pattern>[/<pattern>...]]
+
Configures the DNS patterns that are allowed in DNS queries associated with +the given Linux connection track mark. +If a mask is specified, Linux connection track marks are first bitwise ANDed +with the given mask before they are compared to the given connection track mark. +Patterns follow the syntax of DNS names, but additionally allow the wildcard +character "*" to be used up to twice per label to match 0 or more characters +within that label. Note that the wildcard never matches a dot (e.g., "*.example.com" +matches "api.example.com" but not "api.us.example.com"). Patterns must be +fully qualified, i.e., consist of at least two labels. The final label must not be +fully numeric, and must not be the "local" pseudo-TLD. A pattern must end with at least +two literal (non-wildcard) labels. +Instead of a pattern, "*" can be specified to disable allowlist filtering +for a given Linux connection track mark entirely.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dhcp-alternate-port.html b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-alternate-port.html new file mode 100644 index 0000000..e520f01 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-alternate-port.html @@ -0,0 +1,7 @@ +
--dhcp-alternate-port[=<server port>[,<client port>]]
+
(IPv4 only) Change the ports used for DHCP from the default. If this option is +given alone, without arguments, it changes the ports used for DHCP +from 67 and 68 to 1067 and 1068. If a single argument is given, that +port number is used for the server and the port number plus one used +for the client. Finally, two port numbers allows arbitrary +specification of both server and client ports for DHCP.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dhcp-broadcast.html b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-broadcast.html new file mode 100644 index 0000000..079524b --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-broadcast.html @@ -0,0 +1,6 @@ +
--dhcp-broadcast[=tag:<tag>[,tag:<tag>]]
+
(IPv4 only) When all the given tags appear in the tag set, always use broadcast to +communicate with the host when it is unconfigured. It is permissible +to supply no tags, in which case this is unconditional. Most DHCP clients which +need broadcast replies set a flag in their requests so that this +happens automatically, some old BOOTP clients do not.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dhcp-circuitid.html b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-circuitid.html new file mode 100644 index 0000000..6119962 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-circuitid.html @@ -0,0 +1,10 @@ +
--dhcp-circuitid=set:<tag>,<circuit-id>, --dhcp-remoteid=set:<tag>,<remote-id>
+
Map from RFC3046 relay agent options to tags. This data may +be provided by DHCP relay agents. The circuit-id or remote-id is +normally given as colon-separated hex, but is also allowed to be a +simple string. If an exact match is achieved between the circuit or +agent ID and one provided by a relay agent, the tag is set. +

+--dhcp-remoteid + +(but not --dhcp-circuitid) is supported in IPv6.

\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dhcp-duid.html b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-duid.html new file mode 100644 index 0000000..1f15075 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-duid.html @@ -0,0 +1,9 @@ +
--dhcp-duid=<enterprise-id>,<uid>
+
(IPv6 only) Specify the server persistent UID which the DHCPv6 server +will use. This option is not normally required as dnsmasq creates a +DUID automatically when it is first needed. When given, this option +provides dnsmasq the data required to create a DUID-EN type DUID. Note +that once set, the DUID is stored in the lease database, so to change between DUID-EN and +automatically created DUIDs or vice-versa, the lease database must be +re-initialised. The enterprise-id is assigned by IANA, and the uid is a +string of hex octets unique to a particular device.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dhcp-generate-names.html b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-generate-names.html new file mode 100644 index 0000000..cfcfbe1 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-generate-names.html @@ -0,0 +1,8 @@ +
--dhcp-generate-names=tag:<tag>[,tag:<tag>]
+
(IPv4 only) Generate a name for DHCP clients which do not otherwise have one, +using the MAC address expressed in hex, separated by dashes. Note that +if a host provides a name, it will be used by preference to this, +unless +--dhcp-ignore-names + +is set.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dhcp-ignore-clid.html b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-ignore-clid.html new file mode 100644 index 0000000..b399877 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-ignore-clid.html @@ -0,0 +1,5 @@ +
--dhcp-ignore-clid
+
Dnsmasq is reading 'client identifier' (RFC 2131) option sent by clients +(if available) to identify clients. This allow one to serve same IP address +for a host using several interfaces. Use this option to disable 'client identifier' +reading, i.e. to always identify a host using the MAC address.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dhcp-luascript.html b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-luascript.html new file mode 100644 index 0000000..2d051f1 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-luascript.html @@ -0,0 +1,73 @@ +
--dhcp-luascript=<path>
+
Specify a script written in Lua, to be run when leases are created, +destroyed or changed. To use this option, dnsmasq must be compiled +with the correct support. The Lua interpreter is initialised once, when +dnsmasq starts, so that global variables persist between lease +events. The Lua code must define a +lease + +function, and may provide +init + +and +shutdown + +functions, which are called, without arguments when dnsmasq starts up +and terminates. It may also provide a +tftp + +function. +

+The +lease + +function receives the information detailed in +--dhcp-script. + +It gets two arguments, firstly the action, which is a string +containing, "add", "old" or "del", and secondly a table of tag value +pairs. The tags mostly correspond to the environment variables +detailed above, for instance the tag "domain" holds the same data as +the environment variable DNSMASQ_DOMAIN. There are a few extra tags +which hold the data supplied as arguments to +--dhcp-script. + +These are +mac_address, ip_address + +and +hostname + +for IPv4, and +client_duid, ip_address + +and +hostname + +for IPv6. +

+The +tftp + +function is called in the same way as the lease function, and the +table holds the tags +destination_address, +file_name + +and +file_size. +

+The +arp + +and +arp-old + +functions are called only when enabled with +--script-arp + +and have a table which holds the tags +mac_address + +and +client_address.

\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dhcp-no-override.html b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-no-override.html new file mode 100644 index 0000000..6ebe590 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-no-override.html @@ -0,0 +1,7 @@ +
--dhcp-no-override
+
(IPv4 only) Disable re-use of the DHCP servername and filename fields as extra +option space. If it can, dnsmasq moves the boot server and filename +information (from --dhcp-boot) out of their dedicated fields into +DHCP options. This make extra space available in the DHCP packet for +options but can, rarely, confuse old or broken clients. This flag +forces "simple and safe" behaviour to avoid problems in such a case.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dhcp-option-pxe.html b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-option-pxe.html new file mode 100644 index 0000000..cf9bc3c --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-option-pxe.html @@ -0,0 +1,3 @@ +
--dhcp-option-pxe=[tag:<tag>,[tag:<tag>,]][encap:<opt>,][vi-encap:<enterprise>,]<opt>,[<value>[,<value>]]
+
A variant of --dhcp-option which defines options only sent in reply to PXE clients. In addition, such options are +sent in reply to PXE clients when dnsmasq is acting as a PXE proxy, unlike other options. A typical use-case is option 175, sent to iPXE.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dhcp-proxy.html b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-proxy.html new file mode 100644 index 0000000..ee0c804 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-proxy.html @@ -0,0 +1,18 @@ +
--dhcp-proxy[=<ip addr>]......
+
(IPv4 only) A normal DHCP relay agent is only used to forward the initial parts of +a DHCP interaction to the DHCP server. Once a client is configured, it +communicates directly with the server. This is undesirable if the +relay agent is adding extra information to the DHCP packets, such as +that used by +--dhcp-circuitid + +and +--dhcp-remoteid. + +A full relay implementation can use the RFC 5107 serverid-override +option to force the DHCP server to use the relay as a full proxy, with all +packets passing through it. This flag provides an alternative method +of doing the same thing, for relays which don't support RFC +5107. Given alone, it manipulates the server-id for all interactions +via relays. If a list of IP addresses is given, only interactions via +relays at those addresses are affected.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dhcp-pxe-vendor.html b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-pxe-vendor.html new file mode 100644 index 0000000..1401d13 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-pxe-vendor.html @@ -0,0 +1,16 @@ +
--dhcp-pxe-vendor=<vendor>[,...]
+
According to UEFI and PXE specifications, DHCP packets between PXE clients and +proxy PXE servers should have +PXEClient + +in their vendor-class field. However, the firmware of computers from a few +vendors is customized to carry a different identifier in that field. This option +is used to consider such identifiers valid for identifying PXE clients. For +instance +

+--dhcp-pxe-vendor=PXEClient,HW-Client +

+will enable dnsmasq to also provide proxy PXE service to those PXE clients with +HW-Client + +in as their identifier.

\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dhcp-relay.html b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-relay.html new file mode 100644 index 0000000..30ed0a8 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-relay.html @@ -0,0 +1,39 @@ +
--dhcp-relay=<local address>[,<server address>[#<server port>]][,<interface]
+
Configure dnsmasq to do DHCP relay. The local address is an address +allocated to an interface on the host running dnsmasq. All DHCP +requests arriving on that interface will be relayed to a remote DHCP +server at the server address. It is possible to relay from a single local +address to multiple remote servers by using multiple --dhcp-relay +configs with the same local address and different server +addresses. A server address must be an IP literal address, not a +domain name. If the server address is omitted, the request will be +forwarded by broadcast (IPv4) or multicast (IPv6). In this case the interface +must be given and not be wildcard. The server address may specify a non-standard +port to relay to. If this is used then --dhcp-proxy should likely also be set, +otherwise parts of the DHCP conversation which do not pass through the relay +will be delivered to the wrong port. +

+Access control for DHCP clients has the same rules as for the DHCP +server, see --interface, --except-interface, etc. The optional +interface name in the --dhcp-relay config has a different function: it +controls on which interface DHCP replies from the server will be +accepted. This is intended for configurations which have three +interfaces: one being relayed from, a second connecting the DHCP +server, and a third untrusted network, typically the wider +internet. It avoids the possibility of spoof replies arriving via this +third interface. +

+It is allowed to have dnsmasq act as a DHCP server on one set of +interfaces and relay from a disjoint set of interfaces. Note that +whilst it is quite possible to write configurations which appear to +act as a server and a relay on the same interface, this is not +supported: the relay function will take precedence. +

+Both DHCPv4 and DHCPv6 relay is supported. It's not possible to relay +DHCPv4 to a DHCPv6 server or vice-versa. +

+The DHCP relay function for IPv6 includes the ability to snoop +prefix-delegation from relayed DHCP transactions. See +--dhcp-script + +for details.

\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dhcp-remoteid.html b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-remoteid.html new file mode 100644 index 0000000..6119962 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-remoteid.html @@ -0,0 +1,10 @@ +
--dhcp-circuitid=set:<tag>,<circuit-id>, --dhcp-remoteid=set:<tag>,<remote-id>
+
Map from RFC3046 relay agent options to tags. This data may +be provided by DHCP relay agents. The circuit-id or remote-id is +normally given as colon-separated hex, but is also allowed to be a +simple string. If an exact match is achieved between the circuit or +agent ID and one provided by a relay agent, the tag is set. +

+--dhcp-remoteid + +(but not --dhcp-circuitid) is supported in IPv6.

\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dhcp-scriptuser.html b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-scriptuser.html new file mode 100644 index 0000000..aad6c10 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-scriptuser.html @@ -0,0 +1,2 @@ +
--dhcp-scriptuser
+
Specify the user as which to run the lease-change script or Lua script. This defaults to root, but can be changed to another user using this flag.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dhcp-sequential-ip.html b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-sequential-ip.html new file mode 100644 index 0000000..25dffee --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-sequential-ip.html @@ -0,0 +1,12 @@ +
--dhcp-sequential-ip
+
Dnsmasq is designed to choose IP addresses for DHCP clients using a +hash of the client's MAC address. This normally allows a client's +address to remain stable long-term, even if the client sometimes allows its DHCP +lease to expire. In this default mode IP addresses are distributed +pseudo-randomly over the entire available address range. There are +sometimes circumstances (typically server deployment) where it is more +convenient to have IP +addresses allocated sequentially, starting from the lowest available +address, and setting this flag enables this mode. Note that in the +sequential mode, clients which allow a lease to expire are much more +likely to move IP address; for this reason it should not be generally used.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dhcp-subscrid.html b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-subscrid.html new file mode 100644 index 0000000..9d7c4a1 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dhcp-subscrid.html @@ -0,0 +1,2 @@ +
--dhcp-subscrid=set:<tag>,<subscriber-id>
+
(IPv4 and IPv6) Map from RFC3993 subscriber-id relay agent options to tags.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dns-forward-max.html b/src/DnsmasqWebUI/wwwroot/option-help/dns-forward-max.html new file mode 100644 index 0000000..a396e5a --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dns-forward-max.html @@ -0,0 +1,6 @@ +
-0, --dns-forward-max=<queries>
+
Set the maximum number of concurrent DNS queries. The default value is +150, which should be fine for most setups. The only known situation +where this needs to be increased is when using web-server log file +resolvers, which can generate large numbers of concurrent queries. This +parameter actually controls the number of concurrent queries per server group, where a server group is the set of server(s) associated with a single domain. So if a domain has its own server via --server=/example.com/1.2.3.4 and 1.2.3.4 is not responding, but queries for *.example.com cannot go elsewhere, then other queries will not be affected. On configurations with many such server groups and tight resources, this value may need to be reduced.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dns-rr.html b/src/DnsmasqWebUI/wwwroot/option-help/dns-rr.html new file mode 100644 index 0000000..80124fb --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dns-rr.html @@ -0,0 +1,5 @@ +
--dns-rr=<name>,<RR-number>,[<hex data>]
+
Return an arbitrary DNS Resource Record. The number is the type of the +record (which is always in the C_IN class). The value of the record is +given by the hex data, which may be of the form 01:23:45 or 01 23 45 or +012345 or any mixture of these.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dnssec-debug.html b/src/DnsmasqWebUI/wwwroot/option-help/dnssec-debug.html new file mode 100644 index 0000000..4a927ef --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dnssec-debug.html @@ -0,0 +1,6 @@ +
--dnssec-debug
+
Set debugging mode for the DNSSEC validation, set the Checking Disabled bit on upstream queries, +and don't convert replies which do not validate to responses with +a return code of SERVFAIL. Note that +setting this may affect DNS behaviour in bad ways, it is not an +extra-logging flag and should not be set in production.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dnssec-limits.html b/src/DnsmasqWebUI/wwwroot/option-help/dnssec-limits.html new file mode 100644 index 0000000..c4784ef --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dnssec-limits.html @@ -0,0 +1,8 @@ +
--dnssec-limits=<limit>[,<limit>.......]
+
Override the default resource limits applied to DNSSEC validation. Cryptographic operations are expensive and crafted domains +can DoS a DNSSEC validator by forcing it to do hundreds of thousands of such operations. To avoid this, the dnsmasq validation code +applies limits on how much work will be expended in validation. If any of the limits are exceeded, the validation will fail and the +domain treated as BOGUS. There are four limits, in order(default values in parens): number a signature validation fails per RRset(20), number of signature validations and +hash computations per query(200), number of sub-queries to fetch DS and DNSKEY RRsets per query(40), and the number of iterations in a NSEC3 record(150). +The maximum values reached during validation are stored, and dumped as part of the stats generated by SIGUSR1. Supplying a limit value of 0 leaves the default in place, so +--dnssec-limits=0,0,20 sets the number of sub-queries to 20 whilst leaving the other limits at default values.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dnssec-no-timecheck.html b/src/DnsmasqWebUI/wwwroot/option-help/dnssec-no-timecheck.html new file mode 100644 index 0000000..8494c95 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dnssec-no-timecheck.html @@ -0,0 +1,12 @@ +
--dnssec-no-timecheck
+
DNSSEC signatures are only valid for specified time windows, and should be rejected outside those windows. This generates an +interesting chicken-and-egg problem for machines which don't have a hardware real time clock. For these machines to determine the correct +time typically requires use of NTP and therefore DNS, but validating DNS requires that the correct time is already known. Setting this flag +removes the time-window checks (but not other DNSSEC validation.) only until the dnsmasq process receives SIGINT. The intention is +that dnsmasq should be started with this flag when the platform determines that reliable time is not currently available. As soon as +reliable time is established, a SIGINT should be sent to dnsmasq, which enables time checking, and purges the cache of DNS records +which have not been thoroughly checked. +

+Earlier versions of dnsmasq overloaded SIGHUP (which re-reads much configuration) to also enable time validation. +

+If dnsmasq is run in debug mode (--no-daemon flag) then SIGINT retains its usual meaning of terminating the dnsmasq process.

\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dnssec-timestamp.html b/src/DnsmasqWebUI/wwwroot/option-help/dnssec-timestamp.html new file mode 100644 index 0000000..325e16e --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dnssec-timestamp.html @@ -0,0 +1,6 @@ +
--dnssec-timestamp=<path>
+
Enables an alternative way of checking the validity of the system time for DNSSEC (see --dnssec-no-timecheck). In this case, the +system time is considered to be valid once it becomes later than the timestamp on the specified file. The file is created and +its timestamp set automatically by dnsmasq. The file must be stored on a persistent filesystem, so that it and its mtime are carried +over system restarts. The timestamp file is created after dnsmasq has dropped root, so it must be in a location writable by the +unprivileged user that dnsmasq runs as.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/do-0x20-encode.html b/src/DnsmasqWebUI/wwwroot/option-help/do-0x20-encode.html new file mode 100644 index 0000000..78cb0a5 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/do-0x20-encode.html @@ -0,0 +1,11 @@ +
--do-0x20-encode, --no-0x20-encode
+
Dnsmasq can scramble the case of letters in DNS queries it sends upstream as a security feature. +This technique can interact badly with rare broken DNS servers which don't preserve the case +of the query in their reply. The first time a reply is returned +which matches the query in all respects except case, a warning +will be logged. If this coincides with DNS not functioning, it +is necessary to disable the feature. As at version 2.91, 0x20 encoding +is disabled by default, and must be enabled with --do-0x20-encode. The default +may change in the future, so to be sure of its status after an upgrade, set --do-0x20-encode +or --no-0x20-encode in your config. --no-0x20-encode overrides --do-x20-encode or a future default +0x20-encode enable.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dumpfile.html b/src/DnsmasqWebUI/wwwroot/option-help/dumpfile.html new file mode 100644 index 0000000..8fe2df8 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dumpfile.html @@ -0,0 +1,2 @@ +
--dumpfile=<path/to/file>
+
Specify the location of a pcap-format file which dnsmasq uses to dump copies of network packets for debugging purposes. If the file exists when dnsmasq starts, it is not deleted; new packets are added to the end. The file may be a named-pipe which Wireshark is listening to.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/dumpmask.html b/src/DnsmasqWebUI/wwwroot/option-help/dumpmask.html new file mode 100644 index 0000000..7f8d1a8 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/dumpmask.html @@ -0,0 +1,3 @@ +
--dumpmask=<mask>
+
Specify which types of packets should be added to the dumpfile. The argument should be the OR of the bitmasks for each type of packet to be dumped: it can be specified in hex by preceding the number with 0x in the normal way. Each time a packet is written to the dumpfile, dnsmasq logs the packet sequence and the mask +representing its type. The current types are: 0x0001 - DNS queries from clients, 0x0002 DNS replies to clients, 0x0004 - DNS queries to upstream, 0x0008 - DNS replies from upstream, 0x0010 - queries send upstream for DNSSEC validation, 0x0020 - replies to queries for DNSSEC validation, 0x0040 - replies to client queries which fail DNSSEC validation, 0x0080 replies to queries for DNSSEC validation which fail validation, 0x1000 - DHCPv4, 0x2000 - DHCPv6, 0x4000 - Router advertisement, 0x8000 - TFTP.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/leasequery.html b/src/DnsmasqWebUI/wwwroot/option-help/leasequery.html new file mode 100644 index 0000000..34d309c --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/leasequery.html @@ -0,0 +1,11 @@ +
--leasequery[=<addr>[/<prefix>]]
+
Enable RFC 4388 leasequery. The dnsmasq DHCP server will answer LEASEQUERY messages from DHCP relays +when this option is given. If an address or subnet is given, only queries from a relay at that source are allowed. Repeat +the --leasequery option to specify multiple allowed sources. +To correctly answer lease queries it is necessary to store extra data in +the lease database, and this is also enabled by the --leasequery option. The extra fields +(agent-info and vendorclass) are stored in the leases file in a somewhat backwards compatible manner. +Enabling and then disabling leasequery will not cause problems; the extra information will be aged +out of the database. However, enabling leasequery in release 2.92 or later, storing leases +which come via DHCP relays which add option-82 agent-info data, and then moving back to a pre-2.92 dnsmasq +binary may cause problems reading the leases file. As of release 2.92, leasequery is only supported for DHCPv4.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/localmx.html b/src/DnsmasqWebUI/wwwroot/option-help/localmx.html new file mode 100644 index 0000000..ad5882b --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/localmx.html @@ -0,0 +1,5 @@ +
-L, --localmx
+
Return an MX record pointing to the host given by --mx-target (or the +machine on which dnsmasq is running) for each +local machine. Local machines are those in /etc/hosts or with DHCP +leases.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/no-ping.html b/src/DnsmasqWebUI/wwwroot/option-help/no-ping.html new file mode 100644 index 0000000..68c4f03 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/no-ping.html @@ -0,0 +1,6 @@ +
-5, --no-ping
+
(IPv4 only) By default, the DHCP server will attempt to ensure that an address is +not in use before allocating it to a host. It does this by sending an +ICMP echo request (aka "ping") to the address in question. If it gets +a reply, then the address must already be in use, and another is +tried. This flag disables this check. Use with caution.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/no-round-robin.html b/src/DnsmasqWebUI/wwwroot/option-help/no-round-robin.html new file mode 100644 index 0000000..a74645d --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/no-round-robin.html @@ -0,0 +1,5 @@ +
--no-round-robin
+
Dnsmasq normally permutes the order of A or AAAA records for the same +name on successive queries, for load-balancing. This turns off that +behaviour, so that the records are always returned in the order +that they are received from upstream.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/quiet-dhcp.html b/src/DnsmasqWebUI/wwwroot/option-help/quiet-dhcp.html new file mode 100644 index 0000000..32b7cc2 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/quiet-dhcp.html @@ -0,0 +1,5 @@ +
--quiet-dhcp, --quiet-dhcp6, --quiet-ra, --quiet-tftp
+
Suppress logging of the routine operation of these protocols. Errors and +problems will still be logged. --quiet-tftp does not consider file not +found to be an error. --quiet-dhcp and quiet-dhcp6 are over-ridden by +--log-dhcp.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/quiet-dhcp6.html b/src/DnsmasqWebUI/wwwroot/option-help/quiet-dhcp6.html new file mode 100644 index 0000000..32b7cc2 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/quiet-dhcp6.html @@ -0,0 +1,5 @@ +
--quiet-dhcp, --quiet-dhcp6, --quiet-ra, --quiet-tftp
+
Suppress logging of the routine operation of these protocols. Errors and +problems will still be logged. --quiet-tftp does not consider file not +found to be an error. --quiet-dhcp and quiet-dhcp6 are over-ridden by +--log-dhcp.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/quiet-ra.html b/src/DnsmasqWebUI/wwwroot/option-help/quiet-ra.html new file mode 100644 index 0000000..32b7cc2 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/quiet-ra.html @@ -0,0 +1,5 @@ +
--quiet-dhcp, --quiet-dhcp6, --quiet-ra, --quiet-tftp
+
Suppress logging of the routine operation of these protocols. Errors and +problems will still be logged. --quiet-tftp does not consider file not +found to be an error. --quiet-dhcp and quiet-dhcp6 are over-ridden by +--log-dhcp.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/quiet-tftp.html b/src/DnsmasqWebUI/wwwroot/option-help/quiet-tftp.html new file mode 100644 index 0000000..32b7cc2 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/quiet-tftp.html @@ -0,0 +1,5 @@ +
--quiet-dhcp, --quiet-dhcp6, --quiet-ra, --quiet-tftp
+
Suppress logging of the routine operation of these protocols. Errors and +problems will still be logged. --quiet-tftp does not consider file not +found to be an error. --quiet-dhcp and quiet-dhcp6 are over-ridden by +--log-dhcp.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/script-arp.html b/src/DnsmasqWebUI/wwwroot/option-help/script-arp.html new file mode 100644 index 0000000..c9698fe --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/script-arp.html @@ -0,0 +1,2 @@ +
--script-arp
+
Enable the "arp" and "arp-old" functions in the --dhcp-script and --dhcp-luascript.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/script-on-renewal.html b/src/DnsmasqWebUI/wwwroot/option-help/script-on-renewal.html new file mode 100644 index 0000000..d40dbe4 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/script-on-renewal.html @@ -0,0 +1,3 @@ +
--script-on-renewal
+
Call the DHCP script when the lease expiry time changes, for instance when the +lease is renewed.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/selfmx.html b/src/DnsmasqWebUI/wwwroot/option-help/selfmx.html new file mode 100644 index 0000000..7903257 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/selfmx.html @@ -0,0 +1,3 @@ +
-e, --selfmx
+
Return an MX record pointing to itself for each local +machine. Local machines are those in /etc/hosts or with DHCP leases.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/shared-network.html b/src/DnsmasqWebUI/wwwroot/option-help/shared-network.html new file mode 100644 index 0000000..534387c --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/shared-network.html @@ -0,0 +1,2 @@ +
--shared-network=<interface>,<addr>
+
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/synth-domain.html b/src/DnsmasqWebUI/wwwroot/option-help/synth-domain.html new file mode 100644 index 0000000..046f057 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/synth-domain.html @@ -0,0 +1,22 @@ +
--synth-domain=<domain>,<address range>[,<prefix>[*]]
+
Create artificial A/AAAA and PTR records for an address range. The +records either seqential numbers or the address, with periods (or colons for IPv6) replaced with dashes. +

+An examples should make this clearer. First sequential numbers. +--synth-domain=thekelleys.org.uk,192.168.0.50,192.168.0.70,internal-* + +results in the name internal-0.thekelleys.org.uk. returning 192.168.0.50, internal-1.thekelleys.org.uk returning 192.168.0.51 and so on. (note the *) The same principle applies to IPv6 addresses (where the numbers may be very large). Reverse lookups from address to name behave as expected. +

+Second, +--synth-domain=thekelleys.org.uk,192.168.0.0/24,internal- (no *) + +will result in a query for internal-192-168-0-56.thekelleys.org.uk returning +192.168.0.56 and a reverse query vice versa. The same applies to IPv6; +the representation doesn't use the :: compression feature or +the special representation of V4 mapped IPv6 addresses as these +can generate illegal domain names, so all domains are of the form +internal-1000-0000-0000-0000-0000-0000-0000-0008.example.com +

+The address range can be of the form +<start address>,<end address> or <ip address>/<prefix-length> in both forms of the option. For IPv6 the start and end addresses +must fall in the same /64 network, or prefix-length must be greater than or equal to 64 except that shorter prefix lengths than 64 are allowed only if non-sequential names are in use.

\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/tag-if.html b/src/DnsmasqWebUI/wwwroot/option-help/tag-if.html new file mode 100644 index 0000000..88328c1 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/tag-if.html @@ -0,0 +1,16 @@ +
--tag-if=set:<tag>[,set:<tag>[,tag:<tag>[,tag:<tag>]]]
+
Perform boolean operations on tags. All set:<tag> tags are set if +all tag:<tag> are already set, (or unset when tag:!<tag> is used). +If no tag:<tag> appears, set:<tag> tags are set unconditionally. +Any number of set: and tag: forms may appear, in any order. +--tag-if lines are executed in order, so if the tag in tag:<tag> is a +tag set by another +--tag-if, + +the line which sets the tag must precede the line which tests it. +

+As an extension, the tag:<tag> clauses support limited wildcard matching, +similar to the matching in the --interface directive. This allows the +example --tag-if=set:ppp,tag:ppp* to set the tag 'ppp' for all requests +received on any matching interface (ppp0, ppp1, etc). This can be used in conjunction +with the tag:!<tag> format meaning that no tag matching the wildcard may be set.

\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/umbrella.html b/src/DnsmasqWebUI/wwwroot/option-help/umbrella.html new file mode 100644 index 0000000..1ab9771 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/umbrella.html @@ -0,0 +1,7 @@ +
--umbrella[=[deviceid:<deviceid>][,orgid:<orgid>][,assetid:<id>]]
+
Embeds the requestor's IP address in DNS queries forwarded upstream. +If device id or, asset id or organization id are specified, the information is +included in the forwarded queries and may be able to be used in +filtering policies and reporting. The order of the id +attributes is irrelevant, but they must be separated by a comma. Deviceid is +a sixteen digit hexadecimal number, org and asset ids are decimal numbers.
\ No newline at end of file diff --git a/src/DnsmasqWebUI/wwwroot/option-help/use-stale-cache.html b/src/DnsmasqWebUI/wwwroot/option-help/use-stale-cache.html new file mode 100644 index 0000000..7d22744 --- /dev/null +++ b/src/DnsmasqWebUI/wwwroot/option-help/use-stale-cache.html @@ -0,0 +1,6 @@ +
--use-stale-cache[=<max TTL excess in s>]
+
When set, if a DNS name exists in the cache, but its time-to-live has expired, dnsmasq will return the data anyway. (It attempts to refresh the +data with an upstream query after returning the stale data.) This can improve speed and reliability. It comes at the expense +of sometimes returning out-of-date data and less efficient cache utilisation, since old data cannot be flushed when its TTL expires, so the cache becomes +mostly least-recently-used. To mitigate issues caused by massively outdated DNS replies, the maximum overaging of cached records can be specified in seconds +(defaulting to not serve anything older than one day). Setting the TTL excess time to zero will serve stale cache data regardless how long it has expired.
\ No newline at end of file diff --git a/testdata/README b/testdata/README index 7a8b111..ee6a6df 100644 --- a/testdata/README +++ b/testdata/README @@ -13,7 +13,7 @@ Source for the Docker test harness: `scripts/prepare-test-mount.sh` syncs this d | **dnsmasq.d/** | Included configs (conf-dir; alphabetical order). In the harness, "Config files (load order)" should list main, then these, then zz-dnsmasq-webui.conf. | | **dnsmasq.d/01-other.conf** | `domain=local`. Not managed. | | **dnsmasq.d/02-servers.conf** | Extra `server=` / `local=` (readonly in UI). | -| **dnsmasq.d/03-more.conf** | `resolv-file=`, `dhcp-option=`. Not managed. | +| **dnsmasq.d/03-more.conf** | `resolv-file=`, `dhcp-option=`, `no-round-robin`, `quiet-dhcp`, `dns-forward-max=`, `use-stale-cache=`. Not managed. | | **dnsmasq.d/dhcp.conf** | `dhcp-host=`; managed by app in real use, used as-is in harness. | | **hosts** | First addn-hosts file; system hosts when `Dnsmasq:SystemHostsPath` points here. | | **hosts.extra** | Second addn-hosts file. | diff --git a/testdata/dnsmasq.d/03-more.conf b/testdata/dnsmasq.d/03-more.conf index 9c83dcd..4a10b05 100644 --- a/testdata/dnsmasq.d/03-more.conf +++ b/testdata/dnsmasq.d/03-more.conf @@ -1,4 +1,9 @@ -# Extra options so effective config has resolv-file and dhcp-option (multi-value). Not managed by app. +# Extra options so effective config has resolv-file, dhcp-option (multi-value), and newer options. Not managed by app. # resolv-file must point to a file that exists in the container (/data is the mount). resolv-file=/data/resolv.dnsmasq dhcp-option=3,172.28.0.1 +# New options (readonly in UI when using this testdata) +no-round-robin +quiet-dhcp +dns-forward-max=150 +use-stale-cache=60