Add typed high-level ruleset and set API

This commit is contained in:
Vibe Myass
2026-03-16 03:51:49 +00:00
parent 458494221e
commit 1dfc6aebfd
12 changed files with 644 additions and 7 deletions

View File

@@ -0,0 +1,99 @@
namespace LibNftables.Tests;
public sealed class NftRulesetRendererTests
{
[Fact]
public void Render_WithTypedSet_RendersExpectedCommands()
{
var ruleset = new NftRuleset();
var table = new NftTable
{
Family = NftFamily.Inet,
Name = "filter",
};
var set = new NftSet
{
Name = "blocked_ipv4",
Type = NftSetType.Ipv4Address,
};
set.Elements.Add("10.0.0.1");
set.Elements.Add("10.0.0.2");
table.Sets.Add(set);
ruleset.Tables.Add(table);
string rendered = NftRulesetRenderer.Render(ruleset);
Assert.Equal(
"add table inet filter" + Environment.NewLine +
"add set inet filter blocked_ipv4 { type ipv4_addr; elements = { 10.0.0.1, 10.0.0.2 }; }" + Environment.NewLine,
rendered);
}
[Fact]
public void Render_WithCustomTypeExpression_UsesCustomKeyword()
{
var ruleset = new NftRuleset();
var table = new NftTable
{
Family = NftFamily.Ip,
Name = "custom",
};
var set = new NftSet
{
Name = "ports",
CustomTypeExpression = "inet_service",
};
set.Elements.Add("80");
table.Sets.Add(set);
ruleset.Tables.Add(table);
string rendered = NftRulesetRenderer.Render(ruleset);
Assert.Contains("type inet_service;", rendered, StringComparison.Ordinal);
}
[Fact]
public void Render_WithNoTables_ThrowsValidationException()
{
Assert.Throws<NftValidationException>(() => NftRulesetRenderer.Render(new NftRuleset()));
}
[Fact]
public void Render_WithConflictingSetTypeSources_ThrowsValidationException()
{
var ruleset = new NftRuleset();
var table = new NftTable
{
Name = "filter",
};
table.Sets.Add(new NftSet
{
Name = "conflict",
Type = NftSetType.Ipv4Address,
CustomTypeExpression = "ipv4_addr",
});
ruleset.Tables.Add(table);
Assert.Throws<NftValidationException>(() => NftRulesetRenderer.Render(ruleset));
}
[Fact]
public void Render_WithInvalidElement_ThrowsValidationException()
{
var ruleset = new NftRuleset();
var table = new NftTable
{
Name = "filter",
};
var set = new NftSet
{
Name = "bad",
Type = NftSetType.Mark,
};
set.Elements.Add("1; drop");
table.Sets.Add(set);
ruleset.Tables.Add(table);
Assert.Throws<NftValidationException>(() => NftRulesetRenderer.Render(ruleset));
}
}

View File

@@ -71,6 +71,36 @@ public sealed class NftablesClientIntegrationTests
}
}
[Fact]
public void ValidateRuleset_WithTypedSetDefinition_ReturnsValidResult()
{
if (!CanCreateClient())
{
return;
}
var client = new NftablesClient();
var ruleset = new NftRuleset();
var table = new NftTable
{
Family = NftFamily.Inet,
Name = "typed_validation",
};
var set = new NftSet
{
Name = "blocked_ipv4",
Type = NftSetType.Ipv4Address,
};
set.Elements.Add("10.0.0.1");
set.Elements.Add("10.0.0.2");
table.Sets.Add(set);
ruleset.Tables.Add(table);
NftValidationResult result = client.ValidateRuleset(ruleset);
Assert.True(result.IsValid);
}
private static bool CanCreateClient()
{
try

View File

@@ -64,6 +64,55 @@ public sealed class NftablesClientUnitTests
Assert.Equal("ok", result.Output);
}
[Fact]
public void ValidateRuleset_RendersTypedRulesetAndUsesDryRun()
{
var context = new FakeContext
{
OutputBuffer = "ok",
ErrorBuffer = string.Empty,
};
var client = CreateClient(() => context);
NftValidationResult result = client.ValidateRuleset(CreateTypedRuleset());
Assert.True(result.IsValid);
Assert.True(context.DryRun);
Assert.Equal(
"add table inet filter" + Environment.NewLine +
"add set inet filter blocked_ipv4 { type ipv4_addr; elements = { 10.0.0.1, 10.0.0.2 }; }" + Environment.NewLine,
context.LastCommandText);
}
[Fact]
public void ApplyRuleset_RendersTypedRulesetAndExecutesCommand()
{
var context = new FakeContext();
var client = CreateClient(() => context);
client.ApplyRuleset(CreateTypedRuleset());
Assert.False(context.DryRun);
Assert.Equal(
"add table inet filter" + Environment.NewLine +
"add set inet filter blocked_ipv4 { type ipv4_addr; elements = { 10.0.0.1, 10.0.0.2 }; }" + Environment.NewLine,
context.LastCommandText);
}
[Fact]
public async Task ApplyRulesetAsync_UsesTypedRulesetText()
{
var context = new FakeContext();
var client = CreateClient(() => context);
await client.ApplyRulesetAsync(CreateTypedRuleset());
Assert.Equal(
"add table inet filter" + Environment.NewLine +
"add set inet filter blocked_ipv4 { type ipv4_addr; elements = { 10.0.0.1, 10.0.0.2 }; }" + Environment.NewLine,
context.LastCommandText);
}
[Fact]
public void Apply_WithFileRequest_UsesFileExecutionPath()
{
@@ -191,6 +240,26 @@ public sealed class NftablesClientUnitTests
NftablesClientOptions? options = null)
=> new(options, contextFactory ?? (() => new FakeContext()), skipRuntimeGuard: true);
private static NftRuleset CreateTypedRuleset()
{
var ruleset = new NftRuleset();
var table = new NftTable
{
Family = NftFamily.Inet,
Name = "filter",
};
var set = new NftSet
{
Name = "blocked_ipv4",
Type = NftSetType.Ipv4Address,
};
set.Elements.Add("10.0.0.1");
set.Elements.Add("10.0.0.2");
table.Sets.Add(set);
ruleset.Tables.Add(table);
return ruleset;
}
private sealed class FakeContext : INftContextHandle
{
public bool DryRun { get; set; }