namespace LibNftables.Tests; public sealed class NftRulesetRendererTests { [Fact] public void Render_WithTypedRuleAndMap_RendersExpectedCommands() { NftRuleset ruleset = CreateTypedRuleset(); 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 + "add map inet filter service_policy { type inet_service : verdict; elements = { 80 : accept, 443 : drop }; }" + Environment.NewLine + "add chain inet filter input { type filter hook input priority 0; policy drop; }" + Environment.NewLine + "add rule inet filter input iifname \"eth0\" ip saddr @blocked_ipv4 tcp dport 22 accept" + Environment.NewLine, rendered); } [Fact] public void Render_WithRenderHelperReadyRuleset_RendersMapValueVerdicts() { NftRuleset ruleset = CreateTypedRuleset(); string rendered = NftRulesetRenderer.Render(ruleset); Assert.Contains("80 : accept", rendered, StringComparison.Ordinal); Assert.Contains("443 : drop", rendered, StringComparison.Ordinal); } [Fact] public void Render_WithIncompatibleTypedMapKey_ThrowsValidationException() { var ruleset = new NftRuleset(); var table = new NftTable { Name = "filter", }; var map = new NftMap { Name = "bad_map", KeyType = NftMapType.InetService, ValueType = NftMapType.Verdict, }; map.Add(NftValue.Interface("eth0"), NftValue.Verdict(NftVerdict.Accept)); table.Maps.Add(map); ruleset.Tables.Add(table); Assert.Throws(() => NftRulesetRenderer.Render(ruleset)); } [Fact] public void Render_WithNoTables_ThrowsValidationException() { Assert.Throws(() => NftRulesetRenderer.Render(new NftRuleset())); } [Fact] public void Render_WithPortMatchWithoutProtocol_ThrowsValidationException() { var ruleset = new NftRuleset(); var table = new NftTable { Name = "filter", }; var chain = new NftChain { Name = "input", Type = NftChainType.Filter, Hook = NftHook.Input, Priority = 0, }; chain.Rules.Add(new NftRule { DestinationPort = NftValue.Port(22), Verdict = NftVerdict.Accept, }); table.Chains.Add(chain); ruleset.Tables.Add(table); Assert.Throws(() => NftRulesetRenderer.Render(ruleset)); } [Fact] public void Render_WithUnknownJumpTarget_ThrowsValidationException() { var ruleset = new NftRuleset(); var table = new NftTable { Name = "filter", }; var chain = new NftChain { Name = "input", Type = NftChainType.Filter, Hook = NftHook.Input, Priority = 0, }; chain.Rules.Add(new NftRule { Verdict = NftVerdict.Jump, JumpTarget = "missing", }); table.Chains.Add(chain); ruleset.Tables.Add(table); Assert.Throws(() => NftRulesetRenderer.Render(ruleset)); } [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(() => NftRulesetRenderer.Render(ruleset)); } 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(NftValue.Address(System.Net.IPAddress.Parse("10.0.0.1"))); set.Elements.Add(NftValue.Address(System.Net.IPAddress.Parse("10.0.0.2"))); table.Sets.Add(set); var map = new NftMap { Name = "service_policy", KeyType = NftMapType.InetService, ValueType = NftMapType.Verdict, }; map.Add(NftValue.Port(80), NftValue.Verdict(NftVerdict.Accept)); map.Add(NftValue.Port(443), NftValue.Verdict(NftVerdict.Drop)); table.Maps.Add(map); var chain = new NftChain { Name = "input", Type = NftChainType.Filter, Hook = NftHook.Input, Priority = 0, Policy = NftChainPolicy.Drop, }; chain.Rules.Add(new NftRule { InputInterface = NftValue.Interface("eth0"), SourceAddressSetName = "blocked_ipv4", TransportProtocol = NftTransportProtocol.Tcp, DestinationPort = NftValue.Port(22), Verdict = NftVerdict.Accept, }); table.Chains.Add(chain); ruleset.Tables.Add(table); return ruleset; } }