Effective config: conntrack as flag; avoid key-only directives for empty non-flag values

- Model conntrack as Flag (parser, config bool, cache, registry FlagValueDisplay)
- Apply: for non-flag options with empty/invalid value, remove existing line
  and never write key-only directive (was producing malformed option= without value)
This commit is contained in:
2026-03-02 22:52:27 +10:00
parent 5f728900dc
commit baaba38e23
6 changed files with 23 additions and 8 deletions
@@ -211,6 +211,11 @@
word-break: break-word; word-break: break-word;
} }
/* Row-level ok/cancel (and revert) for multi-value: right-align on the row below the list */
::deep .ec-field .ec-field-value-multi + .status-badge-inline {
margin-left: auto;
}
::deep .ec-field-value-list { ::deep .ec-field-value-list {
margin: 0; margin: 0;
padding-left: 1.25rem; padding-left: 1.25rem;
@@ -286,7 +286,7 @@ public static class EffectiveConfigParserBehaviorMap
[DnsmasqConfKeys.LogDhcp] = EffectiveConfigParserBehavior.Flag, [DnsmasqConfKeys.LogDhcp] = EffectiveConfigParserBehavior.Flag,
[DnsmasqConfKeys.KeepInForeground] = EffectiveConfigParserBehavior.Flag, [DnsmasqConfKeys.KeepInForeground] = EffectiveConfigParserBehavior.Flag,
[DnsmasqConfKeys.NoDaemon] = EffectiveConfigParserBehavior.Flag, [DnsmasqConfKeys.NoDaemon] = EffectiveConfigParserBehavior.Flag,
[DnsmasqConfKeys.Conntrack] = EffectiveConfigParserBehavior.LastWins, [DnsmasqConfKeys.Conntrack] = EffectiveConfigParserBehavior.Flag,
}; };
/// <summary>Returns LastWins, Flag, or Multi for the given option name; defaults to LastWins if unknown.</summary> /// <summary>Returns LastWins, Flag, or Multi for the given option name; defaults to LastWins if unknown.</summary>
@@ -391,8 +391,7 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable
var dhcpScriptPath = string.IsNullOrWhiteSpace(dhcpScriptVal) ? null : DnsmasqConfIncludeParser.ResolvePath(dhcpScriptVal?.Trim(), dhcpScriptDir); var dhcpScriptPath = string.IsNullOrWhiteSpace(dhcpScriptVal) ? null : DnsmasqConfIncludeParser.ResolvePath(dhcpScriptVal?.Trim(), dhcpScriptDir);
var (mxTargetVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.MxTarget); var (mxTargetVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.MxTarget);
var mxTarget = string.IsNullOrWhiteSpace(mxTargetVal) ? null : mxTargetVal.Trim(); var mxTarget = string.IsNullOrWhiteSpace(mxTargetVal) ? null : mxTargetVal.Trim();
var (conntrackVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Conntrack); var conntrack = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Conntrack);
var conntrack = string.IsNullOrWhiteSpace(conntrackVal) ? null : conntrackVal.Trim();
return new EffectiveDnsmasqConfig( return new EffectiveDnsmasqConfig(
noHosts, addnHosts, hostsdirPath, noHosts, addnHosts, hostsdirPath,
@@ -535,7 +534,7 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable
var (_, fastDnsRetrySource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.FastDnsRetry, managedFilePath); var (_, fastDnsRetrySource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.FastDnsRetry, managedFilePath);
var (_, dhcpScriptPathSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpScript, managedFilePath); var (_, dhcpScriptPathSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpScript, managedFilePath);
var (_, mxTargetSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.MxTarget, managedFilePath); var (_, mxTargetSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.MxTarget, managedFilePath);
var (_, conntrackSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Conntrack, managedFilePath); var (_, conntrackSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Conntrack, managedFilePath);
return new EffectiveConfigSources( return new EffectiveConfigSources(
noHostsSource, addnHostsWithSource.Select(t => new PathWithSource(t.Path, t.Source)).ToList(), hostsdirPathSource, noHostsSource, addnHostsWithSource.Select(t => new PathWithSource(t.Path, t.Source)).ToList(), hostsdirPathSource,
@@ -718,7 +717,7 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable
LogFacility: null, LogQueries: null, AuthTtl: null, EdnsPacketMax: null, QueryPort: null, PortLimit: null, MinPort: null, MaxPort: null, LogAsync: null, LocalService: 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, DhcpLeaseMax: null, NegTtl: null, MaxTtl: null, MaxCacheTtl: null, MinCacheTtl: null, DhcpTtl: null,
TftpRootPath: null, PxePrompt: null, EnableDbus: null, EnableUbus: null, FastDnsRetry: null, TftpRootPath: null, PxePrompt: null, EnableDbus: null, EnableUbus: null, FastDnsRetry: null,
DhcpScriptPath: null, MxTarget: null, Conntrack: null DhcpScriptPath: null, MxTarget: null, Conntrack: false
); );
private static EffectiveConfigSources CreateDefaultEffectiveConfigSources() => private static EffectiveConfigSources CreateDefaultEffectiveConfigSources() =>
@@ -271,13 +271,23 @@ public class DnsmasqConfigService : IDnsmasqConfigService
if (idx >= 0) list.RemoveAt(idx); if (idx >= 0) list.RemoveAt(idx);
continue; continue;
} }
if (!isFlag)
{
var v = ToConfValue(c.NewValue);
if (string.IsNullOrWhiteSpace(v))
{
// Non-flag option with no value: remove existing line if any; never write key-only (malformed).
if (idx >= 0) list.RemoveAt(idx);
continue;
}
}
string rawLine; string rawLine;
if (isFlag) if (isFlag)
rawLine = confKey; rawLine = confKey;
else else
{ {
var v = ToConfValue(c.NewValue); var v = ToConfValue(c.NewValue).Trim();
rawLine = string.IsNullOrEmpty(v) ? confKey : confKey + "=" + v; rawLine = confKey + "=" + v;
} }
var newLine = new OtherLine { LineNumber = maxLineNumber + 1, RawLine = rawLine }; var newLine = new OtherLine { LineNumber = maxLineNumber + 1, RawLine = rawLine };
maxLineNumber++; maxLineNumber++;
@@ -75,6 +75,7 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm
RegisterFlag(EffectiveConfigFieldBuilder.SectionProcess, DnsmasqConfKeys.LogDhcp); RegisterFlag(EffectiveConfigFieldBuilder.SectionProcess, DnsmasqConfKeys.LogDhcp);
RegisterFlag(EffectiveConfigFieldBuilder.SectionProcess, DnsmasqConfKeys.KeepInForeground); RegisterFlag(EffectiveConfigFieldBuilder.SectionProcess, DnsmasqConfKeys.KeepInForeground);
RegisterFlag(EffectiveConfigFieldBuilder.SectionProcess, DnsmasqConfKeys.NoDaemon); RegisterFlag(EffectiveConfigFieldBuilder.SectionProcess, DnsmasqConfKeys.NoDaemon);
RegisterFlag(EffectiveConfigFieldBuilder.SectionProcess, DnsmasqConfKeys.Conntrack);
RegisterFlag(EffectiveConfigFieldBuilder.SectionDnssec, DnsmasqConfKeys.ProxyDnssec); RegisterFlag(EffectiveConfigFieldBuilder.SectionDnssec, DnsmasqConfKeys.ProxyDnssec);
// log-queries: dropdown (Off / On / extra / proto / auth). // log-queries: dropdown (Off / On / extra / proto / auth).
@@ -139,7 +139,7 @@ public record EffectiveDnsmasqConfig(
string? FastDnsRetry, string? FastDnsRetry,
string? DhcpScriptPath, string? DhcpScriptPath,
string? MxTarget, string? MxTarget,
string? Conntrack bool Conntrack
) )
{ {
/// <summary> /// <summary>