11// SPDX-FileCopyrightText: 2022 smdn <smdn@smdn.jp>
22// SPDX-License-Identifier: MIT
33using System ;
4+ using System . Collections . Concurrent ;
5+ using System . Collections . Generic ;
46using System . IO ;
57using System . Net ;
68using 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
0 commit comments