Skip to content

Commit a089c4e

Browse files
committed
add APIs to mark unresolvable addresses as invalid and update them manually
1 parent 1824036 commit a089c4e

File tree

8 files changed

+350
-24
lines changed

8 files changed

+350
-24
lines changed

src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution.Arp/ProcfsArpMacAddressResolver.cs

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// SPDX-FileCopyrightText: 2022 smdn <smdn@smdn.jp>
22
// SPDX-License-Identifier: MIT
33
using System;
4+
using System.Collections.Concurrent;
5+
using System.Collections.Generic;
46
using System.IO;
57
using System.Net;
68
using System.Net.NetworkInformation;
@@ -34,30 +36,48 @@ internal partial class ProcfsArpMacAddressResolver : MacAddressResolver {
3436
);
3537
}
3638

39+
private readonly struct None { }
40+
41+
private class ConcurrentSet<T> : ConcurrentDictionary<T, None>
42+
where T : notnull
43+
{
44+
public ConcurrentSet()
45+
{
46+
}
47+
48+
public void Add(T key)
49+
=> AddOrUpdate(key: key, addValue: default, updateValueFactory: static (key, old) => default);
50+
}
51+
3752
/*
3853
* instance members
3954
*/
40-
private DateTime lastArpScanAt = DateTime.MinValue;
41-
private readonly TimeSpan arpScanInterval;
55+
private DateTime lastArpFullScanAt = DateTime.MinValue;
56+
private readonly TimeSpan arpFullScanInterval;
57+
58+
private bool HasArpFullScanIntervalElapsed => lastArpFullScanAt + arpFullScanInterval <= DateTime.Now;
59+
60+
private readonly ConcurrentSet<IPAddress> invalidatedIPAddressSet = new();
61+
private readonly ConcurrentSet<PhysicalAddress> invalidatedMacAddressSet = new();
4262

43-
private bool HasArpScanIntervalElapsed => lastArpScanAt + arpScanInterval <= DateTime.Now;
63+
public override bool HasInvalidated => !(invalidatedIPAddressSet.IsEmpty && invalidatedMacAddressSet.IsEmpty);
4464

4565
public ProcfsArpMacAddressResolver(
4666
MacAddressResolverOptions options,
4767
ILogger? logger
4868
)
4969
: base(logger)
5070
{
51-
arpScanInterval = options.ProcfsArpScanInterval;
71+
arpFullScanInterval = options.ProcfsArpFullScanInterval;
5272
}
5373

5474
protected override async ValueTask<PhysicalAddress?> ResolveIPAddressToMacAddressAsyncCore(
5575
IPAddress ipAddress,
5676
CancellationToken cancellationToken
5777
)
5878
{
59-
if (HasArpScanIntervalElapsed)
60-
await ArpScanAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
79+
if (HasArpFullScanIntervalElapsed)
80+
await ArpFullScanAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
6181

6282
ArpTableEntry priorCandidate = default;
6383
ArpTableEntry candidate = default;
@@ -67,6 +87,9 @@ CancellationToken cancellationToken
6787
Logger,
6888
cancellationToken
6989
).ConfigureAwait(false)) {
90+
if (invalidatedMacAddressSet.ContainsKey(entry.HardwareAddress!))
91+
continue; // ignore the entry that is marked as invalidated
92+
7093
if (entry.IsPermanentOrComplete) {
7194
// prefer permanent or complete entry
7295
priorCandidate = entry;
@@ -88,8 +111,8 @@ CancellationToken cancellationToken
88111
CancellationToken cancellationToken
89112
)
90113
{
91-
if (HasArpScanIntervalElapsed)
92-
await ArpScanAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
114+
if (HasArpFullScanIntervalElapsed)
115+
await ArpFullScanAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
93116

94117
ArpTableEntry priorCandidate = default;
95118
ArpTableEntry candidate = default;
@@ -99,6 +122,9 @@ CancellationToken cancellationToken
99122
Logger,
100123
cancellationToken
101124
).ConfigureAwait(false)) {
125+
if (invalidatedIPAddressSet.ContainsKey(entry.IPAddress!))
126+
continue; // ignore the entry that is marked as invalidated
127+
102128
if (entry.IsPermanentOrComplete) {
103129
// prefer permanent or complete entry
104130
priorCandidate = entry;
@@ -115,6 +141,12 @@ CancellationToken cancellationToken
115141
: priorCandidate.IPAddress;
116142
}
117143

144+
protected override void InvalidateCore(IPAddress resolvedIPAddress)
145+
=> invalidatedIPAddressSet.Add(resolvedIPAddress);
146+
147+
protected override void InvalidateCore(PhysicalAddress resolvedMacAddress)
148+
=> invalidatedMacAddressSet.Add(resolvedMacAddress);
149+
118150
protected override ValueTask RefreshCacheAsyncCore(
119151
CancellationToken cancellationToken = default
120152
)
@@ -124,19 +156,67 @@ protected override ValueTask RefreshCacheAsyncCore(
124156
ValueTask.FromCanceled(cancellationToken)
125157
#else
126158
ValueTaskShim.FromCanceled(cancellationToken)
159+
#endif
160+
: ArpFullScanAsync(cancellationToken: cancellationToken);
161+
162+
private async ValueTask ArpFullScanAsync(CancellationToken cancellationToken)
163+
{
164+
Logger?.LogDebug("Performing ARP full scan");
165+
166+
await ArpFullScanAsyncCore(cancellationToken: cancellationToken).ConfigureAwait(false);
167+
168+
invalidatedIPAddressSet.Clear();
169+
invalidatedMacAddressSet.Clear();
170+
171+
lastArpFullScanAt = DateTime.Now;
172+
}
173+
174+
protected virtual ValueTask ArpFullScanAsyncCore(CancellationToken cancellationToken)
175+
{
176+
Logger?.LogWarning("ARP scan is not supported in this class.");
177+
178+
return default;
179+
}
180+
181+
protected override ValueTask RefreshInvalidatedCacheAsyncCore(
182+
CancellationToken cancellationToken = default
183+
)
184+
=> cancellationToken.IsCancellationRequested
185+
?
186+
#if SYSTEM_THREADING_TASKS_VALUETASK_FROMCANCELED
187+
ValueTask.FromCanceled(cancellationToken)
188+
#else
189+
ValueTaskShim.FromCanceled(cancellationToken)
127190
#endif
128191
: ArpScanAsync(cancellationToken: cancellationToken);
129192

130193
private async ValueTask ArpScanAsync(CancellationToken cancellationToken)
131194
{
132-
Logger?.LogDebug("Performing ARP scan");
195+
Logger?.LogDebug("Performing ARP scan for invalidated targets.");
196+
197+
var invalidatedIPAddresses = invalidatedIPAddressSet.Keys;
198+
var invalidatedMacAddresses = invalidatedMacAddressSet.Keys;
133199

134-
await ArpScanAsyncCore(cancellationToken: cancellationToken).ConfigureAwait(false);
200+
Logger?.LogTrace("Invalidated IP addresses: {InvalidatedIPAddresses}", string.Join(" ", invalidatedIPAddresses));
201+
Logger?.LogTrace("Invalidated MAC addresses: {InvalidatedMACAddresses}", string.Join(" ", invalidatedMacAddresses));
135202

136-
lastArpScanAt = DateTime.Now;
203+
await ArpScanAsyncCore(
204+
invalidatedIPAddresses: invalidatedIPAddresses,
205+
invalidatedMacAddresses: invalidatedMacAddresses,
206+
cancellationToken: cancellationToken
207+
).ConfigureAwait(false);
208+
209+
invalidatedIPAddressSet.Clear();
210+
invalidatedMacAddressSet.Clear();
211+
212+
lastArpFullScanAt = DateTime.Now;
137213
}
138214

139-
protected virtual ValueTask ArpScanAsyncCore(CancellationToken cancellationToken)
215+
protected virtual ValueTask ArpScanAsyncCore(
216+
IEnumerable<IPAddress> invalidatedIPAddresses,
217+
IEnumerable<PhysicalAddress> invalidatedMacAddresses,
218+
CancellationToken cancellationToken
219+
)
140220
{
141221
Logger?.LogWarning("ARP scan is not supported in this class.");
142222

src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution.Arp/ProcfsArpNmapScanMacAddressResolver.cs

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using System.Diagnostics;
66
using System.IO;
77
using System.Linq;
8+
using System.Net;
9+
using System.Net.NetworkInformation;
810
using System.Threading;
911
using System.Threading.Tasks;
1012
using Microsoft.Extensions.Logging;
@@ -40,7 +42,45 @@ public ProcfsArpNmapScanMacAddressResolver(
4042
?? throw new ArgumentException($"{nameof(options.NmapTargetSpecification)} must be specified with {nameof(MacAddressResolverOptions)}");
4143
}
4244

43-
protected override async ValueTask ArpScanAsyncCore(CancellationToken cancellationToken)
45+
protected override ValueTask ArpFullScanAsyncCore(CancellationToken cancellationToken)
46+
=> NmapScanAsync(
47+
nmapOptionTargetSpecification: nmapTargetSpecification,
48+
logger: Logger,
49+
cancellationToken: cancellationToken
50+
);
51+
52+
protected override ValueTask ArpScanAsyncCore(
53+
IEnumerable<IPAddress> invalidatedIPAddresses,
54+
IEnumerable<PhysicalAddress> invalidatedMacAddresses,
55+
CancellationToken cancellationToken
56+
)
57+
{
58+
if (invalidatedMacAddresses.Any()) {
59+
// perform full scan
60+
return NmapScanAsync(
61+
nmapOptionTargetSpecification: nmapTargetSpecification,
62+
logger: Logger,
63+
cancellationToken: cancellationToken
64+
);
65+
}
66+
67+
// perform scan for specific target IPs
68+
var nmapOptionTargetSpecification = string.Join(" ", invalidatedIPAddresses);
69+
70+
return nmapOptionTargetSpecification.Length == 0
71+
? default // do nothing
72+
: NmapScanAsync(
73+
nmapOptionTargetSpecification: nmapOptionTargetSpecification,
74+
logger: Logger,
75+
cancellationToken: cancellationToken
76+
);
77+
}
78+
79+
private static async ValueTask NmapScanAsync(
80+
string nmapOptionTargetSpecification,
81+
ILogger? logger,
82+
CancellationToken cancellationToken
83+
)
4484
{
4585
// -sn: Ping Scan - disable port scan
4686
// -n: Never do DNS resolution
@@ -51,13 +91,13 @@ protected override async ValueTask ArpScanAsyncCore(CancellationToken cancellati
5191

5292
var nmapProcessStartInfo = new ProcessStartInfo() {
5393
FileName = lazyPathToNmap.Value,
54-
Arguments = nmapOptions + nmapTargetSpecification,
94+
Arguments = nmapOptions + nmapOptionTargetSpecification,
5595
RedirectStandardOutput = true,
5696
RedirectStandardError = true,
5797
UseShellExecute = false,
5898
};
5999

60-
Logger?.LogDebug(
100+
logger?.LogDebug(
61101
"[nmap] {ProcessStartInfoFileName} {ProcessStartInfoArguments}",
62102
nmapProcessStartInfo.FileName,
63103
nmapProcessStartInfo.Arguments
@@ -76,7 +116,7 @@ protected override async ValueTask ArpScanAsyncCore(CancellationToken cancellati
76116
nmapProcess.WaitForExit(); // TODO: cacellation
77117
#endif
78118

79-
if (Logger is not null) {
119+
if (logger is not null) {
80120
const LogLevel logLevelForStandardOutput = LogLevel.Trace;
81121
const LogLevel logLevelForStandardError = LogLevel.Error;
82122

@@ -87,7 +127,7 @@ protected override async ValueTask ArpScanAsyncCore(CancellationToken cancellati
87127
}
88128

89129
foreach (var (stdio, logLevel) in EnumerateLogTarget(nmapProcess.StandardOutput, nmapProcess.StandardError)) {
90-
if (!Logger.IsEnabled(logLevel))
130+
if (!logger.IsEnabled(logLevel))
91131
continue;
92132

93133
for (; ;) {
@@ -96,13 +136,13 @@ protected override async ValueTask ArpScanAsyncCore(CancellationToken cancellati
96136
if (line is null)
97137
break;
98138

99-
Logger.Log(logLevel, "[nmap] {Line}", line);
139+
logger.Log(logLevel, "[nmap] {Line}", line);
100140
}
101141
}
102142
}
103143
}
104144
catch (Exception ex) {
105-
Logger?.LogError(ex, "[nmap] failed to perform ARP scanning");
145+
logger?.LogError(ex, "[nmap] failed to perform ARP scanning");
106146
}
107147
}
108148
}

src/Smdn.Net.AddressResolution/Smdn.Net.AddressResolution/IAddressResolver.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ namespace Smdn.Net.AddressResolution;
88
public interface IAddressResolver<TAddress, TResolvedAddress> {
99
/// <returns>An resolved address. <see langword="null"/> if address could not be resolved.</returns>
1010
public ValueTask<TResolvedAddress?> ResolveAsync(TAddress address, CancellationToken cancellationToken);
11+
public void Invalidate(TResolvedAddress resolvedAddress);
1112
}

0 commit comments

Comments
 (0)