mirror of
https://github.com/alexhopeoconnor/dnsmasq-webui.git
synced 2026-10-03 22:12:00 +10:00
Effective config: do-0x20-encode revert fix, validation UI, custom displays, option help
- Fix do-0x20-encode: clearing pending when toggling back to original (Disabled/Default) - Do0x20EncodeDisplay: uncheck uses pending.OldValue when present - EffectiveConfigFieldDisplay: use pending.OldValue as _valueAtActivation when re-entering with pending - Effective config: validation errors/warnings in toolbar and save modal, revert from modal - Custom displays: Do0x20EncodeDisplay (tri-state), KeyOnlyOrValueDisplay - ExplicitToggleState, config/apply support for do-0x20-encode, option help and tests
This commit is contained in:
+30
-3
@@ -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=<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
|
||||
|
||||
@@ -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=<resolved-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.
|
||||
|
||||
<!-- SUGGESTED SCREENSHOT: TERMINAL WITH PREPARE-TEST-MOUNT.SH USAGE OR DOCKER COMPOSE UP OUTPUT -->
|
||||
|
||||
---
|
||||
|
||||
@@ -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=<resolved-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/<key>.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.
|
||||
|
||||
@@ -14,6 +14,8 @@ services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
DNSMASQ_VERSION: ${DNSMASQ_VERSION:-latest}
|
||||
ports:
|
||||
- "8080:8080"
|
||||
cap_add:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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); }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Save-path tests for DnsmasqConfigService.ApplyEffectiveConfigChangesAsync.
|
||||
/// Verifies that pending changes produce the correct managed config lines.
|
||||
/// </summary>
|
||||
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<ConfigSetCache>.Instance);
|
||||
var setService = new DnsmasqConfigSetService(cache);
|
||||
var configService = new DnsmasqConfigService(setService, cache, NullLogger<DnsmasqConfigService>.Instance);
|
||||
|
||||
var changes = new List<PendingEffectiveConfigChange>
|
||||
{
|
||||
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<ConfigSetCache>.Instance);
|
||||
cache.Invalidate();
|
||||
var setService = new DnsmasqConfigSetService(cache);
|
||||
var configService = new DnsmasqConfigService(setService, cache, NullLogger<DnsmasqConfigService>.Instance);
|
||||
|
||||
var changes = new List<PendingEffectiveConfigChange>
|
||||
{
|
||||
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<ConfigSetCache>.Instance);
|
||||
var setService = new DnsmasqConfigSetService(cache);
|
||||
var configService = new DnsmasqConfigService(setService, cache, NullLogger<DnsmasqConfigService>.Instance);
|
||||
|
||||
var changes = new List<PendingEffectiveConfigChange>
|
||||
{
|
||||
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<ConfigSetCache>.Instance);
|
||||
var setService = new DnsmasqConfigSetService(cache);
|
||||
var configService = new DnsmasqConfigService(setService, cache, NullLogger<DnsmasqConfigService>.Instance);
|
||||
|
||||
var changes = new List<PendingEffectiveConfigChange>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,23 @@ public class EffectiveDnsmasqConfigTests
|
||||
DhcpHostsfilePaths: Array.Empty<string>(),
|
||||
DhcpOptsfilePaths: Array.Empty<string>(),
|
||||
DhcpHostsdirPaths: Array.Empty<string>(),
|
||||
ConnmarkAllowlistValues: Array.Empty<string>(),
|
||||
CaaRecordValues: Array.Empty<string>(),
|
||||
DnsRrValues: Array.Empty<string>(),
|
||||
SynthDomainValues: Array.Empty<string>(),
|
||||
AuthZoneValues: Array.Empty<string>(),
|
||||
AuthSoaValues: Array.Empty<string>(),
|
||||
AuthSecServersValues: Array.Empty<string>(),
|
||||
AuthPeerValues: Array.Empty<string>(),
|
||||
DhcpRelayValues: Array.Empty<string>(),
|
||||
DhcpCircuitidValues: Array.Empty<string>(),
|
||||
DhcpRemoteidValues: Array.Empty<string>(),
|
||||
DhcpSubscridValues: Array.Empty<string>(),
|
||||
DhcpProxyValues: Array.Empty<string>(),
|
||||
TagIfValues: Array.Empty<string>(),
|
||||
BridgeInterfaceValues: Array.Empty<string>(),
|
||||
SharedNetworkValues: Array.Empty<string>(),
|
||||
DhcpOptionPxeValues: Array.Empty<string>(),
|
||||
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
|
||||
);
|
||||
|
||||
|
||||
@@ -24,7 +24,21 @@
|
||||
{
|
||||
<pre class="pre-block mb-0" style="max-height: 22rem;">@_status.StatusShowOutput</pre>
|
||||
}
|
||||
<div class="mt-2">
|
||||
<div class="mt-2 d-flex align-items-center gap-2 flex-wrap">
|
||||
<button class="btn btn-primary btn-sm" @onclick="RefreshAsync" disabled="@_refreshing"><i class="bi bi-arrow-repeat me-1" aria-hidden="true"></i>Refresh</button>
|
||||
@if (ReloadCommandConfigured && OnReloadRequested.HasDelegate)
|
||||
{
|
||||
<button class="btn btn-primary btn-sm" @onclick="InvokeReloadAsync" disabled="@IsReloading" title="Reload dnsmasq config (e.g. SIGHUP)">
|
||||
@if (IsReloading)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="bi bi-arrow-repeat me-1" aria-hidden="true"></i>
|
||||
}
|
||||
Reload config
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ public partial class StatusSection : IDisposable
|
||||
|
||||
[Parameter] public bool IsReloading { get; set; }
|
||||
|
||||
/// <summary>When set and reload is configured, show a "Reload config" button next to Refresh.</summary>
|
||||
[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;
|
||||
|
||||
+8
-5
@@ -18,7 +18,7 @@
|
||||
}
|
||||
<EffectiveConfigToolbar ShowSearchBox="@ShowSearchBox" SearchTerm="@_searchTerm" SearchTermChanged="@OnSearchTermChanged"
|
||||
IsEditMode="@Session.IsEditMode" PendingChangesCount="@Session.PendingChanges.Count"
|
||||
ValidationErrorCount="@_validationErrorCount" ValidationWarningCount="@_validationWarningCount"
|
||||
ValidationSummary="@Session.GetValidationSummary()"
|
||||
AllSectionsExpanded="@_allSectionsExpanded"
|
||||
OnExpandAll="ExpandAll" OnCollapseAll="CollapseAll" OnEnterEditMode="EnterEditMode" OnExitEditModeWithConfirm="ExitEditModeWithConfirmAsync" OnOpenSaveModal="OpenSaveModal" />
|
||||
<CascadingValue Value="@Session">
|
||||
@@ -28,7 +28,7 @@
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
<EffectiveConfigSaveFlow ShowModal="@_showSaveModal" PendingChanges="@Session.PendingChanges" Status="@Status" Session="@Session"
|
||||
OnClose="CloseSaveModal" OnSaveCompleted="OnSaveCompleted" OnContinueEditingAfterSavedFailure="ContinueEditingAfterSavedFailureAsync" OnCloseAndEndEdit="CloseSaveModalAndEndEdit" />
|
||||
OnClose="CloseSaveModal" OnRevertedNotify="OnRevertedFromSaveModal" OnSaveCompleted="OnSaveCompleted" OnContinueEditingAfterSavedFailure="ContinueEditingAfterSavedFailureAsync" OnCloseAndEndEdit="CloseSaveModalAndEndEdit" />
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -42,9 +42,6 @@
|
||||
private HashSet<string> _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;
|
||||
|
||||
+29
-2
@@ -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;
|
||||
|
||||
+25
-6
@@ -35,12 +35,24 @@
|
||||
<span class="small ec-toolbar-validation-count" aria-live="polite">
|
||||
@if (ValidationErrorCount > 0)
|
||||
{
|
||||
<span class="text-danger fw-medium">errors: @ValidationErrorCount</span>
|
||||
<span class="text-danger fw-medium ec-toolbar-changes-link" role="button" tabindex="0"
|
||||
title="@ValidationErrorTooltip"
|
||||
aria-label="Open save dialog"
|
||||
@onclick="OnOpenSaveModal"
|
||||
@onkeydown="OnValidationLinkKeyDown">
|
||||
@ValidationErrorCount error(s)
|
||||
</span>
|
||||
}
|
||||
@if (ValidationErrorCount > 0 && ValidationWarningCount > 0) { <span class="text-muted">·</span> }
|
||||
@if (ValidationWarningCount > 0)
|
||||
{
|
||||
<span class="text-warning">warnings: @ValidationWarningCount</span>
|
||||
<span class="text-warning ec-toolbar-changes-link" role="button" tabindex="0"
|
||||
title="@ValidationWarningTooltip"
|
||||
aria-label="Open save dialog"
|
||||
@onclick="OnOpenSaveModal"
|
||||
@onkeydown="OnValidationLinkKeyDown">
|
||||
@ValidationWarningCount warning(s)
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
@@ -60,8 +72,11 @@
|
||||
[Parameter] public EventCallback<string> 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<FieldIssue>? 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
<input type="checkbox" class="form-check-input" checked="@isOn" @onchange="OnCheckedChange" @onfocusout="OnCommitRequested" />
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
+84
@@ -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 : "";
|
||||
<div class="ec-key-only-or-value-editor">
|
||||
<div class="form-check form-switch mb-0 ec-key-only-or-value-switch">
|
||||
<input type="checkbox"
|
||||
class="form-check-input"
|
||||
checked="@isEnabled"
|
||||
aria-label="Enable @Descriptor.OptionName"
|
||||
title="Set @Descriptor.OptionName"
|
||||
@onchange="OnCheckedChange" />
|
||||
</div>
|
||||
@if (isEnabled)
|
||||
{
|
||||
<input type="text"
|
||||
class="form-control form-control-sm ec-key-only-or-value-input"
|
||||
placeholder="Optional value"
|
||||
value="@valuePart"
|
||||
@oninput="OnValueInput" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
+31
@@ -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;
|
||||
}
|
||||
+25
-10
@@ -22,12 +22,15 @@
|
||||
<span class="ec-field-value">
|
||||
@if (isActiveEditor)
|
||||
{
|
||||
<div class="ec-field-edit-wrapper" @onfocusout="OnBlur" @onclick:stopPropagation="true">
|
||||
<CascadingValue Value="@true">
|
||||
<CascadingValue Value="@(EventCallback.Factory.Create<FocusEventArgs>(this, OnBlur))">
|
||||
@((fragmentWithCallback ?? fragment)(Descriptor))
|
||||
<div class="ec-field-value-when-editing" @onclick:stopPropagation="true">
|
||||
<div class="ec-field-edit-wrapper">
|
||||
<CascadingValue Value="@true">
|
||||
<CascadingValue Value="@(EventCallback.Factory.Create<FocusEventArgs>(this, OnBlur))">
|
||||
@((fragmentWithCallback ?? fragment)(Descriptor))
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</div>
|
||||
<OptionStateBadge AnchorId="@_readonlyBadgeAnchorId" Source="@Descriptor.GetSource()" OptionName="@Descriptor.OptionName" Value="@effectiveValue" IsInteractive="@IsEditMode" ShowEditableBadge="@showEditableBadge" HasPendingChange="@_hasPendingChange" EditableAsButton="true" FieldKey="@FieldKey" IsActiveEditor="@isActiveEditor" OnConfirmRequested="@HandleConfirmEdit" OnCancelRequested="@HandleCancelEdit" OnRevertRequested="@HandleRevertEdit" />
|
||||
</div>
|
||||
}
|
||||
else
|
||||
@@ -35,8 +38,8 @@
|
||||
<CascadingValue Value="@false">
|
||||
@fragment(Descriptor)
|
||||
</CascadingValue>
|
||||
<OptionStateBadge AnchorId="@_readonlyBadgeAnchorId" Source="@Descriptor.GetSource()" OptionName="@Descriptor.OptionName" Value="@effectiveValue" IsInteractive="@IsEditMode" ShowEditableBadge="@showEditableBadge" HasPendingChange="@_hasPendingChange" EditableAsButton="true" FieldKey="@FieldKey" IsActiveEditor="@isActiveEditor" OnConfirmRequested="@HandleConfirmEdit" OnCancelRequested="@HandleCancelEdit" OnRevertRequested="@HandleRevertEdit" />
|
||||
}
|
||||
<OptionStateBadge AnchorId="@_readonlyBadgeAnchorId" Source="@Descriptor.GetSource()" OptionName="@Descriptor.OptionName" Value="@effectiveValue" IsInteractive="@IsEditMode" ShowEditableBadge="@showEditableBadge" HasPendingChange="@_hasPendingChange" EditableAsButton="true" FieldKey="@FieldKey" IsActiveEditor="@isActiveEditor" OnConfirmRequested="@HandleConfirmEdit" OnCancelRequested="@HandleCancelEdit" OnRevertRequested="@HandleRevertEdit" />
|
||||
</span>
|
||||
</li>
|
||||
}
|
||||
@@ -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<string>, _draftValue as IReadOnlyList<string>);
|
||||
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<bool> 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<string> values)
|
||||
{
|
||||
_draftValue = values.ToList();
|
||||
_hasDraftValue = true;
|
||||
if (Descriptor.IsMultiValue)
|
||||
ComputeMultiValueState(Descriptor.GetItems(), _effectiveValue as IReadOnlyList<string>, _draftValue as IReadOnlyList<string>);
|
||||
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)
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
[Parameter] public IReadOnlyList<PendingEffectiveConfigChange> PendingChanges { get; set; } = null!;
|
||||
[Parameter] public DnsmasqServiceStatus? Status { get; set; }
|
||||
[Parameter] public EventCallback OnClose { get; set; }
|
||||
/// <summary>Invoked when the modal reverts a change so the parent can re-run validation (e.g. cross-option evaluator).</summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
@using DnsmasqWebUI.Models.Dnsmasq
|
||||
@using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig
|
||||
@using DnsmasqWebUI.Infrastructure.Services.EffectiveConfig.Abstractions
|
||||
@using DnsmasqWebUI.Infrastructure.Helpers.Config
|
||||
|
||||
<div class="modal fade show" style="display: block;" tabindex="-1" role="dialog" aria-labelledby="ec-save-modal-title" aria-modal="true">
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
@@ -85,6 +86,35 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (ValidationErrors.Count > 0 || ValidationWarnings.Count > 0)
|
||||
{
|
||||
<div class="ec-save-modal-validation mb-3">
|
||||
@if (ValidationErrors.Count > 0)
|
||||
{
|
||||
<div class="alert alert-danger py-2 mb-2">
|
||||
<strong>Validation errors</strong>
|
||||
<ul class="mb-0 mt-1 ps-3">
|
||||
@foreach (var issue in ValidationErrors)
|
||||
{
|
||||
<li>@issue.Message</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
@if (ValidationWarnings.Count > 0)
|
||||
{
|
||||
<div class="alert alert-warning py-2 mb-2">
|
||||
<strong>Validation warnings</strong>
|
||||
<ul class="mb-0 mt-1 ps-3">
|
||||
@foreach (var issue in ValidationWarnings)
|
||||
{
|
||||
<li>@issue.Message</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<p class="text-muted mb-3">These changes will be written to your config. Remove any you don't want to keep.</p>
|
||||
@if (ManagedFilePath != null)
|
||||
{
|
||||
@@ -114,9 +144,9 @@
|
||||
@if (!isMultiValue)
|
||||
{
|
||||
<span class="ec-save-modal-diff text-body">
|
||||
<span class="text-muted text-decoration-line-through">@FormatValue(change.OldValue)</span>
|
||||
<span class="text-muted text-decoration-line-through">@FormatValue(change.OldValue, change.OptionName)</span>
|
||||
<span class="text-muted mx-1" aria-hidden="true">→</span>
|
||||
<span>@FormatValue(change.NewValue)</span>
|
||||
<span>@FormatValue(change.NewValue, change.OptionName)</span>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
@@ -180,7 +210,7 @@
|
||||
else if (SaveState != EffectiveConfigSaveFlow.SaveUiState.Saving && SaveState != EffectiveConfigSaveFlow.SaveUiState.RestoreInProgress)
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="Close"><i class="bi bi-x me-1" aria-hidden="true"></i>Cancel</button>
|
||||
<button type="button" class="btn btn-primary" @onclick="Save" disabled="@(PendingChanges.Count == 0)"><i class="bi bi-floppy-fill me-1" aria-hidden="true"></i>Save</button>
|
||||
<button type="button" class="btn btn-primary" @onclick="Save" disabled="@(PendingChanges.Count == 0 || HasBlockingValidationErrors)"><i class="bi bi-floppy-fill me-1" aria-hidden="true"></i>Save</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -206,6 +236,10 @@
|
||||
|
||||
private string? ManagedFilePath => Status?.ManagedFilePath;
|
||||
|
||||
private IReadOnlyList<FieldIssue> ValidationErrors => Session.GetValidationSummary().Where(i => i.Severity == FieldIssueSeverity.Error).ToList();
|
||||
private IReadOnlyList<FieldIssue> 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);
|
||||
|
||||
@@ -76,7 +76,7 @@ else if (_status != null)
|
||||
</div>
|
||||
}
|
||||
|
||||
<StatusSection RefreshIntervalSeconds="@ClientSettingsFields.ServiceStatusPollingInterval.Value" IsReloading="_reloading" OnOpenSettings="OpenServiceStatusSettings" />
|
||||
<StatusSection RefreshIntervalSeconds="@ClientSettingsFields.ServiceStatusPollingInterval.Value" IsReloading="_reloading" OnReloadRequested="ReloadDnsmasq" ReloadCommandConfigured="_status.ReloadCommandConfigured" OnOpenSettings="OpenServiceStatusSettings" />
|
||||
<LogsSection Status="_status" RefreshIntervalSeconds="@ClientSettingsFields.RecentLogsPollingInterval.Value" LogsMaxLines="@ClientSettingsFields.RecentLogsMaxLines.Value" LogsAutoScroll="@ClientSettingsFields.RecentLogsAutoScroll.Value" OnOpenSettings="OpenRecentLogsSettings" />
|
||||
<AppLogsSection RefreshIntervalSeconds="@ClientSettingsFields.AppLogsPollingInterval.Value" LogsMaxLines="@ClientSettingsFields.AppLogsMaxLines.Value" LogsAutoScroll="@ClientSettingsFields.AppLogsAutoScroll.Value" OnOpenSettings="OpenAppLogsSettings" />
|
||||
</section>
|
||||
|
||||
@@ -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<ReloadController> _logger;
|
||||
|
||||
public ReloadController(IReloadService reloadService, ILogger<ReloadController> logger)
|
||||
public ReloadController(IReloadService reloadService, IConfigSetCache configSetCache, ILogger<ReloadController> 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)
|
||||
|
||||
@@ -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";
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
/// <summary>Returns a short tooltip for the option, or null if none is defined.</summary>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
/// <summary>Returns Single or Multi for the given option name; defaults to Single if unknown.</summary>
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
/// <summary>Returns LastWins, Flag, or Multi for the given option name; defaults to LastWins if unknown.</summary>
|
||||
public static EffectiveConfigParserBehavior GetBehavior(string optionName) =>
|
||||
BehaviorByOptionName.TryGetValue(optionName, out var b) ? b : EffectiveConfigParserBehavior.LastWins;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How to serialize an option when writing the managed config file.
|
||||
/// Used by DnsmasqConfigService so KeyOnlyOrValue and InversePair options serialize correctly.
|
||||
/// </summary>
|
||||
public enum EffectiveConfigWriteBehavior
|
||||
{
|
||||
Flag,
|
||||
SingleValue,
|
||||
MultiValue,
|
||||
KeyOnlyOrValue,
|
||||
InversePair,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static class EffectiveConfigWriteBehaviorMap
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<string, EffectiveConfigWriteBehavior> BehaviorByOptionName =
|
||||
new Dictionary<string, EffectiveConfigWriteBehavior>(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,
|
||||
};
|
||||
|
||||
/// <summary>For InversePair options: (key for "enabled", key for "disabled"). Null if not an InversePair or unknown.</summary>
|
||||
private static readonly IReadOnlyDictionary<string, (string KeyA, string KeyB)> InversePairKeysByOptionName =
|
||||
new Dictionary<string, (string, string)>(StringComparer.Ordinal)
|
||||
{
|
||||
[DnsmasqConfKeys.Do0x20Encode] = (DnsmasqConfKeys.Do0x20Encode, DnsmasqConfKeys.No0x20Encode),
|
||||
};
|
||||
|
||||
/// <summary>Returns write behavior for the option; defaults to SingleValue if unknown.</summary>
|
||||
public static EffectiveConfigWriteBehavior GetBehavior(string optionName) =>
|
||||
BehaviorByOptionName.TryGetValue(optionName, out var b) ? b : EffectiveConfigWriteBehavior.SingleValue;
|
||||
|
||||
/// <summary>Returns the pair of config keys (enabled, disabled) for an InversePair option; null otherwise.</summary>
|
||||
public static (string KeyA, string KeyB)? GetInversePairKeys(string optionName) =>
|
||||
InversePairKeysByOptionName.TryGetValue(optionName, out var pair) ? pair : null;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
]),
|
||||
];
|
||||
|
||||
|
||||
@@ -293,6 +293,23 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable
|
||||
var dhcpHostsfilePaths = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpHostsfile);
|
||||
var dhcpOptsfilePaths = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpOptsfile);
|
||||
var dhcpHostsdirPaths = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpHostsdir);
|
||||
var connmarkAllowlistValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.ConnmarkAllowlist);
|
||||
var caaRecordValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.CaaRecord);
|
||||
var dnsRrValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DnsRr);
|
||||
var synthDomainValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.SynthDomain);
|
||||
var authZoneValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.AuthZone);
|
||||
var authSoaValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.AuthSoa);
|
||||
var authSecServersValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.AuthSecServers);
|
||||
var authPeerValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.AuthPeer);
|
||||
var dhcpRelayValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpRelay);
|
||||
var dhcpCircuitidValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpCircuitid);
|
||||
var dhcpRemoteidValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpRemoteid);
|
||||
var dhcpSubscridValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpSubscrid);
|
||||
var dhcpProxyValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpProxy);
|
||||
var tagIfValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.TagIf);
|
||||
var bridgeInterfaceValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.BridgeInterface);
|
||||
var sharedNetworkValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.SharedNetwork);
|
||||
var dhcpOptionPxeValues = (IReadOnlyList<string>)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<string>(), IpsetValues: Array.Empty<string>(), NftsetValues: Array.Empty<string>(),
|
||||
DhcpMacValues: Array.Empty<string>(), DhcpNameMatchValues: Array.Empty<string>(), DhcpIgnoreNamesValues: Array.Empty<string>(),
|
||||
DhcpHostsfilePaths: Array.Empty<string>(), DhcpOptsfilePaths: Array.Empty<string>(), DhcpHostsdirPaths: Array.Empty<string>(),
|
||||
ConnmarkAllowlistValues: Array.Empty<string>(), CaaRecordValues: Array.Empty<string>(), DnsRrValues: Array.Empty<string>(), SynthDomainValues: Array.Empty<string>(),
|
||||
AuthZoneValues: Array.Empty<string>(), AuthSoaValues: Array.Empty<string>(), AuthSecServersValues: Array.Empty<string>(), AuthPeerValues: Array.Empty<string>(),
|
||||
DhcpRelayValues: Array.Empty<string>(), DhcpCircuitidValues: Array.Empty<string>(), DhcpRemoteidValues: Array.Empty<string>(), DhcpSubscridValues: Array.Empty<string>(), DhcpProxyValues: Array.Empty<string>(),
|
||||
TagIfValues: Array.Empty<string>(), BridgeInterfaceValues: Array.Empty<string>(), SharedNetworkValues: Array.Empty<string>(), DhcpOptionPxeValues: Array.Empty<string>(),
|
||||
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<ValueWithSource>(), IpsetValues: Array.Empty<ValueWithSource>(), NftsetValues: Array.Empty<ValueWithSource>(),
|
||||
DhcpMacValues: Array.Empty<ValueWithSource>(), DhcpNameMatchValues: Array.Empty<ValueWithSource>(), DhcpIgnoreNamesValues: Array.Empty<ValueWithSource>(),
|
||||
DhcpHostsfilePaths: Array.Empty<ValueWithSource>(), DhcpOptsfilePaths: Array.Empty<ValueWithSource>(), DhcpHostsdirPaths: Array.Empty<ValueWithSource>(),
|
||||
ConnmarkAllowlistValues: Array.Empty<ValueWithSource>(), CaaRecordValues: Array.Empty<ValueWithSource>(), DnsRrValues: Array.Empty<ValueWithSource>(), SynthDomainValues: Array.Empty<ValueWithSource>(),
|
||||
AuthZoneValues: Array.Empty<ValueWithSource>(), AuthSoaValues: Array.Empty<ValueWithSource>(), AuthSecServersValues: Array.Empty<ValueWithSource>(), AuthPeerValues: Array.Empty<ValueWithSource>(),
|
||||
DhcpRelayValues: Array.Empty<ValueWithSource>(), DhcpCircuitidValues: Array.Empty<ValueWithSource>(), DhcpRemoteidValues: Array.Empty<ValueWithSource>(), DhcpSubscridValues: Array.Empty<ValueWithSource>(), DhcpProxyValues: Array.Empty<ValueWithSource>(),
|
||||
TagIfValues: Array.Empty<ValueWithSource>(), BridgeInterfaceValues: Array.Empty<ValueWithSource>(), SharedNetworkValues: Array.Empty<ValueWithSource>(), DhcpOptionPxeValues: Array.Empty<ValueWithSource>(),
|
||||
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)
|
||||
|
||||
@@ -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<string> readonlyValues = readonlyByOption.TryGetValue(confKey, out var listValues) ? listValues : Array.Empty<string>();
|
||||
@@ -299,6 +335,15 @@ public class DnsmasqConfigService : IDnsmasqConfigService
|
||||
await WriteManagedConfigAsync(list, ct);
|
||||
}
|
||||
|
||||
private static void RemoveAllMatchingLines(List<DnsmasqConfLine> list, Predicate<DnsmasqConfLine> match)
|
||||
{
|
||||
for (var i = list.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (match(list[i]))
|
||||
list.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Keeps only values that should be written to managed config by subtracting values provided by
|
||||
/// non-managed files (multiset subtraction, order preserved).
|
||||
|
||||
+45
@@ -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);
|
||||
|
||||
+42
-8
@@ -19,7 +19,7 @@ public static class EffectiveConfigCrossOptionEvaluator
|
||||
{
|
||||
var issues = new List<FieldIssue>();
|
||||
|
||||
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<PendingEffectiveConfigChange> pending)
|
||||
private static bool GetEffectiveBool(
|
||||
DnsmasqServiceStatus? status,
|
||||
IReadOnlyList<PendingEffectiveConfigChange>? pending,
|
||||
string optionName,
|
||||
Func<DnsmasqServiceStatus?, bool> 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<PendingEffectiveConfigChange>? pending,
|
||||
string optionName,
|
||||
Func<DnsmasqServiceStatus?, int?> 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<string>? GetEffectiveServerValues(DnsmasqServiceStatus? status, IReadOnlyList<PendingEffectiveConfigChange> pending)
|
||||
|
||||
@@ -65,6 +65,23 @@ public record EffectiveConfigSources(
|
||||
IReadOnlyList<ValueWithSource> DhcpHostsfilePaths,
|
||||
IReadOnlyList<ValueWithSource> DhcpOptsfilePaths,
|
||||
IReadOnlyList<ValueWithSource> DhcpHostsdirPaths,
|
||||
IReadOnlyList<ValueWithSource> ConnmarkAllowlistValues,
|
||||
IReadOnlyList<ValueWithSource> CaaRecordValues,
|
||||
IReadOnlyList<ValueWithSource> DnsRrValues,
|
||||
IReadOnlyList<ValueWithSource> SynthDomainValues,
|
||||
IReadOnlyList<ValueWithSource> AuthZoneValues,
|
||||
IReadOnlyList<ValueWithSource> AuthSoaValues,
|
||||
IReadOnlyList<ValueWithSource> AuthSecServersValues,
|
||||
IReadOnlyList<ValueWithSource> AuthPeerValues,
|
||||
IReadOnlyList<ValueWithSource> DhcpRelayValues,
|
||||
IReadOnlyList<ValueWithSource> DhcpCircuitidValues,
|
||||
IReadOnlyList<ValueWithSource> DhcpRemoteidValues,
|
||||
IReadOnlyList<ValueWithSource> DhcpSubscridValues,
|
||||
IReadOnlyList<ValueWithSource> DhcpProxyValues,
|
||||
IReadOnlyList<ValueWithSource> TagIfValues,
|
||||
IReadOnlyList<ValueWithSource> BridgeInterfaceValues,
|
||||
IReadOnlyList<ValueWithSource> SharedNetworkValues,
|
||||
IReadOnlyList<ValueWithSource> 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
|
||||
);
|
||||
|
||||
@@ -69,6 +69,23 @@ public record EffectiveDnsmasqConfig(
|
||||
IReadOnlyList<string> DhcpHostsfilePaths,
|
||||
IReadOnlyList<string> DhcpOptsfilePaths,
|
||||
IReadOnlyList<string> DhcpHostsdirPaths,
|
||||
IReadOnlyList<string> ConnmarkAllowlistValues,
|
||||
IReadOnlyList<string> CaaRecordValues,
|
||||
IReadOnlyList<string> DnsRrValues,
|
||||
IReadOnlyList<string> SynthDomainValues,
|
||||
IReadOnlyList<string> AuthZoneValues,
|
||||
IReadOnlyList<string> AuthSoaValues,
|
||||
IReadOnlyList<string> AuthSecServersValues,
|
||||
IReadOnlyList<string> AuthPeerValues,
|
||||
IReadOnlyList<string> DhcpRelayValues,
|
||||
IReadOnlyList<string> DhcpCircuitidValues,
|
||||
IReadOnlyList<string> DhcpRemoteidValues,
|
||||
IReadOnlyList<string> DhcpSubscridValues,
|
||||
IReadOnlyList<string> DhcpProxyValues,
|
||||
IReadOnlyList<string> TagIfValues,
|
||||
IReadOnlyList<string> BridgeInterfaceValues,
|
||||
IReadOnlyList<string> SharedNetworkValues,
|
||||
IReadOnlyList<string> 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
|
||||
)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public enum ExplicitToggleState
|
||||
{
|
||||
Default,
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<dt><b>--add-cpe-id=<string></b></dt>
|
||||
<dd>Add an arbitrary identifying string to DNS queries which are
|
||||
forwarded upstream.</dd>
|
||||
@@ -0,0 +1,10 @@
|
||||
<dt><b>--add-mac[=base64|text]</b></dt>
|
||||
<dd>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 <b>--add-subnet</b> applies to <b>--add-mac</b> 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.</dd>
|
||||
@@ -0,0 +1,23 @@
|
||||
<dt><b>--add-subnet[[=[<IPv4 address>/]<IPv4 prefix length>][,[<IPv6 address>/]<IPv6 prefix length>]]</b></dt>
|
||||
<dd>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.
|
||||
<p>
|
||||
For example,
|
||||
<b>--add-subnet=24,96</b>
|
||||
|
||||
will add the /24 and /96 subnets of the requestor for IPv4 and IPv6 requestors, respectively.
|
||||
<b>--add-subnet=1.2.3.4/24</b>
|
||||
|
||||
will add 1.2.3.0/24 for IPv4 requestors and ::/0 for IPv6 requestors.
|
||||
<b>--add-subnet=1.2.3.4/24,1.2.3.4/24</b>
|
||||
|
||||
will add 1.2.3.0/24 for both IPv4 and IPv6 requestors.</dd>
|
||||
@@ -0,0 +1,12 @@
|
||||
<dt><b>--auth-peer=<ip-address>[,<ip-address>[,<ip-address>...]]</b></dt>
|
||||
<dd>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
|
||||
<b>--auth-peer</b>
|
||||
|
||||
without
|
||||
<b>--auth-sec-servers</b>
|
||||
|
||||
enables zone transfer but does not advertise the secondary in NS records returned by dnsmasq.</dd>
|
||||
@@ -0,0 +1,5 @@
|
||||
<dt><b>--auth-sec-servers=<domain>[,<domain>[,<domain>...]]</b></dt>
|
||||
<dd>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.</dd>
|
||||
@@ -0,0 +1,3 @@
|
||||
<dt><b>--auth-soa=<serial>[,<hostmaster>[,<refresh>[,<retry>[,<expiry>]]]]</b></dt>
|
||||
<dd>Specify fields in the SOA record associated with authoritative
|
||||
zones. Note that this is optional, all the values are set to sane defaults.</dd>
|
||||
@@ -0,0 +1,28 @@
|
||||
<dt><b>--auth-zone=<domain>[,<subnet>[/<prefix length>][,<subnet>[/<prefix length>].....][,exclude:<subnet>[/<prefix length>]].....]</b></dt>
|
||||
<dd>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.
|
||||
<p>
|
||||
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 <b>--auth-zone</b> declaration.
|
||||
<p>
|
||||
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).
|
||||
<p>
|
||||
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.</dd>
|
||||
@@ -0,0 +1,7 @@
|
||||
<dt><b>-3, --bootp-dynamic[=<network-id>[,<network-id>]]</b></dt>
|
||||
<dd>(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.</dd>
|
||||
@@ -0,0 +1,12 @@
|
||||
<dt><b>--bridge-interface=<interface>,<alias>[,<alias>]</b></dt>
|
||||
<dd>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>.
|
||||
<p>
|
||||
It is permissible to add more than one alias using more than one <b>--bridge-interface</b> option since
|
||||
<b>--bridge-interface=int1,alias1,alias2</b> is exactly equivalent to
|
||||
<b>--bridge-interface=int1,alias1 --bridge-interface=int1,alias2</b></dd>
|
||||
@@ -0,0 +1,2 @@
|
||||
<dt><b>--caa-record=<name>,<flags>,<tag>,<value></b></dt>
|
||||
<dd>Return a CAA DNS record, as specified in RFC6844.</dd>
|
||||
@@ -0,0 +1,10 @@
|
||||
<dt><b>--connmark-allowlist-enable[=<mask>]</b></dt>
|
||||
<dd>Enables filtering of incoming DNS queries with associated Linux connection track marks
|
||||
according to individual allowlists configured via a series of <b>--connmark-allowlist</b>
|
||||
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.</dd>
|
||||
@@ -0,0 +1,14 @@
|
||||
<dt><b>--connmark-allowlist=<connmark>[/<mask>][,<pattern>[/<pattern>...]]</b></dt>
|
||||
<dd>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.</dd>
|
||||
@@ -0,0 +1,7 @@
|
||||
<dt><b>--dhcp-alternate-port[=<server port>[,<client port>]]</b></dt>
|
||||
<dd>(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.</dd>
|
||||
@@ -0,0 +1,6 @@
|
||||
<dt><b>--dhcp-broadcast[=tag:<tag>[,tag:<tag>]]</b></dt>
|
||||
<dd>(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.</dd>
|
||||
@@ -0,0 +1,10 @@
|
||||
<dt><b>--dhcp-circuitid=set:<tag>,<circuit-id>, --dhcp-remoteid=set:<tag>,<remote-id></b></dt>
|
||||
<dd>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.
|
||||
<p>
|
||||
<b>--dhcp-remoteid</b>
|
||||
|
||||
(but not <b>--dhcp-circuitid</b>) is supported in IPv6.</dd>
|
||||
@@ -0,0 +1,9 @@
|
||||
<dt><b>--dhcp-duid=<enterprise-id>,<uid></b></dt>
|
||||
<dd>(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.</dd>
|
||||
@@ -0,0 +1,8 @@
|
||||
<dt><b>--dhcp-generate-names=tag:<tag>[,tag:<tag>]</b></dt>
|
||||
<dd>(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
|
||||
<b>--dhcp-ignore-names </b>
|
||||
|
||||
is set.</dd>
|
||||
@@ -0,0 +1,5 @@
|
||||
<dt><b>--dhcp-ignore-clid</b></dt>
|
||||
<dd>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.</dd>
|
||||
@@ -0,0 +1,73 @@
|
||||
<dt><b>--dhcp-luascript=<path></b></dt>
|
||||
<dd>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
|
||||
<b>lease</b>
|
||||
|
||||
function, and may provide
|
||||
<b>init</b>
|
||||
|
||||
and
|
||||
<b>shutdown</b>
|
||||
|
||||
functions, which are called, without arguments when dnsmasq starts up
|
||||
and terminates. It may also provide a
|
||||
<b>tftp</b>
|
||||
|
||||
function.
|
||||
<p>
|
||||
The
|
||||
<b>lease</b>
|
||||
|
||||
function receives the information detailed in
|
||||
<b>--dhcp-script. </b>
|
||||
|
||||
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
|
||||
<b>--dhcp-script. </b>
|
||||
|
||||
These are
|
||||
<b>mac_address, ip_address</b>
|
||||
|
||||
and
|
||||
<b>hostname</b>
|
||||
|
||||
for IPv4, and
|
||||
<b>client_duid, ip_address</b>
|
||||
|
||||
and
|
||||
<b>hostname</b>
|
||||
|
||||
for IPv6.
|
||||
<p>
|
||||
The
|
||||
<b>tftp</b>
|
||||
|
||||
function is called in the same way as the lease function, and the
|
||||
table holds the tags
|
||||
<b>destination_address,</b>
|
||||
<b>file_name</b>
|
||||
|
||||
and
|
||||
<b>file_size.</b>
|
||||
<p>
|
||||
The
|
||||
<b>arp</b>
|
||||
|
||||
and
|
||||
<b>arp-old</b>
|
||||
|
||||
functions are called only when enabled with
|
||||
<b>--script-arp</b>
|
||||
|
||||
and have a table which holds the tags
|
||||
<b>mac_address</b>
|
||||
|
||||
and
|
||||
<b>client_address.</b></dd>
|
||||
@@ -0,0 +1,7 @@
|
||||
<dt><b>--dhcp-no-override</b></dt>
|
||||
<dd>(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 <b>--dhcp-boot</b>) 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.</dd>
|
||||
@@ -0,0 +1,3 @@
|
||||
<dt><b>--dhcp-option-pxe=[tag:<tag>,[tag:<tag>,]][encap:<opt>,][vi-encap:<enterprise>,]<opt>,[<value>[,<value>]]</b></dt>
|
||||
<dd>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.</dd>
|
||||
@@ -0,0 +1,18 @@
|
||||
<dt><b>--dhcp-proxy[=<ip addr>]......</b></dt>
|
||||
<dd>(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
|
||||
<b>--dhcp-circuitid</b>
|
||||
|
||||
and
|
||||
<b>--dhcp-remoteid.</b>
|
||||
|
||||
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.</dd>
|
||||
@@ -0,0 +1,16 @@
|
||||
<dt><b>--dhcp-pxe-vendor=<vendor>[,...]</b></dt>
|
||||
<dd>According to UEFI and PXE specifications, DHCP packets between PXE clients and
|
||||
proxy PXE servers should have
|
||||
<i>PXEClient </i>
|
||||
|
||||
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
|
||||
<p>
|
||||
<b>--dhcp-pxe-vendor=PXEClient,HW-Client</b>
|
||||
<p>
|
||||
will enable dnsmasq to also provide proxy PXE service to those PXE clients with
|
||||
<i>HW-Client</i>
|
||||
|
||||
in as their identifier.</dd>
|
||||
@@ -0,0 +1,39 @@
|
||||
<dt><b>--dhcp-relay=<local address>[,<server address>[#<server port>]][,<interface]</b></dt>
|
||||
<dd>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 <b>--dhcp-relay</b>
|
||||
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 <b>--dhcp-proxy</b> 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.
|
||||
<p>
|
||||
Access control for DHCP clients has the same rules as for the DHCP
|
||||
server, see <b>--interface</b>, <b>--except-interface</b>, etc. The optional
|
||||
interface name in the <b>--dhcp-relay</b> 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.
|
||||
<p>
|
||||
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.
|
||||
<p>
|
||||
Both DHCPv4 and DHCPv6 relay is supported. It's not possible to relay
|
||||
DHCPv4 to a DHCPv6 server or vice-versa.
|
||||
<p>
|
||||
The DHCP relay function for IPv6 includes the ability to snoop
|
||||
prefix-delegation from relayed DHCP transactions. See
|
||||
<b>--dhcp-script</b>
|
||||
|
||||
for details.</dd>
|
||||
@@ -0,0 +1,10 @@
|
||||
<dt><b>--dhcp-circuitid=set:<tag>,<circuit-id>, --dhcp-remoteid=set:<tag>,<remote-id></b></dt>
|
||||
<dd>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.
|
||||
<p>
|
||||
<b>--dhcp-remoteid</b>
|
||||
|
||||
(but not <b>--dhcp-circuitid</b>) is supported in IPv6.</dd>
|
||||
@@ -0,0 +1,2 @@
|
||||
<dt><b>--dhcp-scriptuser</b></dt>
|
||||
<dd>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.</dd>
|
||||
@@ -0,0 +1,12 @@
|
||||
<dt><b>--dhcp-sequential-ip</b></dt>
|
||||
<dd>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.</dd>
|
||||
@@ -0,0 +1,2 @@
|
||||
<dt><b>--dhcp-subscrid=set:<tag>,<subscriber-id></b></dt>
|
||||
<dd>(IPv4 and IPv6) Map from RFC3993 subscriber-id relay agent options to tags.</dd>
|
||||
@@ -0,0 +1,6 @@
|
||||
<dt><b>-0, --dns-forward-max=<queries></b></dt>
|
||||
<dd>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.</dd>
|
||||
@@ -0,0 +1,5 @@
|
||||
<dt><b>--dns-rr=<name>,<RR-number>,[<hex data>]</b></dt>
|
||||
<dd>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.</dd>
|
||||
@@ -0,0 +1,6 @@
|
||||
<dt><b>--dnssec-debug</b></dt>
|
||||
<dd>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.</dd>
|
||||
@@ -0,0 +1,8 @@
|
||||
<dt><b>--dnssec-limits=<limit>[,<limit>.......]</b></dt>
|
||||
<dd>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
|
||||
<b>--dnssec-limits=0,0,20</b> sets the number of sub-queries to 20 whilst leaving the other limits at default values.</dd>
|
||||
@@ -0,0 +1,12 @@
|
||||
<dt><b>--dnssec-no-timecheck</b></dt>
|
||||
<dd>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.
|
||||
<p>
|
||||
Earlier versions of dnsmasq overloaded SIGHUP (which re-reads much configuration) to also enable time validation.
|
||||
<p>
|
||||
If dnsmasq is run in debug mode (<b>--no-daemon</b> flag) then SIGINT retains its usual meaning of terminating the dnsmasq process.</dd>
|
||||
@@ -0,0 +1,6 @@
|
||||
<dt><b>--dnssec-timestamp=<path></b></dt>
|
||||
<dd>Enables an alternative way of checking the validity of the system time for DNSSEC (see <b>--dnssec-no-timecheck</b>). 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.</dd>
|
||||
@@ -0,0 +1,11 @@
|
||||
<dt><b>--do-0x20-encode, --no-0x20-encode</b></dt>
|
||||
<dd>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.</dd>
|
||||
@@ -0,0 +1,2 @@
|
||||
<dt><b>--dumpfile=<path/to/file></b></dt>
|
||||
<dd>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.</dd>
|
||||
@@ -0,0 +1,3 @@
|
||||
<dt><b>--dumpmask=<mask></b></dt>
|
||||
<dd>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.</dd>
|
||||
@@ -0,0 +1,11 @@
|
||||
<dt><b>--leasequery[=<addr>[/<prefix>]]</b></dt>
|
||||
<dd>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 <b>--leasequery</b> 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.</dd>
|
||||
@@ -0,0 +1,5 @@
|
||||
<dt><b>-L, --localmx</b></dt>
|
||||
<dd>Return an MX record pointing to the host given by <b>--mx-target</b> (or the
|
||||
machine on which dnsmasq is running) for each
|
||||
local machine. Local machines are those in /etc/hosts or with DHCP
|
||||
leases.</dd>
|
||||
@@ -0,0 +1,6 @@
|
||||
<dt><b>-5, --no-ping</b></dt>
|
||||
<dd>(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.</dd>
|
||||
@@ -0,0 +1,5 @@
|
||||
<dt><b>--no-round-robin</b></dt>
|
||||
<dd>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.</dd>
|
||||
@@ -0,0 +1,5 @@
|
||||
<dt><b>--quiet-dhcp, --quiet-dhcp6, --quiet-ra, --quiet-tftp</b></dt>
|
||||
<dd>Suppress logging of the routine operation of these protocols. Errors and
|
||||
problems will still be logged. <b>--quiet-tftp</b> does not consider file not
|
||||
found to be an error. <b>--quiet-dhcp</b> and quiet-dhcp6 are over-ridden by
|
||||
<b>--log-dhcp</b>.</dd>
|
||||
@@ -0,0 +1,5 @@
|
||||
<dt><b>--quiet-dhcp, --quiet-dhcp6, --quiet-ra, --quiet-tftp</b></dt>
|
||||
<dd>Suppress logging of the routine operation of these protocols. Errors and
|
||||
problems will still be logged. <b>--quiet-tftp</b> does not consider file not
|
||||
found to be an error. <b>--quiet-dhcp</b> and quiet-dhcp6 are over-ridden by
|
||||
<b>--log-dhcp</b>.</dd>
|
||||
@@ -0,0 +1,5 @@
|
||||
<dt><b>--quiet-dhcp, --quiet-dhcp6, --quiet-ra, --quiet-tftp</b></dt>
|
||||
<dd>Suppress logging of the routine operation of these protocols. Errors and
|
||||
problems will still be logged. <b>--quiet-tftp</b> does not consider file not
|
||||
found to be an error. <b>--quiet-dhcp</b> and quiet-dhcp6 are over-ridden by
|
||||
<b>--log-dhcp</b>.</dd>
|
||||
@@ -0,0 +1,5 @@
|
||||
<dt><b>--quiet-dhcp, --quiet-dhcp6, --quiet-ra, --quiet-tftp</b></dt>
|
||||
<dd>Suppress logging of the routine operation of these protocols. Errors and
|
||||
problems will still be logged. <b>--quiet-tftp</b> does not consider file not
|
||||
found to be an error. <b>--quiet-dhcp</b> and quiet-dhcp6 are over-ridden by
|
||||
<b>--log-dhcp</b>.</dd>
|
||||
@@ -0,0 +1,2 @@
|
||||
<dt><b>--script-arp</b></dt>
|
||||
<dd>Enable the "arp" and "arp-old" functions in the <b>--dhcp-script</b> and <b>--dhcp-luascript</b>.</dd>
|
||||
@@ -0,0 +1,3 @@
|
||||
<dt><b>--script-on-renewal</b></dt>
|
||||
<dd>Call the DHCP script when the lease expiry time changes, for instance when the
|
||||
lease is renewed.</dd>
|
||||
@@ -0,0 +1,3 @@
|
||||
<dt><b>-e, --selfmx</b></dt>
|
||||
<dd>Return an MX record pointing to itself for each local
|
||||
machine. Local machines are those in /etc/hosts or with DHCP leases.</dd>
|
||||
@@ -0,0 +1,2 @@
|
||||
<dt><b>--shared-network=<interface>,<addr></b></dt>
|
||||
<dd></dd>
|
||||
@@ -0,0 +1,22 @@
|
||||
<dt><b>--synth-domain=<domain>,<address range>[,<prefix>[*]]</b></dt>
|
||||
<dd>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.
|
||||
<p>
|
||||
An examples should make this clearer. First sequential numbers.
|
||||
<b>--synth-domain=thekelleys.org.uk,192.168.0.50,192.168.0.70,internal-*</b>
|
||||
|
||||
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.
|
||||
<p>
|
||||
Second,
|
||||
<b>--synth-domain=thekelleys.org.uk,192.168.0.0/24,internal- (no *)</b>
|
||||
|
||||
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
|
||||
<p>
|
||||
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.</dd>
|
||||
@@ -0,0 +1,16 @@
|
||||
<dt><b>--tag-if=set:<tag>[,set:<tag>[,tag:<tag>[,tag:<tag>]]]</b></dt>
|
||||
<dd>Perform boolean operations on tags. All <b>set:<tag></b> tags are set if
|
||||
all <b>tag:<tag></b> are already set, (or unset when <b>tag:!<tag></b> is used).
|
||||
If no <b>tag:<tag></b> appears, <b>set:<tag></b> tags are set unconditionally.
|
||||
Any number of <b>set:</b> and <b>tag:</b> forms may appear, in any order.
|
||||
<b>--tag-if</b> lines are executed in order, so if the tag in <b>tag:<tag></b> is a
|
||||
tag set by another
|
||||
<b>--tag-if,</b>
|
||||
|
||||
the line which sets the tag must precede the line which tests it.
|
||||
<p>
|
||||
As an extension, the <b>tag:<tag></b> clauses support limited wildcard matching,
|
||||
similar to the matching in the <b>--interface</b> directive. This allows the
|
||||
example <b>--tag-if=set:ppp,tag:ppp*</b> to set the tag 'ppp' for all requests
|
||||
received on any matching interface (ppp0, ppp1, etc). This can be used in conjunction
|
||||
with the <b>tag:!<tag></b> format meaning that no tag matching the wildcard may be set.</dd>
|
||||
@@ -0,0 +1,7 @@
|
||||
<dt><b>--umbrella[=[deviceid:<deviceid>][,orgid:<orgid>][,assetid:<id>]]</b></dt>
|
||||
<dd>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.</dd>
|
||||
@@ -0,0 +1,6 @@
|
||||
<dt><b>--use-stale-cache[=<max TTL excess in s>]</b></dt>
|
||||
<dd>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.</dd>
|
||||
Vendored
+1
-1
@@ -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. |
|
||||
|
||||
Vendored
+6
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user