@@ -133,7 +133,7 @@ private sealed class Rule
133
133
private readonly NetworkSet include ;
134
134
private readonly string pattern ;
135
135
private readonly int specificity ;
136
- private readonly NetworkSet split ;
136
+ private readonly SplitNetwork [ ] split ;
137
137
private readonly bool wildcard ;
138
138
139
139
public Rule ( JsonElement json ) : this (
@@ -147,13 +147,11 @@ public Rule(string pattern, JsonElement jsonRule)
147
147
{
148
148
this . pattern = Normalize ( pattern ) ;
149
149
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 ;
153
151
154
152
this . include = new ( GetNetworks ( jsonRule , true , "includeNetworks" , "include" ) ) ;
155
153
this . exclude = new ( GetNetworks ( jsonRule , false , "excludeNetworks" , "exclude" ) ) ;
156
- this . split = new ( GetNetworks ( jsonRule , false , "splitNetworks" ) ) ;
154
+ this . split = GetSplitNetworks ( jsonRule ) ;
157
155
}
158
156
159
157
private static List < NetworkAddress > GetNetworks ( JsonElement json , bool addDefault , params string [ ] names )
@@ -211,9 +209,73 @@ public bool IsClientAllowed(IPAddress clientIp)
211
209
return true ;
212
210
}
213
211
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
+
214
276
public bool PassesSplit ( IPAddress clientIp , DnsResourceRecord record )
215
277
{
216
- if ( this . split . IsEmpty )
278
+ if ( this . split . Length == 0 )
217
279
return true ;
218
280
219
281
var recordIp = record switch
@@ -226,10 +288,58 @@ public bool PassesSplit(IPAddress clientIp, DnsResourceRecord record)
226
288
if ( recordIp is null )
227
289
return true ;
228
290
229
- var clientInside = this . split . Contains ( clientIp ) ;
230
- var recordInside = this . split . Contains ( recordIp ) ;
291
+ foreach ( var sn in this . split )
292
+ {
293
+ var clientInside = sn . Network . Contains ( clientIp ) ;
294
+ var recordInside = sn . Network . Contains ( recordIp ) ;
295
+
296
+ if ( clientInside != recordInside )
297
+ return false ;
298
+
299
+ if ( clientInside && sn . SamePrefix . HasValue && ! IpPrefixEqual ( clientIp , recordIp , sn . SamePrefix . Value ) )
300
+ return false ;
301
+ }
302
+
303
+ return true ;
304
+ }
305
+
306
+ private static bool IpPrefixEqual ( IPAddress a , IPAddress b , int prefixBits )
307
+ {
308
+ var aBytes = a . GetAddressBytes ( ) ;
309
+ var bBytes = b . GetAddressBytes ( ) ;
310
+
311
+ if ( aBytes . Length != bBytes . Length || prefixBits < 0 )
312
+ return false ;
313
+
314
+ var maxBits = aBytes . Length * 8 ;
315
+ if ( prefixBits > maxBits )
316
+ prefixBits = maxBits ;
317
+
318
+ var bits = prefixBits ;
319
+
320
+ for ( var i = 0 ; i < aBytes . Length && bits > 0 ; i ++ )
321
+ {
322
+ var take = bits >= 8 ? 8 : bits ;
323
+ var mask = ( byte ) ( 0xFF << ( 8 - take ) ) ;
324
+
325
+ if ( ( aBytes [ i ] & mask ) != ( bBytes [ i ] & mask ) )
326
+ return false ;
327
+ bits -= take ;
328
+ }
329
+
330
+ return true ;
331
+ }
332
+
333
+ private readonly struct SplitNetwork
334
+ {
335
+ public SplitNetwork ( NetworkAddress network , int ? samePrefix )
336
+ {
337
+ this . Network = network ;
338
+ this . SamePrefix = samePrefix ;
339
+ }
231
340
232
- return clientInside == recordInside ;
341
+ public NetworkAddress Network { get ; }
342
+ public int ? SamePrefix { get ; }
233
343
}
234
344
}
235
345
0 commit comments