@@ -133,7 +133,7 @@ private sealed class Rule
133133 private readonly NetworkSet include ;
134134 private readonly string pattern ;
135135 private readonly int specificity ;
136- private readonly NetworkSet split ;
136+ private readonly SplitNetwork [ ] split ;
137137 private readonly bool wildcard ;
138138
139139 public Rule ( JsonElement json ) : this (
@@ -147,13 +147,11 @@ public Rule(string pattern, JsonElement jsonRule)
147147 {
148148 this . pattern = Normalize ( pattern ) ;
149149 this . wildcard = this . pattern == "*" || this . pattern . StartsWith ( "*." ) ;
150- this . specificity = this . wildcard
151- ? this . pattern == "*" ? 0 : this . pattern . Length - 2
152- : this . pattern . Length ;
150+ this . specificity = this . wildcard ? this . pattern == "*" ? 0 : this . pattern . Length - 2 : this . pattern . Length ;
153151
154152 this . include = new ( GetNetworks ( jsonRule , true , "includeNetworks" , "include" ) ) ;
155153 this . exclude = new ( GetNetworks ( jsonRule , false , "excludeNetworks" , "exclude" ) ) ;
156- this . split = new ( GetNetworks ( jsonRule , false , "splitNetworks" ) ) ;
154+ this . split = GetSplitNetworks ( jsonRule ) ;
157155 }
158156
159157 private static List < NetworkAddress > GetNetworks ( JsonElement json , bool addDefault , params string [ ] names )
@@ -211,9 +209,73 @@ public bool IsClientAllowed(IPAddress clientIp)
211209 return true ;
212210 }
213211
212+ private static SplitNetwork [ ] GetSplitNetworks ( JsonElement json )
213+ {
214+ if ( ! json . TryGetProperty ( "splitNetworks" , out var value ) || value . ValueKind != JsonValueKind . Array )
215+ return [ ] ;
216+
217+ var list = new List < SplitNetwork > ( ) ;
218+
219+ foreach ( var elem in value . EnumerateArray ( ) )
220+ {
221+ if ( elem . ValueKind == JsonValueKind . String )
222+ {
223+ if ( NetworkAddress . TryParse ( elem . GetString ( ) , out var net ) )
224+ list . Add ( new SplitNetwork ( net , null ) ) ;
225+ }
226+ else if ( elem . ValueKind == JsonValueKind . Object )
227+ {
228+ if ( ! elem . TryGetProperty ( "network" , out var netProp ) || netProp . ValueKind != JsonValueKind . String )
229+ continue ;
230+ if ( ! NetworkAddress . TryParse ( netProp . GetString ( ) , out var net ) )
231+ continue ;
232+
233+ int ? samePrefix = null ;
234+ if ( elem . TryGetProperty ( "samePrefix" , out var prefProp ) && prefProp . ValueKind == JsonValueKind . Number )
235+ samePrefix = prefProp . GetInt32 ( ) ;
236+
237+ list . Add ( new SplitNetwork ( net , samePrefix ) ) ;
238+ }
239+ }
240+
241+ return list . Count == 0 ? [ ] : list . ToArray ( ) ;
242+ }
243+
244+ private static bool TryParseSplitNetwork ( string str , out SplitNetwork split )
245+ {
246+ split = default ;
247+
248+ if ( string . IsNullOrWhiteSpace ( str ) )
249+ return false ;
250+
251+ var parts = str . Split ( '/' ) ;
252+
253+ if ( parts . Length == 3 )
254+ {
255+ var networkPart = $ "{ parts [ 0 ] } /{ parts [ 1 ] } ";
256+
257+ if ( ! NetworkAddress . TryParse ( networkPart , out var net ) )
258+ return false ;
259+ if ( ! int . TryParse ( parts [ 2 ] , NumberStyles . Integer , CultureInfo . InvariantCulture , out var samePrefix ) )
260+ return false ;
261+ split = new SplitNetwork ( net , samePrefix ) ;
262+
263+ return true ;
264+ }
265+
266+ if ( NetworkAddress . TryParse ( str , out var network ) )
267+ {
268+ split = new SplitNetwork ( network , null ) ;
269+
270+ return true ;
271+ }
272+
273+ return false ;
274+ }
275+
214276 public bool PassesSplit ( IPAddress clientIp , DnsResourceRecord record )
215277 {
216- if ( this . split . IsEmpty )
278+ if ( this . split . Length == 0 )
217279 return true ;
218280
219281 var recordIp = record switch
@@ -226,10 +288,61 @@ public bool PassesSplit(IPAddress clientIp, DnsResourceRecord record)
226288 if ( recordIp is null )
227289 return true ;
228290
229- var clientInside = this . split . Contains ( clientIp ) ;
230- var recordInside = this . split . Contains ( recordIp ) ;
291+ var clientInsideAny = false ;
292+ var recordInsideAny = false ;
293+
294+ foreach ( var sn in this . split )
295+ {
296+ var clientInside = sn . Network . Contains ( clientIp ) ;
297+ var recordInside = sn . Network . Contains ( recordIp ) ;
298+
299+ if ( clientInside && recordInside && sn . SamePrefix . HasValue && ! IpPrefixEqual ( clientIp , recordIp , sn . SamePrefix . Value ) )
300+ return false ;
301+
302+ clientInsideAny |= clientInside ;
303+ recordInsideAny |= recordInside ;
304+ }
305+
306+ return clientInsideAny == recordInsideAny ;
307+ }
308+
309+ private static bool IpPrefixEqual ( IPAddress a , IPAddress b , int prefixBits )
310+ {
311+ var aBytes = a . GetAddressBytes ( ) ;
312+ var bBytes = b . GetAddressBytes ( ) ;
313+
314+ if ( aBytes . Length != bBytes . Length || prefixBits < 0 )
315+ return false ;
316+
317+ var maxBits = aBytes . Length * 8 ;
318+ if ( prefixBits > maxBits )
319+ prefixBits = maxBits ;
320+
321+ var bits = prefixBits ;
322+
323+ for ( var i = 0 ; i < aBytes . Length && bits > 0 ; i ++ )
324+ {
325+ var take = bits >= 8 ? 8 : bits ;
326+ var mask = ( byte ) ( 0xFF << ( 8 - take ) ) ;
327+
328+ if ( ( aBytes [ i ] & mask ) != ( bBytes [ i ] & mask ) )
329+ return false ;
330+ bits -= take ;
331+ }
332+
333+ return true ;
334+ }
335+
336+ private readonly struct SplitNetwork
337+ {
338+ public SplitNetwork ( NetworkAddress network , int ? samePrefix )
339+ {
340+ this . Network = network ;
341+ this . SamePrefix = samePrefix ;
342+ }
231343
232- return clientInside == recordInside ;
344+ public NetworkAddress Network { get ; }
345+ public int ? SamePrefix { get ; }
233346 }
234347 }
235348
0 commit comments