Skip to content

Smdn.Net.AddressResolution version 1.0.0-preview5

Pre-release
Pre-release
Compare
Choose a tag to compare
@smdn smdn released this 23 Mar 12:38
· 186 commits to main since this release
e576389

Released package

Release notes

The full release notes are available at gist.

Change log

Change log in this release:

API changes

API changes in this release:
diff --git a/doc/api-list/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution-net6.0.apilist.cs b/doc/api-list/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution-net6.0.apilist.cs
index 8de9632..235f0f0 100644
--- a/doc/api-list/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution-net6.0.apilist.cs
+++ b/doc/api-list/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution-net6.0.apilist.cs
@@ -1,88 +1,92 @@
-// Smdn.Net.AddressResolution.dll (Smdn.Net.AddressResolution-1.0.0-preview4)
+// Smdn.Net.AddressResolution.dll (Smdn.Net.AddressResolution-1.0.0-preview5)
 //   Name: Smdn.Net.AddressResolution
 //   AssemblyVersion: 1.0.0.0
-//   InformationalVersion: 1.0.0-preview4+f17248a683dc95a5f8a1d3f3ec79fb49b8b2852f
+//   InformationalVersion: 1.0.0-preview5+fe261fa7dd16c43347c28935b3055ec3b8ab0674
 //   TargetFramework: .NETCoreApp,Version=v6.0
 //   Configuration: Release
 //   Referenced assemblies:
 //     Microsoft.Extensions.DependencyInjection.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
 //     Microsoft.Extensions.Logging.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
 //     System.Collections.Concurrent, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 //     System.ComponentModel, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 //     System.Diagnostics.Process, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 //     System.Linq, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 //     System.Memory, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
 //     System.Net.NetworkInformation, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 //     System.Net.Primitives, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 //     System.Runtime, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 //     System.Runtime.InteropServices.RuntimeInformation, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.Threading, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 #nullable enable annotations
 
 using System;
 using System.Net;
 using System.Net.NetworkInformation;
 using System.Threading;
 using System.Threading.Tasks;
 using Microsoft.Extensions.Logging;
 using Smdn.Net.AddressResolution;
 
 namespace Smdn.Net {
   public static class PhysicalAddressExtensions {
     public static string ToMacAddressString(this PhysicalAddress hardwareAddress, char delimiter = ':') {}
   }
 }
 
 namespace Smdn.Net.AddressResolution {
   public interface IAddressResolver<TAddress, TResolvedAddress> {
-    void Invalidate(TResolvedAddress resolvedAddress);
+    void Invalidate(TAddress address);
     ValueTask<TResolvedAddress> ResolveAsync(TAddress address, CancellationToken cancellationToken);
   }
 
   public abstract class MacAddressResolver :
     IAddressResolver<IPAddress, PhysicalAddress>,
     IAddressResolver<PhysicalAddress, IPAddress>,
     IDisposable
   {
     protected static readonly PhysicalAddress AllZeroMacAddress; // = "000000000000"
 
     public static MacAddressResolver Null { get; }
 
     public static MacAddressResolver Create(MacAddressResolverOptions? options = null, IServiceProvider? serviceProvider = null) {}
 
     protected MacAddressResolver(ILogger? logger = null) {}
 
     public abstract bool HasInvalidated { get; }
     protected ILogger? Logger { get; }
 
     protected virtual void Dispose(bool disposing) {}
     public void Dispose() {}
-    public void Invalidate(IPAddress resolvedIPAddress) {}
-    public void Invalidate(PhysicalAddress resolvedMacAddress) {}
-    protected abstract void InvalidateCore(IPAddress resolvedIPAddress);
-    protected abstract void InvalidateCore(PhysicalAddress resolvedMacAddress);
+    public void Invalidate(IPAddress ipAddress) {}
+    public void Invalidate(PhysicalAddress macAddress) {}
+    protected abstract void InvalidateCore(IPAddress ipAddress);
+    protected abstract void InvalidateCore(PhysicalAddress macAddress);
     public ValueTask RefreshCacheAsync(CancellationToken cancellationToken = default) {}
     protected virtual ValueTask RefreshCacheAsyncCore(CancellationToken cancellationToken) {}
     public ValueTask RefreshInvalidatedCacheAsync(CancellationToken cancellationToken = default) {}
     protected virtual ValueTask RefreshInvalidatedCacheAsyncCore(CancellationToken cancellationToken) {}
     public ValueTask<PhysicalAddress?> ResolveIPAddressToMacAddressAsync(IPAddress ipAddress, CancellationToken cancellationToken = default) {}
     protected abstract ValueTask<PhysicalAddress?> ResolveIPAddressToMacAddressAsyncCore(IPAddress ipAddress, CancellationToken cancellationToken);
     public ValueTask<IPAddress?> ResolveMacAddressToIPAddressAsync(PhysicalAddress macAddress, CancellationToken cancellationToken = default) {}
     protected abstract ValueTask<IPAddress?> ResolveMacAddressToIPAddressAsyncCore(PhysicalAddress macAddress, CancellationToken cancellationToken);
-    void IAddressResolver<IPAddress, PhysicalAddress>.Invalidate(PhysicalAddress resolvedAddress) {}
+    void IAddressResolver<IPAddress, PhysicalAddress>.Invalidate(IPAddress address) {}
     ValueTask<PhysicalAddress?> IAddressResolver<IPAddress, PhysicalAddress>.ResolveAsync(IPAddress address, CancellationToken cancellationToken) {}
-    void IAddressResolver<PhysicalAddress, IPAddress>.Invalidate(IPAddress resolvedAddress) {}
+    void IAddressResolver<PhysicalAddress, IPAddress>.Invalidate(PhysicalAddress address) {}
     ValueTask<IPAddress?> IAddressResolver<PhysicalAddress, IPAddress>.ResolveAsync(PhysicalAddress address, CancellationToken cancellationToken) {}
     protected void ThrowIfDisposed() {}
   }
 
   public sealed class MacAddressResolverOptions {
     public static readonly MacAddressResolverOptions Default; // = "Smdn.Net.AddressResolution.MacAddressResolverOptions"
 
     public MacAddressResolverOptions() {}
 
-    public string? NmapTargetSpecification { get; init; }
+    public string? ArpScanCommandInterfaceSpecification { get; init; }
+    public string? ArpScanCommandTargetSpecification { get; init; }
+    public string? NmapCommandInterfaceSpecification { get; init; }
+    public string? NmapCommandTargetSpecification { get; init; }
     public TimeSpan ProcfsArpFullScanInterval { get; init; }
   }
 }
 // API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.2.1.0.
 // Smdn.Reflection.ReverseGenerating.ListApi.Core v1.2.0.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)
diff --git a/doc/api-list/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution-net7.0.apilist.cs b/doc/api-list/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution-net7.0.apilist.cs
new file mode 100644
index 0000000..d9d912b
--- /dev/null
+++ b/doc/api-list/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution-net7.0.apilist.cs
@@ -0,0 +1,91 @@
+// Smdn.Net.AddressResolution.dll (Smdn.Net.AddressResolution-1.0.0-preview5)
+//   Name: Smdn.Net.AddressResolution
+//   AssemblyVersion: 1.0.0.0
+//   InformationalVersion: 1.0.0-preview5+fe261fa7dd16c43347c28935b3055ec3b8ab0674
+//   TargetFramework: .NETCoreApp,Version=v7.0
+//   Configuration: Release
+//   Referenced assemblies:
+//     Microsoft.Extensions.DependencyInjection.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
+//     Microsoft.Extensions.Logging.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
+//     System.Collections.Concurrent, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.ComponentModel, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.Diagnostics.Process, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.Linq, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.Memory, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
+//     System.Net.NetworkInformation, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.Net.Primitives, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.Runtime, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.Threading, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+#nullable enable annotations
+
+using System;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Smdn.Net.AddressResolution;
+
+namespace Smdn.Net {
+  public static class PhysicalAddressExtensions {
+    public static string ToMacAddressString(this PhysicalAddress hardwareAddress, char delimiter = ':') {}
+  }
+}
+
+namespace Smdn.Net.AddressResolution {
+  public interface IAddressResolver<TAddress, TResolvedAddress> {
+    void Invalidate(TAddress address);
+    ValueTask<TResolvedAddress> ResolveAsync(TAddress address, CancellationToken cancellationToken);
+  }
+
+  public abstract class MacAddressResolver :
+    IAddressResolver<IPAddress, PhysicalAddress>,
+    IAddressResolver<PhysicalAddress, IPAddress>,
+    IDisposable
+  {
+    protected static readonly PhysicalAddress AllZeroMacAddress; // = "000000000000"
+
+    public static MacAddressResolver Null { get; }
+
+    public static MacAddressResolver Create(MacAddressResolverOptions? options = null, IServiceProvider? serviceProvider = null) {}
+
+    protected MacAddressResolver(ILogger? logger = null) {}
+
+    public abstract bool HasInvalidated { get; }
+    protected ILogger? Logger { get; }
+
+    protected virtual void Dispose(bool disposing) {}
+    public void Dispose() {}
+    public void Invalidate(IPAddress ipAddress) {}
+    public void Invalidate(PhysicalAddress macAddress) {}
+    protected abstract void InvalidateCore(IPAddress ipAddress);
+    protected abstract void InvalidateCore(PhysicalAddress macAddress);
+    public ValueTask RefreshCacheAsync(CancellationToken cancellationToken = default) {}
+    protected virtual ValueTask RefreshCacheAsyncCore(CancellationToken cancellationToken) {}
+    public ValueTask RefreshInvalidatedCacheAsync(CancellationToken cancellationToken = default) {}
+    protected virtual ValueTask RefreshInvalidatedCacheAsyncCore(CancellationToken cancellationToken) {}
+    public ValueTask<PhysicalAddress?> ResolveIPAddressToMacAddressAsync(IPAddress ipAddress, CancellationToken cancellationToken = default) {}
+    protected abstract ValueTask<PhysicalAddress?> ResolveIPAddressToMacAddressAsyncCore(IPAddress ipAddress, CancellationToken cancellationToken);
+    public ValueTask<IPAddress?> ResolveMacAddressToIPAddressAsync(PhysicalAddress macAddress, CancellationToken cancellationToken = default) {}
+    protected abstract ValueTask<IPAddress?> ResolveMacAddressToIPAddressAsyncCore(PhysicalAddress macAddress, CancellationToken cancellationToken);
+    void IAddressResolver<IPAddress, PhysicalAddress>.Invalidate(IPAddress address) {}
+    ValueTask<PhysicalAddress?> IAddressResolver<IPAddress, PhysicalAddress>.ResolveAsync(IPAddress address, CancellationToken cancellationToken) {}
+    void IAddressResolver<PhysicalAddress, IPAddress>.Invalidate(PhysicalAddress address) {}
+    ValueTask<IPAddress?> IAddressResolver<PhysicalAddress, IPAddress>.ResolveAsync(PhysicalAddress address, CancellationToken cancellationToken) {}
+    protected void ThrowIfDisposed() {}
+  }
+
+  public sealed class MacAddressResolverOptions {
+    public static readonly MacAddressResolverOptions Default; // = "Smdn.Net.AddressResolution.MacAddressResolverOptions"
+
+    public MacAddressResolverOptions() {}
+
+    public string? ArpScanCommandInterfaceSpecification { get; init; }
+    public string? ArpScanCommandTargetSpecification { get; init; }
+    public string? NmapCommandInterfaceSpecification { get; init; }
+    public string? NmapCommandTargetSpecification { get; init; }
+    public TimeSpan ProcfsArpFullScanInterval { get; init; }
+  }
+}
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.2.1.0.
+// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.2.0.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)
diff --git a/doc/api-list/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution-netstandard2.0.apilist.cs b/doc/api-list/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution-netstandard2.0.apilist.cs
index f9d6a20..f2a6c75 100644
--- a/doc/api-list/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution-netstandard2.0.apilist.cs
+++ b/doc/api-list/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution-netstandard2.0.apilist.cs
@@ -1,82 +1,85 @@
-// Smdn.Net.AddressResolution.dll (Smdn.Net.AddressResolution-1.0.0-preview4)
+// Smdn.Net.AddressResolution.dll (Smdn.Net.AddressResolution-1.0.0-preview5)
 //   Name: Smdn.Net.AddressResolution
 //   AssemblyVersion: 1.0.0.0
-//   InformationalVersion: 1.0.0-preview4+f17248a683dc95a5f8a1d3f3ec79fb49b8b2852f
+//   InformationalVersion: 1.0.0-preview5+fe261fa7dd16c43347c28935b3055ec3b8ab0674
 //   TargetFramework: .NETStandard,Version=v2.0
 //   Configuration: Release
 //   Referenced assemblies:
 //     Microsoft.Bcl.AsyncInterfaces, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
 //     Microsoft.Extensions.DependencyInjection.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
 //     Microsoft.Extensions.Logging.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
 //     System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
 //     netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
 #nullable enable annotations
 
 using System;
 using System.Net;
 using System.Net.NetworkInformation;
 using System.Threading;
 using System.Threading.Tasks;
 using Microsoft.Extensions.Logging;
 using Smdn.Net.AddressResolution;
 
 namespace Smdn.Net {
   public static class PhysicalAddressExtensions {
     public static string ToMacAddressString(this PhysicalAddress hardwareAddress, char delimiter = ':') {}
   }
 }
 
 namespace Smdn.Net.AddressResolution {
   public interface IAddressResolver<TAddress, TResolvedAddress> {
-    void Invalidate(TResolvedAddress resolvedAddress);
+    void Invalidate(TAddress address);
     ValueTask<TResolvedAddress> ResolveAsync(TAddress address, CancellationToken cancellationToken);
   }
 
   public abstract class MacAddressResolver :
     IAddressResolver<IPAddress, PhysicalAddress>,
     IAddressResolver<PhysicalAddress, IPAddress>,
     IDisposable
   {
     protected static readonly PhysicalAddress AllZeroMacAddress; // = "000000000000"
 
     public static MacAddressResolver Null { get; }
 
     public static MacAddressResolver Create(MacAddressResolverOptions? options = null, IServiceProvider? serviceProvider = null) {}
 
     protected MacAddressResolver(ILogger? logger = null) {}
 
     public abstract bool HasInvalidated { get; }
     protected ILogger? Logger { get; }
 
     protected virtual void Dispose(bool disposing) {}
     public void Dispose() {}
-    public void Invalidate(IPAddress resolvedIPAddress) {}
-    public void Invalidate(PhysicalAddress resolvedMacAddress) {}
-    protected abstract void InvalidateCore(IPAddress resolvedIPAddress);
-    protected abstract void InvalidateCore(PhysicalAddress resolvedMacAddress);
+    public void Invalidate(IPAddress ipAddress) {}
+    public void Invalidate(PhysicalAddress macAddress) {}
+    protected abstract void InvalidateCore(IPAddress ipAddress);
+    protected abstract void InvalidateCore(PhysicalAddress macAddress);
     public ValueTask RefreshCacheAsync(CancellationToken cancellationToken = default) {}
     protected virtual ValueTask RefreshCacheAsyncCore(CancellationToken cancellationToken) {}
     public ValueTask RefreshInvalidatedCacheAsync(CancellationToken cancellationToken = default) {}
     protected virtual ValueTask RefreshInvalidatedCacheAsyncCore(CancellationToken cancellationToken) {}
     public ValueTask<PhysicalAddress?> ResolveIPAddressToMacAddressAsync(IPAddress ipAddress, CancellationToken cancellationToken = default) {}
     protected abstract ValueTask<PhysicalAddress?> ResolveIPAddressToMacAddressAsyncCore(IPAddress ipAddress, CancellationToken cancellationToken);
     public ValueTask<IPAddress?> ResolveMacAddressToIPAddressAsync(PhysicalAddress macAddress, CancellationToken cancellationToken = default) {}
     protected abstract ValueTask<IPAddress?> ResolveMacAddressToIPAddressAsyncCore(PhysicalAddress macAddress, CancellationToken cancellationToken);
-    void IAddressResolver<IPAddress, PhysicalAddress>.Invalidate(PhysicalAddress resolvedAddress) {}
+    void IAddressResolver<IPAddress, PhysicalAddress>.Invalidate(IPAddress address) {}
     ValueTask<PhysicalAddress?> IAddressResolver<IPAddress, PhysicalAddress>.ResolveAsync(IPAddress address, CancellationToken cancellationToken) {}
-    void IAddressResolver<PhysicalAddress, IPAddress>.Invalidate(IPAddress resolvedAddress) {}
+    void IAddressResolver<PhysicalAddress, IPAddress>.Invalidate(PhysicalAddress address) {}
     ValueTask<IPAddress?> IAddressResolver<PhysicalAddress, IPAddress>.ResolveAsync(PhysicalAddress address, CancellationToken cancellationToken) {}
     protected void ThrowIfDisposed() {}
   }
 
   public sealed class MacAddressResolverOptions {
     public static readonly MacAddressResolverOptions Default; // = "Smdn.Net.AddressResolution.MacAddressResolverOptions"
 
     public MacAddressResolverOptions() {}
 
-    public string? NmapTargetSpecification { get; init; }
+    public string? ArpScanCommandInterfaceSpecification { get; init; }
+    public string? ArpScanCommandTargetSpecification { get; init; }
+    public string? NmapCommandInterfaceSpecification { get; init; }
+    public string? NmapCommandTargetSpecification { get; init; }
     public TimeSpan ProcfsArpFullScanInterval { get; init; }
   }
 }
 // API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.2.1.0.
 // Smdn.Reflection.ReverseGenerating.ListApi.Core v1.2.0.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)
diff --git a/doc/api-list/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution-netstandard2.1.apilist.cs b/doc/api-list/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution-netstandard2.1.apilist.cs
index 565bdda..bb31c94 100644
--- a/doc/api-list/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution-netstandard2.1.apilist.cs
+++ b/doc/api-list/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution-netstandard2.1.apilist.cs
@@ -1,80 +1,83 @@
-// Smdn.Net.AddressResolution.dll (Smdn.Net.AddressResolution-1.0.0-preview4)
+// Smdn.Net.AddressResolution.dll (Smdn.Net.AddressResolution-1.0.0-preview5)
 //   Name: Smdn.Net.AddressResolution
 //   AssemblyVersion: 1.0.0.0
-//   InformationalVersion: 1.0.0-preview4+f17248a683dc95a5f8a1d3f3ec79fb49b8b2852f
+//   InformationalVersion: 1.0.0-preview5+fe261fa7dd16c43347c28935b3055ec3b8ab0674
 //   TargetFramework: .NETStandard,Version=v2.1
 //   Configuration: Release
 //   Referenced assemblies:
 //     Microsoft.Extensions.DependencyInjection.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
 //     Microsoft.Extensions.Logging.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
 //     netstandard, Version=2.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
 #nullable enable annotations
 
 using System;
 using System.Net;
 using System.Net.NetworkInformation;
 using System.Threading;
 using System.Threading.Tasks;
 using Microsoft.Extensions.Logging;
 using Smdn.Net.AddressResolution;
 
 namespace Smdn.Net {
   public static class PhysicalAddressExtensions {
     public static string ToMacAddressString(this PhysicalAddress hardwareAddress, char delimiter = ':') {}
   }
 }
 
 namespace Smdn.Net.AddressResolution {
   public interface IAddressResolver<TAddress, TResolvedAddress> {
-    void Invalidate(TResolvedAddress resolvedAddress);
+    void Invalidate(TAddress address);
     ValueTask<TResolvedAddress> ResolveAsync(TAddress address, CancellationToken cancellationToken);
   }
 
   public abstract class MacAddressResolver :
     IAddressResolver<IPAddress, PhysicalAddress>,
     IAddressResolver<PhysicalAddress, IPAddress>,
     IDisposable
   {
     protected static readonly PhysicalAddress AllZeroMacAddress; // = "000000000000"
 
     public static MacAddressResolver Null { get; }
 
     public static MacAddressResolver Create(MacAddressResolverOptions? options = null, IServiceProvider? serviceProvider = null) {}
 
     protected MacAddressResolver(ILogger? logger = null) {}
 
     public abstract bool HasInvalidated { get; }
     protected ILogger? Logger { get; }
 
     protected virtual void Dispose(bool disposing) {}
     public void Dispose() {}
-    public void Invalidate(IPAddress resolvedIPAddress) {}
-    public void Invalidate(PhysicalAddress resolvedMacAddress) {}
-    protected abstract void InvalidateCore(IPAddress resolvedIPAddress);
-    protected abstract void InvalidateCore(PhysicalAddress resolvedMacAddress);
+    public void Invalidate(IPAddress ipAddress) {}
+    public void Invalidate(PhysicalAddress macAddress) {}
+    protected abstract void InvalidateCore(IPAddress ipAddress);
+    protected abstract void InvalidateCore(PhysicalAddress macAddress);
     public ValueTask RefreshCacheAsync(CancellationToken cancellationToken = default) {}
     protected virtual ValueTask RefreshCacheAsyncCore(CancellationToken cancellationToken) {}
     public ValueTask RefreshInvalidatedCacheAsync(CancellationToken cancellationToken = default) {}
     protected virtual ValueTask RefreshInvalidatedCacheAsyncCore(CancellationToken cancellationToken) {}
     public ValueTask<PhysicalAddress?> ResolveIPAddressToMacAddressAsync(IPAddress ipAddress, CancellationToken cancellationToken = default) {}
     protected abstract ValueTask<PhysicalAddress?> ResolveIPAddressToMacAddressAsyncCore(IPAddress ipAddress, CancellationToken cancellationToken);
     public ValueTask<IPAddress?> ResolveMacAddressToIPAddressAsync(PhysicalAddress macAddress, CancellationToken cancellationToken = default) {}
     protected abstract ValueTask<IPAddress?> ResolveMacAddressToIPAddressAsyncCore(PhysicalAddress macAddress, CancellationToken cancellationToken);
-    void IAddressResolver<IPAddress, PhysicalAddress>.Invalidate(PhysicalAddress resolvedAddress) {}
+    void IAddressResolver<IPAddress, PhysicalAddress>.Invalidate(IPAddress address) {}
     ValueTask<PhysicalAddress?> IAddressResolver<IPAddress, PhysicalAddress>.ResolveAsync(IPAddress address, CancellationToken cancellationToken) {}
-    void IAddressResolver<PhysicalAddress, IPAddress>.Invalidate(IPAddress resolvedAddress) {}
+    void IAddressResolver<PhysicalAddress, IPAddress>.Invalidate(PhysicalAddress address) {}
     ValueTask<IPAddress?> IAddressResolver<PhysicalAddress, IPAddress>.ResolveAsync(PhysicalAddress address, CancellationToken cancellationToken) {}
     protected void ThrowIfDisposed() {}
   }
 
   public sealed class MacAddressResolverOptions {
     public static readonly MacAddressResolverOptions Default; // = "Smdn.Net.AddressResolution.MacAddressResolverOptions"
 
     public MacAddressResolverOptions() {}
 
-    public string? NmapTargetSpecification { get; init; }
+    public string? ArpScanCommandInterfaceSpecification { get; init; }
+    public string? ArpScanCommandTargetSpecification { get; init; }
+    public string? NmapCommandInterfaceSpecification { get; init; }
+    public string? NmapCommandTargetSpecification { get; init; }
     public TimeSpan ProcfsArpFullScanInterval { get; init; }
   }
 }
 // API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.2.1.0.
 // Smdn.Reflection.ReverseGenerating.ListApi.Core v1.2.0.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)

Full changes

Full changes in this release:
diff --git a/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution.Arp/ProcfsArpMacAddressResolver.cs b/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution.Arp/ProcfsArpMacAddressResolver.cs
index 5693ea8..7026981 100644
--- a/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution.Arp/ProcfsArpMacAddressResolver.cs
+++ b/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution.Arp/ProcfsArpMacAddressResolver.cs
@@ -3,7 +3,9 @@
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.IO;
+using System.Linq;
 using System.Net;
 using System.Net.NetworkInformation;
 using System.Threading;
@@ -15,6 +17,7 @@ namespace Smdn.Net.AddressResolution.Arp;
 
 internal partial class ProcfsArpMacAddressResolver : MacAddressResolver {
   private const string PathToProcNetArp = "/proc/net/arp";
+  private const int ArpScanParallelMax = 3;
 
   public static bool IsSupported => File.Exists(PathToProcNetArp);
 
@@ -23,10 +26,17 @@ internal partial class ProcfsArpMacAddressResolver : MacAddressResolver {
     IServiceProvider? serviceProvider
   )
   {
-    if (ProcfsArpNmapScanMacAddressResolver.IsSupported) {
-      return new ProcfsArpNmapScanMacAddressResolver(
+    if (ProcfsArpWithArpScanCommandMacAddressResolver.IsSupported) {
+      return new ProcfsArpWithArpScanCommandMacAddressResolver(
         options: options,
-        logger: serviceProvider?.GetService<ILoggerFactory>()?.CreateLogger<ProcfsArpNmapScanMacAddressResolver>()
+        logger: serviceProvider?.GetService<ILoggerFactory>()?.CreateLogger<ProcfsArpWithArpScanCommandMacAddressResolver>()
+      );
+    }
+
+    if (ProcfsArpWithNmapCommandMacAddressResolver.IsSupported) {
+      return new ProcfsArpWithNmapCommandMacAddressResolver(
+        options: options,
+        logger: serviceProvider?.GetService<ILoggerFactory>()?.CreateLogger<ProcfsArpWithNmapCommandMacAddressResolver>()
       );
     }
 
@@ -62,6 +72,9 @@ internal partial class ProcfsArpMacAddressResolver : MacAddressResolver {
 
   public override bool HasInvalidated => !(invalidatedIPAddressSet.IsEmpty && invalidatedMacAddressSet.IsEmpty);
 
+  private SemaphoreSlim arpFullScanMutex = new(initialCount: 1, maxCount: 1);
+  private SemaphoreSlim arpPartialScanMutex = new(initialCount: ArpScanParallelMax, maxCount: ArpScanParallelMax);
+
   public ProcfsArpMacAddressResolver(
     MacAddressResolverOptions options,
     ILogger? logger
@@ -71,6 +84,17 @@ internal partial class ProcfsArpMacAddressResolver : MacAddressResolver {
     arpFullScanInterval = options.ProcfsArpFullScanInterval;
   }
 
+  protected override void Dispose(bool disposing)
+  {
+    arpFullScanMutex?.Dispose();
+    arpFullScanMutex = null!;
+
+    arpPartialScanMutex?.Dispose();
+    arpPartialScanMutex = null!;
+
+    base.Dispose(disposing);
+  }
+
   protected override async ValueTask<PhysicalAddress?> ResolveIPAddressToMacAddressAsyncCore(
     IPAddress ipAddress,
     CancellationToken cancellationToken
@@ -141,11 +165,11 @@ internal partial class ProcfsArpMacAddressResolver : MacAddressResolver {
       : priorCandidate.IPAddress;
   }
 
-  protected override void InvalidateCore(IPAddress resolvedIPAddress)
-    => invalidatedIPAddressSet.Add(resolvedIPAddress);
+  protected override void InvalidateCore(IPAddress ipAddress)
+    => invalidatedIPAddressSet.Add(ipAddress);
 
-  protected override void InvalidateCore(PhysicalAddress resolvedMacAddress)
-    => invalidatedMacAddressSet.Add(resolvedMacAddress);
+  protected override void InvalidateCore(PhysicalAddress macAddress)
+    => invalidatedMacAddressSet.Add(macAddress);
 
   protected override ValueTask RefreshCacheAsyncCore(
     CancellationToken cancellationToken = default
@@ -161,8 +185,16 @@ internal partial class ProcfsArpMacAddressResolver : MacAddressResolver {
 
   private async ValueTask ArpFullScanAsync(CancellationToken cancellationToken)
   {
-    Logger?.LogDebug("Performing ARP full scan");
+    if (!await arpFullScanMutex.WaitAsync(0, cancellationToken: default).ConfigureAwait(false)) {
+      Logger?.LogInformation("ARP full scan is currently being performed.");
+      return;
+    }
+
+    Logger?.LogInformation("Performing ARP full scan.");
 
+    var sw = Logger is null ? null : Stopwatch.StartNew();
+
+    try {
       await ArpFullScanAsyncCore(cancellationToken: cancellationToken).ConfigureAwait(false);
 
       invalidatedIPAddressSet.Clear();
@@ -170,6 +202,12 @@ internal partial class ProcfsArpMacAddressResolver : MacAddressResolver {
 
       lastArpFullScanAt = DateTime.Now;
     }
+    finally {
+      Logger?.LogInformation("ARP full scan finished in {ElapsedMilliseconds} ms.", sw!.ElapsedMilliseconds);
+
+      arpFullScanMutex.Release();
+    }
+  }
 
   protected virtual ValueTask ArpFullScanAsyncCore(CancellationToken cancellationToken)
   {
@@ -192,29 +230,44 @@ internal partial class ProcfsArpMacAddressResolver : MacAddressResolver {
 
   private async ValueTask ArpScanAsync(CancellationToken cancellationToken)
   {
-    Logger?.LogDebug("Performing ARP scan for invalidated targets.");
-
     var invalidatedIPAddresses = invalidatedIPAddressSet.Keys;
     var invalidatedMacAddresses = invalidatedMacAddressSet.Keys;
 
     Logger?.LogTrace("Invalidated IP addresses: {InvalidatedIPAddresses}", string.Join(" ", invalidatedIPAddresses));
     Logger?.LogTrace("Invalidated MAC addresses: {InvalidatedMACAddresses}", string.Join(" ", invalidatedMacAddresses));
 
+    if (invalidatedMacAddresses.Any()) {
+      // perform full scan
+      await ArpFullScanAsync(
+        cancellationToken: cancellationToken
+      ).ConfigureAwait(false);
+
+      return;
+    }
+
+    await arpPartialScanMutex.WaitAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
+
+    var sw = Logger is null ? null : Stopwatch.StartNew();
+
+    try {
+      Logger?.LogInformation("Performing ARP scan for the invalidated {Count} IP addresses.", invalidatedIPAddresses.Count);
+
       await ArpScanAsyncCore(
         invalidatedIPAddresses: invalidatedIPAddresses,
-      invalidatedMacAddresses: invalidatedMacAddresses,
         cancellationToken: cancellationToken
       ).ConfigureAwait(false);
 
       invalidatedIPAddressSet.Clear();
-    invalidatedMacAddressSet.Clear();
+    }
+    finally {
+      Logger?.LogInformation("ARP scan finished in {ElapsedMilliseconds} ms.", sw!.ElapsedMilliseconds);
 
-    lastArpFullScanAt = DateTime.Now;
+      arpPartialScanMutex.Release();
+    }
   }
 
   protected virtual ValueTask ArpScanAsyncCore(
     IEnumerable<IPAddress> invalidatedIPAddresses,
-    IEnumerable<PhysicalAddress> invalidatedMacAddresses,
     CancellationToken cancellationToken
   )
   {
diff --git a/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution.Arp/ProcfsArpWithArpScanCommandMacAddressResolver.cs b/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution.Arp/ProcfsArpWithArpScanCommandMacAddressResolver.cs
new file mode 100644
index 0000000..a838136
--- /dev/null
+++ b/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution.Arp/ProcfsArpWithArpScanCommandMacAddressResolver.cs
@@ -0,0 +1,171 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+
+namespace Smdn.Net.AddressResolution.Arp;
+
+internal sealed class ProcfsArpWithArpScanCommandMacAddressResolver : ProcfsArpMacAddressResolver {
+  // ref: https://manpages.ubuntu.com/manpages/jammy/man1/arp-scan.1.html
+  //   --numeric: IP addresses only, no hostnames.
+  //   --quiet: Only display minimal output. No protocol decoding.
+  private const string ArpScanCommandBaseOptions = "--numeric --quiet ";
+
+  public static new bool IsSupported =>
+    ProcfsArpMacAddressResolver.IsSupported &&
+    lazyPathToArpScanCommand.Value is not null &&
+#pragma warning disable IDE0047, SA1119
+    (
+#if NET8_0_OR_GREATER
+      Environment.IsPrivilegedProcess ||
+#endif
+#if NET7_0_OR_GREATER
+      HasSgidOrSuid(File.GetUnixFileMode(lazyPathToArpScanCommand.Value))
+#else
+      false // TODO: use Mono.Posix
+#endif
+    );
+#pragma warning restore IDE0047, SA1119
+
+#if NET7_0_OR_GREATER
+  private static bool HasSgidOrSuid(UnixFileMode fileMode)
+    => fileMode.HasFlag(UnixFileMode.SetGroup) || fileMode.HasFlag(UnixFileMode.SetUser);
+#endif
+
+  private static readonly Lazy<string?> lazyPathToArpScanCommand = new(valueFactory: GetPathToArpScanCommand, isThreadSafe: true);
+  private static readonly string[] BinDirs = new[] { "/sbin/", "/usr/sbin/", "/bin/", "/usr/bin/" };
+
+  private static string? GetPathToArpScanCommand()
+    => BinDirs
+      .Select(static dir => Path.Combine(dir, "arp-scan"))
+      .FirstOrDefault(static pathToArpScanCommand => File.Exists(pathToArpScanCommand));
+
+  /*
+   * instance members
+   */
+  private readonly string arpScanCommandCommonOptions;
+  private readonly string arpScanCommandFullScanOptions;
+
+  public ProcfsArpWithArpScanCommandMacAddressResolver(
+    MacAddressResolverOptions options,
+    ILogger? logger
+  )
+    : base(
+      options,
+      logger
+    )
+  {
+    arpScanCommandCommonOptions = ArpScanCommandBaseOptions;
+
+    if (!string.IsNullOrEmpty(options.ArpScanCommandInterfaceSpecification))
+      arpScanCommandCommonOptions += $"--interface={options.ArpScanCommandInterfaceSpecification} ";
+
+    arpScanCommandFullScanOptions = string.Concat(
+      arpScanCommandCommonOptions,
+      // ref: https://manpages.ubuntu.com/manpages/jammy/man1/arp-scan.1.html
+      //   --localnet: Generate addresses from network interface configuration.
+      options.ArpScanCommandTargetSpecification ?? "--localnet "
+    );
+  }
+
+  protected override ValueTask ArpFullScanAsyncCore(CancellationToken cancellationToken)
+    // perform full scan
+    => RunArpScanCommandAsync(
+      arpScanCommandOptions: arpScanCommandFullScanOptions,
+      logger: Logger,
+      cancellationToken: cancellationToken
+    );
+
+  protected override ValueTask ArpScanAsyncCore(
+    IEnumerable<IPAddress> invalidatedIPAddresses,
+    CancellationToken cancellationToken
+  )
+  {
+    // perform scan for specific target IPs
+    var arpScanCommandTargetSpecification = string.Join(" ", invalidatedIPAddresses);
+
+    return arpScanCommandTargetSpecification.Length == 0
+      ? default // do nothing
+      : RunArpScanCommandAsync(
+          arpScanCommandOptions: arpScanCommandCommonOptions + arpScanCommandTargetSpecification,
+          logger: Logger,
+          cancellationToken: cancellationToken
+        );
+  }
+
+  private static async ValueTask RunArpScanCommandAsync(
+    string arpScanCommandOptions,
+    ILogger? logger,
+    CancellationToken cancellationToken
+  )
+  {
+    var arpScanCommandProcessStartInfo = new ProcessStartInfo() {
+      FileName = lazyPathToArpScanCommand.Value,
+      Arguments = arpScanCommandOptions,
+      RedirectStandardOutput = true,
+      RedirectStandardError = true,
+      UseShellExecute = false,
+    };
+
+    logger?.LogDebug(
+      "[arp-scan] {ProcessStartInfoFileName} {ProcessStartInfoArguments}",
+      arpScanCommandProcessStartInfo.FileName,
+      arpScanCommandProcessStartInfo.Arguments
+    );
+
+    using var arpScanProcess = new Process() {
+      StartInfo = arpScanCommandProcessStartInfo,
+    };
+
+    try {
+      arpScanProcess.Start();
+
+#if SYSTEM_DIAGNOSTICS_PROCESS_WAITFOREXITASYNC
+      await arpScanProcess.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
+#else
+      arpScanProcess.WaitForExit(); // TODO: cacellation
+#endif
+
+      if (logger is not null) {
+        const LogLevel logLevelForStandardOutput = LogLevel.Trace;
+        const LogLevel logLevelForStandardError = LogLevel.Error;
+
+        static IEnumerable<(StreamReader, LogLevel)> EnumerateLogTarget(StreamReader stdout, StreamReader stderr)
+        {
+          yield return (stdout, logLevelForStandardOutput);
+          yield return (stderr, logLevelForStandardError);
+        }
+
+        foreach (var (stdio, logLevel) in EnumerateLogTarget(arpScanProcess.StandardOutput, arpScanProcess.StandardError)) {
+          if (!logger.IsEnabled(logLevel))
+            continue;
+
+          for (; ;) {
+            var line = await stdio.ReadLineAsync().ConfigureAwait(false);
+
+            if (line is null)
+              break;
+
+            logger.Log(logLevel, "[arp-scan] {Line}", line);
+          }
+        }
+
+        if (!logger.IsEnabled(LogLevel.Error))
+          logger.LogDebug("[arp-scan] process exited with code {ExitCode}", arpScanProcess.ExitCode);
+      }
+
+      if (arpScanProcess.ExitCode != 0)
+        logger?.LogError("[arp-scan] process exited with code {ExitCode}", arpScanProcess.ExitCode);
+    }
+    catch (Exception ex) {
+      logger?.LogError(ex, "[arp-scan] failed to perform ARP scanning");
+    }
+  }
+}
diff --git a/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution.Arp/ProcfsArpNmapScanMacAddressResolver.cs b/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution.Arp/ProcfsArpWithNmapCommandMacAddressResolver.cs
similarity index 53%
rename from src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution.Arp/ProcfsArpNmapScanMacAddressResolver.cs
rename to src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution.Arp/ProcfsArpWithNmapCommandMacAddressResolver.cs
index 983f739..508e2a5 100644
--- a/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution.Arp/ProcfsArpNmapScanMacAddressResolver.cs
+++ b/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution.Arp/ProcfsArpWithNmapCommandMacAddressResolver.cs
@@ -6,20 +6,27 @@ using System.Diagnostics;
 using System.IO;
 using System.Linq;
 using System.Net;
-using System.Net.NetworkInformation;
 using System.Threading;
 using System.Threading.Tasks;
 using Microsoft.Extensions.Logging;
 
 namespace Smdn.Net.AddressResolution.Arp;
 
-internal sealed class ProcfsArpNmapScanMacAddressResolver : ProcfsArpMacAddressResolver {
-  public static new bool IsSupported => ProcfsArpMacAddressResolver.IsSupported && lazyPathToNmap.Value is not null;
+internal sealed class ProcfsArpWithNmapCommandMacAddressResolver : ProcfsArpMacAddressResolver {
+  // ref: https://nmap.org/book/man-briefoptions.html
+  //   -sn: Ping Scan - disable port scan
+  //   -n: Never do DNS resolution
+  //   -T<0-5>: Set timing template (higher is faster)
+  //     4 = aggressive
+  //   -oG <file>: Output scan in Grepable format
+  private const string NmapCommandBaseOptions = "-sn -n -T4 -oG - ";
+
+  public static new bool IsSupported => ProcfsArpMacAddressResolver.IsSupported && lazyPathToNmapCommand.Value is not null;
 
-  private static readonly Lazy<string?> lazyPathToNmap = new(valueFactory: GetPathToNmap, isThreadSafe: true);
+  private static readonly Lazy<string?> lazyPathToNmapCommand = new(valueFactory: GetPathToNmapCommand, isThreadSafe: true);
   private static readonly string[] BinDirs = new[] { "/bin/", "/sbin/", "/usr/bin/" };
 
-  private static string? GetPathToNmap()
+  private static string? GetPathToNmapCommand()
     => BinDirs
       .Select(static dir => Path.Combine(dir, "nmap"))
       .FirstOrDefault(static nmap => File.Exists(nmap));
@@ -27,9 +34,10 @@ internal sealed class ProcfsArpNmapScanMacAddressResolver : ProcfsArpMacAddressR
   /*
    * instance members
    */
-  private readonly string nmapTargetSpecification;
+  private readonly string nmapCommandCommonOptions;
+  private readonly string nmapCommandFullScanOptions;
 
-  public ProcfsArpNmapScanMacAddressResolver(
+  public ProcfsArpWithNmapCommandMacAddressResolver(
     MacAddressResolverOptions options,
     ILogger? logger
   )
@@ -38,60 +46,53 @@ internal sealed class ProcfsArpNmapScanMacAddressResolver : ProcfsArpMacAddressR
       logger
     )
   {
-    nmapTargetSpecification = options.NmapTargetSpecification
-      ?? throw new ArgumentException($"{nameof(options.NmapTargetSpecification)} must be specified with {nameof(MacAddressResolverOptions)}");
+    if (string.IsNullOrEmpty(options.NmapCommandInterfaceSpecification))
+      nmapCommandCommonOptions = NmapCommandBaseOptions;
+    else
+      // -e <iface>: Use specified interface
+      nmapCommandCommonOptions = NmapCommandBaseOptions + $"-e {options.NmapCommandInterfaceSpecification} ";
+
+    nmapCommandFullScanOptions = string.Concat(
+      nmapCommandCommonOptions,
+      options.NmapCommandTargetSpecification
+        ?? throw new ArgumentException($"{nameof(options.NmapCommandTargetSpecification)} must be specified with {nameof(MacAddressResolverOptions)}")
+    );
   }
 
   protected override ValueTask ArpFullScanAsyncCore(CancellationToken cancellationToken)
-    => NmapScanAsync(
-      nmapOptionTargetSpecification: nmapTargetSpecification,
+    // perform full scan
+    => RunNmapCommandAsync(
+      nmapCommandOptions: nmapCommandFullScanOptions,
       logger: Logger,
       cancellationToken: cancellationToken
     );
 
   protected override ValueTask ArpScanAsyncCore(
     IEnumerable<IPAddress> invalidatedIPAddresses,
-    IEnumerable<PhysicalAddress> invalidatedMacAddresses,
     CancellationToken cancellationToken
   )
   {
-    if (invalidatedMacAddresses.Any()) {
-      // perform full scan
-      return NmapScanAsync(
-        nmapOptionTargetSpecification: nmapTargetSpecification,
-        logger: Logger,
-        cancellationToken: cancellationToken
-      );
-    }
-
     // perform scan for specific target IPs
-    var nmapOptionTargetSpecification = string.Join(" ", invalidatedIPAddresses);
+    var nmapCommandOptionTargetSpecification = string.Join(" ", invalidatedIPAddresses);
 
-    return nmapOptionTargetSpecification.Length == 0
+    return nmapCommandOptionTargetSpecification.Length == 0
       ? default // do nothing
-      : NmapScanAsync(
-          nmapOptionTargetSpecification: nmapOptionTargetSpecification,
+      : RunNmapCommandAsync(
+          nmapCommandOptions: nmapCommandCommonOptions + nmapCommandOptionTargetSpecification,
           logger: Logger,
           cancellationToken: cancellationToken
         );
   }
 
-  private static async ValueTask NmapScanAsync(
-    string nmapOptionTargetSpecification,
+  private static async ValueTask RunNmapCommandAsync(
+    string nmapCommandOptions,
     ILogger? logger,
     CancellationToken cancellationToken
   )
   {
-    // -sn: Ping Scan - disable port scan
-    // -n: Never do DNS resolution
-    // -T<0-5>: Set timing template (higher is faster)
-    //   4 = aggressive
-    // -oG <file>: Output scan in Grepable format
-    const string nmapOptions = "-sn -n -T4 -oG - ";
-
-    var nmapProcessStartInfo = new ProcessStartInfo() {
-      FileName = lazyPathToNmap.Value,
-      Arguments = nmapOptions + nmapOptionTargetSpecification,
+    var nmapCommandProcessStartInfo = new ProcessStartInfo() {
+      FileName = lazyPathToNmapCommand.Value,
+      Arguments = nmapCommandOptions,
       RedirectStandardOutput = true,
       RedirectStandardError = true,
       UseShellExecute = false,
@@ -99,12 +100,12 @@ internal sealed class ProcfsArpNmapScanMacAddressResolver : ProcfsArpMacAddressR
 
     logger?.LogDebug(
       "[nmap] {ProcessStartInfoFileName} {ProcessStartInfoArguments}",
-      nmapProcessStartInfo.FileName,
-      nmapProcessStartInfo.Arguments
+      nmapCommandProcessStartInfo.FileName,
+      nmapCommandProcessStartInfo.Arguments
     );
 
     using var nmapProcess = new Process() {
-      StartInfo = nmapProcessStartInfo,
+      StartInfo = nmapCommandProcessStartInfo,
     };
 
     try {
@@ -139,7 +140,13 @@ internal sealed class ProcfsArpNmapScanMacAddressResolver : ProcfsArpMacAddressR
             logger.Log(logLevel, "[nmap] {Line}", line);
           }
         }
+
+        if (!logger.IsEnabled(LogLevel.Error))
+          logger.LogDebug("[nmap] process exited with code {ExitCode}", nmapProcess.ExitCode);
       }
+
+      if (nmapProcess.ExitCode != 0)
+        logger?.LogError("[nmap] process exited with code {ExitCode}", nmapProcess.ExitCode);
     }
     catch (Exception ex) {
       logger?.LogError(ex, "[nmap] failed to perform ARP scanning");
diff --git a/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution.csproj b/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution.csproj
index c23de68..7597ef5 100644
--- a/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution.csproj
+++ b/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution.csproj
@@ -5,8 +5,10 @@ SPDX-License-Identifier: MIT
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFrameworks>netstandard2.0;netstandard2.1;net6.0</TargetFrameworks>
+    <TargetFrameworks Condition="$([MSBuild]::VersionGreaterThanOrEquals('$(NETCoreSdkVersion)', '7.0.0'))">net7.0;$(TargetFrameworks)</TargetFrameworks>
+    <TargetFrameworks Condition="$([MSBuild]::VersionGreaterThanOrEquals('$(NETCoreSdkVersion)', '8.0.0'))">net8.0;$(TargetFrameworks)</TargetFrameworks>
     <VersionPrefix>1.0.0</VersionPrefix>
-    <VersionSuffix>preview4</VersionSuffix>
+    <VersionSuffix>preview5</VersionSuffix>
     <!-- <PackageValidationBaselineVersion>1.0.0</PackageValidationBaselineVersion> -->
     <Nullable>enable</Nullable>
     <NoWarn>CA1848</NoWarn> <!-- use the LoggerMessage delegates instead -->
@@ -25,6 +27,10 @@ This library provides interfaces for resolving between IP addresses and MAC addr
     <PackageTags>ARP;ip-address;mac-address;hardware-address;address-lookup;address-resolution</PackageTags>
   </PropertyGroup>
 
+  <PropertyGroup Label="StyleCop code analysis">
+    <StyleCopAnalyzersConfigurationFile>stylecop.json</StyleCopAnalyzersConfigurationFile>
+  </PropertyGroup>
+
   <ItemGroup>
     <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
   </ItemGroup>
diff --git a/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution/IAddressResolver.cs b/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution/IAddressResolver.cs
index b172e3d..8504fbc 100644
--- a/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution/IAddressResolver.cs
+++ b/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution/IAddressResolver.cs
@@ -8,5 +8,5 @@ namespace Smdn.Net.AddressResolution;
 public interface IAddressResolver<TAddress, TResolvedAddress> {
   /// <returns>An resolved address. <see langword="null"/> if address could not be resolved.</returns>
   public ValueTask<TResolvedAddress?> ResolveAsync(TAddress address, CancellationToken cancellationToken);
-  public void Invalidate(TResolvedAddress resolvedAddress);
+  public void Invalidate(TAddress address);
 }
diff --git a/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution/MacAddressResolver.cs b/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution/MacAddressResolver.cs
index b9b01c5..7793db5 100644
--- a/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution/MacAddressResolver.cs
+++ b/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution/MacAddressResolver.cs
@@ -83,9 +83,9 @@ public abstract class MacAddressResolver :
     );
 
   void IAddressResolver<IPAddress, PhysicalAddress>.Invalidate(
-    PhysicalAddress resolvedAddress
+    IPAddress address
   )
-    => Invalidate(resolvedMacAddress: resolvedAddress);
+    => Invalidate(ipAddress: address);
 
 #pragma warning disable SA1305
   public ValueTask<PhysicalAddress?> ResolveIPAddressToMacAddressAsync(
@@ -131,19 +131,19 @@ public abstract class MacAddressResolver :
   );
 #pragma warning restore SA1305
 
-  public void Invalidate(PhysicalAddress resolvedMacAddress)
+  public void Invalidate(IPAddress ipAddress)
   {
-    if (resolvedMacAddress is null)
-      throw new ArgumentNullException(nameof(resolvedMacAddress));
+    if (ipAddress is null)
+      throw new ArgumentNullException(nameof(ipAddress));
 
     ThrowIfDisposed();
 
-    Logger?.LogDebug("Invalidating {MacAddress}", resolvedMacAddress.ToMacAddressString());
+    Logger?.LogDebug("Invalidating {IPAddress}", ipAddress);
 
-    InvalidateCore(resolvedMacAddress);
+    InvalidateCore(ipAddress);
   }
 
-  protected abstract void InvalidateCore(PhysicalAddress resolvedMacAddress);
+  protected abstract void InvalidateCore(IPAddress ipAddress);
 
   /*
    * PhysicalAddress -> IPAddress
@@ -158,9 +158,9 @@ public abstract class MacAddressResolver :
     );
 
   void IAddressResolver<PhysicalAddress, IPAddress>.Invalidate(
-    IPAddress resolvedAddress
+    PhysicalAddress address
   )
-    => Invalidate(resolvedIPAddress: resolvedAddress);
+    => Invalidate(macAddress: address);
 
   public ValueTask<IPAddress?> ResolveMacAddressToIPAddressAsync(
     PhysicalAddress macAddress,
@@ -209,19 +209,19 @@ public abstract class MacAddressResolver :
     CancellationToken cancellationToken
   );
 
-  public void Invalidate(IPAddress resolvedIPAddress)
+  public void Invalidate(PhysicalAddress macAddress)
   {
-    if (resolvedIPAddress is null)
-      throw new ArgumentNullException(nameof(resolvedIPAddress));
+    if (macAddress is null)
+      throw new ArgumentNullException(nameof(macAddress));
 
     ThrowIfDisposed();
 
-    Logger?.LogDebug("Invalidating {IPAddress}", resolvedIPAddress);
+    Logger?.LogDebug("Invalidating {IPAddress}", macAddress.ToMacAddressString());
 
-    InvalidateCore(resolvedIPAddress);
+    InvalidateCore(macAddress);
   }
 
-  protected abstract void InvalidateCore(IPAddress resolvedIPAddress);
+  protected abstract void InvalidateCore(PhysicalAddress macAddress);
 
   /*
    * other virtual/abstract members
diff --git a/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution/MacAddressResolverOptions.cs b/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution/MacAddressResolverOptions.cs
index 9525810..d2cd901 100644
--- a/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution/MacAddressResolverOptions.cs
+++ b/src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution/MacAddressResolverOptions.cs
@@ -7,10 +7,25 @@ namespace Smdn.Net.AddressResolution;
 public sealed class MacAddressResolverOptions {
   public static readonly MacAddressResolverOptions Default = new() { };
 
+  /// <summary>
+  /// Gets the string value passed to the argument '-e &lt;iface&gt;' of nmap command.
+  /// </summary>
+  public string? NmapCommandInterfaceSpecification { get; init; }
+
   /// <summary>
   /// Gets the string value passed to the argument &lt;target specification&gt; of nmap command.
   /// </summary>
-  public string? NmapTargetSpecification { get; init; }
+  public string? NmapCommandTargetSpecification { get; init; }
+
+  /// <summary>
+  /// Gets the string value passed to the argument '--interface=&lt;s&gg;' of arp-scan command.
+  /// </summary>
+  public string? ArpScanCommandInterfaceSpecification { get; init; }
+
+  /// <summary>
+  /// Gets the string value represents the 'target hosts' pass to the arp-scan command. This value can be IP addresses or hostnames.
+  /// </summary>
+  public string? ArpScanCommandTargetSpecification { get; init; }
 
   public TimeSpan ProcfsArpFullScanInterval { get; init; } = TimeSpan.FromMinutes(15.0);
 }
diff --git a/src/Smdn.Net.AddressResolution/stylecop.json b/src/Smdn.Net.AddressResolution/stylecop.json
new file mode 100644
index 0000000..39c1bb6
--- /dev/null
+++ b/src/Smdn.Net.AddressResolution/stylecop.json
@@ -0,0 +1,18 @@
+{
+  "settings": {
+    "indentation": {
+      "indentationSize": 2,
+      "tabSize": 2,
+      "useTabs": false
+    },
+    "maintainabilityRules": {
+      "topLevelTypes": ["class", "struct", "interface", "delegate", "enum"]
+    },
+    "namingRules": {
+      "allowedHungarianPrefixes": ["ex", "ip"]
+    },
+    "documentationRules": {
+      "xmlHeader": false
+    }
+  }
+}

Notes

Full Changelog: releases/Smdn.Net.AddressResolution-1.0.0-preview4...releases/Smdn.Net.AddressResolution-1.0.0-preview5