Add src files
This commit is contained in:
179
src/LibNftables/NftablesClient.cs
Normal file
179
src/LibNftables/NftablesClient.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibNftables;
|
||||
|
||||
/// <summary>
|
||||
/// Default high-level nftables client implementation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This client wraps <see cref="NftContext"/> and applies a command-centric workflow:
|
||||
/// validate (dry-run), apply, snapshot, and restore.
|
||||
/// </remarks>
|
||||
public sealed class NftablesClient : INftablesClient
|
||||
{
|
||||
private readonly NftablesClientOptions _options;
|
||||
private readonly System.Func<NftContext> _contextFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NftablesClient"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options">Optional client options.</param>
|
||||
/// <exception cref="NftUnsupportedException">Thrown when runtime/platform is unsupported.</exception>
|
||||
/// <exception cref="NftNativeLoadException">Thrown when required native runtime components cannot be loaded.</exception>
|
||||
public NftablesClient(NftablesClientOptions? options = null)
|
||||
: this(options, static () => new NftContext())
|
||||
{
|
||||
}
|
||||
|
||||
internal NftablesClient(NftablesClientOptions? options, System.Func<NftContext> contextFactory)
|
||||
{
|
||||
_options = options ?? new NftablesClientOptions();
|
||||
_contextFactory = contextFactory;
|
||||
NftRuntimeGuard.EnsureInitializedForLinuxX64();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public NftValidationResult Validate(NftApplyRequest request, CancellationToken ct = default)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
ValidateRequest(request);
|
||||
|
||||
try
|
||||
{
|
||||
CommandExecutionResult result = Execute(request, forceDryRun: true, ct);
|
||||
return new NftValidationResult(true, result.Output, result.Error);
|
||||
}
|
||||
catch (NftValidationException ex)
|
||||
{
|
||||
return new NftValidationResult(false, null, ex.NativeErrorOutput ?? ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<NftValidationResult> ValidateAsync(NftApplyRequest request, CancellationToken ct = default)
|
||||
=> Task.FromResult(Validate(request, ct));
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply(NftApplyRequest request, CancellationToken ct = default)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
ValidateRequest(request);
|
||||
_ = Execute(request, forceDryRun: null, ct);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task ApplyAsync(NftApplyRequest request, CancellationToken ct = default)
|
||||
{
|
||||
Apply(request, ct);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public NftSnapshot Snapshot(CancellationToken ct = default)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
NftApplyRequest request = NftApplyRequest.FromText(_options.SnapshotCommandText, dryRun: false);
|
||||
CommandExecutionResult result = Execute(request, forceDryRun: null, ct);
|
||||
string snapshotText = result.Output ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(snapshotText))
|
||||
{
|
||||
throw new NftException("Snapshot returned an empty ruleset output.", nativeErrorOutput: result.Error);
|
||||
}
|
||||
|
||||
return new NftSnapshot(snapshotText, System.DateTimeOffset.UtcNow);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<NftSnapshot> SnapshotAsync(CancellationToken ct = default)
|
||||
=> Task.FromResult(Snapshot(ct));
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Restore(NftSnapshot snapshot, CancellationToken ct = default)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
if (snapshot is null)
|
||||
{
|
||||
throw new NftValidationException("Snapshot must not be null.", 0, null);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(snapshot.RulesetText))
|
||||
{
|
||||
throw new NftValidationException("Snapshot ruleset text must not be empty.", 0, null);
|
||||
}
|
||||
|
||||
NftApplyRequest request = NftApplyRequest.FromText(snapshot.RulesetText, dryRun: false);
|
||||
_ = Execute(request, forceDryRun: null, ct);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task RestoreAsync(NftSnapshot snapshot, CancellationToken ct = default)
|
||||
{
|
||||
Restore(snapshot, ct);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private CommandExecutionResult Execute(NftApplyRequest request, bool? forceDryRun, CancellationToken ct)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
using NftContext ctx = _contextFactory();
|
||||
ConfigureContext(ctx, request, forceDryRun);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.RulesetText))
|
||||
{
|
||||
ctx.RunCommand(request.RulesetText);
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.RunCommandFromFile(request.RulesetFilePath!);
|
||||
}
|
||||
|
||||
return new CommandExecutionResult(ctx.GetOutputBuffer(), ctx.GetErrorBuffer());
|
||||
}
|
||||
|
||||
private void ConfigureContext(NftContext ctx, NftApplyRequest request, bool? forceDryRun)
|
||||
{
|
||||
ctx.DryRun = forceDryRun ?? request.DryRun;
|
||||
ctx.InputFlags = _options.DefaultInputFlags | request.InputFlags;
|
||||
ctx.OutputFlags = _options.DefaultOutputFlags | request.OutputFlags;
|
||||
ctx.DebugFlags = _options.DefaultDebugFlags | request.DebugFlags;
|
||||
ctx.OptimizeFlags = _options.DefaultOptimizeFlags | request.OptimizeFlags;
|
||||
|
||||
if (_options.BufferOutput)
|
||||
{
|
||||
ctx.BufferOutput();
|
||||
}
|
||||
|
||||
if (_options.BufferError)
|
||||
{
|
||||
ctx.BufferError();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateRequest(NftApplyRequest request)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
throw new NftValidationException("Request must not be null.", 0, null);
|
||||
}
|
||||
|
||||
bool hasText = !string.IsNullOrWhiteSpace(request.RulesetText);
|
||||
bool hasFile = !string.IsNullOrWhiteSpace(request.RulesetFilePath);
|
||||
|
||||
if (hasText == hasFile)
|
||||
{
|
||||
throw new NftValidationException("Exactly one source must be provided: RulesetText or RulesetFilePath.", 0, null);
|
||||
}
|
||||
|
||||
if (hasFile && !System.IO.File.Exists(request.RulesetFilePath))
|
||||
{
|
||||
throw new NftValidationException($"Ruleset file was not found: '{request.RulesetFilePath}'.", 0, null);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record CommandExecutionResult(string? Output, string? Error);
|
||||
}
|
||||
Reference in New Issue
Block a user