@@ -285,16 +285,27 @@ enum ServiceState
285285 long _cookieBadcookieSent ;
286286 long _cookieClientOnly ;
287287 long _cookieInvalidDropped ;
288+ long _cookieRateLimited ;
288289
289290 // Hard-coded initial policy for invalid DNS cookie abuse handling.
290291 // Iteration 2 can make these values configurable.
291292 const int COOKIE_FAILURE_WINDOW_SECONDS = 60 ;
292293 const int COOKIE_FAILURE_SOFT_THRESHOLD = 20 ;
294+ const int COOKIE_FAILURE_BADCOOKIE_SLIP_THRESHOLD = 40 ;
295+ const int COOKIE_FAILURE_BADCOOKIE_SLIP_FACTOR = 4 ; // send BADCOOKIE for every Nth request after slip threshold
293296 const int COOKIE_FAILURE_HARD_THRESHOLD = 100 ;
294297 const int COOKIE_FAILURE_RETENTION_SECONDS = 300 ;
298+ const int COOKIE_BOOTSTRAP_WINDOW_SECONDS = 10 ;
299+ const int COOKIE_BOOTSTRAP_HARD_THRESHOLD = 30 ;
300+ const int COOKIE_BOOTSTRAP_REFILL_RATE_PER_SECOND = 3 ; // 30 tokens per 10 seconds
301+ const int COOKIE_BOOTSTRAP_IDLE_EVICT_SECONDS = 120 ;
302+ const int COOKIE_BOOTSTRAP_BUCKET_COUNT = 16384 ; // power-of-two for fast masking
303+ const int COOKIE_BOOTSTRAP_MAX_PROBES = 8 ;
295304
296305 readonly Dictionary < IPAddress , ( long WindowStart , int Count ) > _cookieFailureByClient = new ( ) ;
297306 readonly object _cookieFailureLock = new ( ) ;
307+ readonly object _cookieBootstrapLock = new ( ) ;
308+ readonly CookieBootstrapBucket [ ] _cookieBootstrapBuckets = new CookieBootstrapBucket [ COOKIE_BOOTSTRAP_BUCKET_COUNT ] ;
298309
299310 #endregion
300311
@@ -1582,6 +1593,14 @@ private string ConvertToAbsolutePath(string path)
15821593
15831594 #region cookie
15841595
1596+ private struct CookieBootstrapBucket
1597+ {
1598+ public ulong ClientHash ;
1599+ public uint LastRefillSecond ;
1600+ public uint LastSeenSecond ;
1601+ public int Tokens ;
1602+ }
1603+
15851604 private void InitDnsCookies ( )
15861605 {
15871606 lock ( _saveLock )
@@ -1596,6 +1615,10 @@ private void InitDnsCookies()
15961615
15971616 lock ( _cookieFailureLock )
15981617 _cookieFailureByClient . Clear ( ) ;
1618+ lock ( _cookieBootstrapLock )
1619+ {
1620+ Array . Clear ( _cookieBootstrapBuckets , 0 , _cookieBootstrapBuckets . Length ) ;
1621+ }
15991622
16001623 return ;
16011624 }
@@ -1988,7 +2011,124 @@ private bool ShouldDropInvalidCookie(IPAddress clientAddress, out int failureCou
19882011 }
19892012 }
19902013
1991- return failureCount > COOKIE_FAILURE_HARD_THRESHOLD ;
2014+ if ( failureCount > COOKIE_FAILURE_HARD_THRESHOLD )
2015+ return true ;
2016+
2017+ if ( failureCount > COOKIE_FAILURE_BADCOOKIE_SLIP_THRESHOLD )
2018+ return ( failureCount % COOKIE_FAILURE_BADCOOKIE_SLIP_FACTOR ) != 0 ;
2019+
2020+ return false ;
2021+ }
2022+
2023+ private static ulong GetCookieBootstrapClientHash ( IPAddress clientAddress )
2024+ {
2025+ Span < byte > addressBytes = stackalloc byte [ 16 ] ;
2026+ if ( ! clientAddress . TryWriteBytes ( addressBytes , out int written ) )
2027+ written = 0 ;
2028+
2029+ int offset = 0 ;
2030+ if ( written == 16 )
2031+ {
2032+ bool isV4Mapped =
2033+ addressBytes [ 0 ] == 0 && addressBytes [ 1 ] == 0 && addressBytes [ 2 ] == 0 && addressBytes [ 3 ] == 0 &&
2034+ addressBytes [ 4 ] == 0 && addressBytes [ 5 ] == 0 && addressBytes [ 6 ] == 0 && addressBytes [ 7 ] == 0 &&
2035+ addressBytes [ 8 ] == 0 && addressBytes [ 9 ] == 0 &&
2036+ addressBytes [ 10 ] == 0xff && addressBytes [ 11 ] == 0xff ;
2037+
2038+ if ( isV4Mapped )
2039+ {
2040+ offset = 12 ;
2041+ written = 4 ;
2042+ }
2043+ }
2044+
2045+ // FNV-1a 64-bit.
2046+ ulong hash = 1469598103934665603UL ;
2047+ for ( int i = 0 ; i < written ; i ++ )
2048+ {
2049+ hash ^= addressBytes [ offset + i ] ;
2050+ hash *= 1099511628211UL ;
2051+ }
2052+
2053+ // Reserve zero as "empty bucket" marker.
2054+ return hash == 0 ? 1UL : hash ;
2055+ }
2056+
2057+ private bool ShouldRateLimitCookieBootstrap ( IPAddress clientAddress , out int requestCount )
2058+ {
2059+ uint now = unchecked ( ( uint ) DateTimeOffset . UtcNow . ToUnixTimeSeconds ( ) ) ;
2060+ ulong clientHash = GetCookieBootstrapClientHash ( clientAddress ) ;
2061+ int mask = COOKIE_BOOTSTRAP_BUCKET_COUNT - 1 ;
2062+ int start = ( int ) ( clientHash & ( uint ) mask ) ;
2063+
2064+ lock ( _cookieBootstrapLock )
2065+ {
2066+ int selectedIndex = - 1 ;
2067+ int staleIndex = - 1 ;
2068+
2069+ for ( int probe = 0 ; probe < COOKIE_BOOTSTRAP_MAX_PROBES ; probe ++ )
2070+ {
2071+ int index = ( start + probe ) & mask ;
2072+ ref CookieBootstrapBucket bucket = ref _cookieBootstrapBuckets [ index ] ;
2073+
2074+ if ( bucket . ClientHash == clientHash )
2075+ {
2076+ selectedIndex = index ;
2077+ break ;
2078+ }
2079+
2080+ if ( bucket . ClientHash == 0 )
2081+ {
2082+ selectedIndex = index ;
2083+ break ;
2084+ }
2085+
2086+ if ( ( now - bucket . LastSeenSecond ) >= COOKIE_BOOTSTRAP_IDLE_EVICT_SECONDS && staleIndex < 0 )
2087+ staleIndex = index ;
2088+ }
2089+
2090+ if ( selectedIndex < 0 )
2091+ selectedIndex = staleIndex ;
2092+
2093+ if ( selectedIndex < 0 )
2094+ {
2095+ requestCount = int . MaxValue ; // table pressure fail-closed
2096+ return true ;
2097+ }
2098+
2099+ ref CookieBootstrapBucket state = ref _cookieBootstrapBuckets [ selectedIndex ] ;
2100+
2101+ if ( state . ClientHash != clientHash )
2102+ {
2103+ state . ClientHash = clientHash ;
2104+ state . LastRefillSecond = now ;
2105+ state . LastSeenSecond = now ;
2106+ state . Tokens = COOKIE_BOOTSTRAP_HARD_THRESHOLD ;
2107+ }
2108+ else
2109+ {
2110+ uint elapsed = now - state . LastRefillSecond ;
2111+ if ( elapsed > 0 )
2112+ {
2113+ int refill = ( int ) Math . Min ( int . MaxValue , elapsed * ( uint ) COOKIE_BOOTSTRAP_REFILL_RATE_PER_SECOND ) ;
2114+ state . Tokens = Math . Min ( COOKIE_BOOTSTRAP_HARD_THRESHOLD , state . Tokens + refill ) ;
2115+ state . LastRefillSecond = now ;
2116+ }
2117+
2118+ state . LastSeenSecond = now ;
2119+ }
2120+
2121+ if ( state . Tokens <= 0 )
2122+ {
2123+ requestCount = COOKIE_BOOTSTRAP_HARD_THRESHOLD + 1 ;
2124+ return true ;
2125+ }
2126+
2127+ state . Tokens -- ;
2128+ }
2129+
2130+ requestCount = 0 ;
2131+ return false ;
19922132 }
19932133
19942134 private DnsDatagram HandleInvalidCookieRequest (
@@ -2011,6 +2151,11 @@ private DnsDatagram HandleInvalidCookieRequest(
20112151 {
20122152 Interlocked . Increment ( ref _cookieInvalidDropped ) ;
20132153
2154+ if ( failuresInWindow == ( COOKIE_FAILURE_BADCOOKIE_SLIP_THRESHOLD + 1 ) )
2155+ {
2156+ _log . Write ( $ "Client '{ remoteEP . Address } ' crossed DNS cookie invalid slip threshold ({ COOKIE_FAILURE_BADCOOKIE_SLIP_THRESHOLD } /{ COOKIE_FAILURE_WINDOW_SECONDS } s); BADCOOKIE responses are now throttled to 1/{ COOKIE_FAILURE_BADCOOKIE_SLIP_FACTOR } .") ;
2157+ }
2158+
20142159 if ( failuresInWindow == ( COOKIE_FAILURE_HARD_THRESHOLD + 1 ) )
20152160 {
20162161 _log . Write ( $ "Client '{ remoteEP . Address } ' exceeded DNS cookie invalid hard threshold ({ COOKIE_FAILURE_HARD_THRESHOLD } /{ COOKIE_FAILURE_WINDOW_SECONDS } s); dropping invalid-cookie requests.") ;
@@ -2956,6 +3101,18 @@ private async Task<DnsDatagram> ProcessRequestAsync(DnsDatagram request, IPEndPo
29563101 if ( requestCookie == null )
29573102 {
29583103 Interlocked . Increment ( ref _cookieMissing ) ;
3104+
3105+ if ( ShouldRateLimitCookieBootstrap ( remoteEP . Address , out int requestsInWindow ) )
3106+ {
3107+ Interlocked . Increment ( ref _cookieRateLimited ) ;
3108+
3109+ if ( requestsInWindow == ( COOKIE_BOOTSTRAP_HARD_THRESHOLD + 1 ) )
3110+ {
3111+ _log . Write ( $ "Client '{ remoteEP . Address } ' exceeded DNS cookie bootstrap threshold ({ COOKIE_BOOTSTRAP_HARD_THRESHOLD } /{ COOKIE_BOOTSTRAP_WINDOW_SECONDS } s); dropping UDP requests without cookie.") ;
3112+ }
3113+
3114+ return null ;
3115+ }
29593116 }
29603117 else
29613118 {
@@ -3000,6 +3157,18 @@ private async Task<DnsDatagram> ProcessRequestAsync(DnsDatagram request, IPEndPo
30003157 if ( scLen == 0 )
30013158 {
30023159 Interlocked . Increment ( ref _cookieClientOnly ) ;
3160+
3161+ if ( ShouldRateLimitCookieBootstrap ( remoteEP . Address , out int requestsInWindow ) )
3162+ {
3163+ Interlocked . Increment ( ref _cookieRateLimited ) ;
3164+
3165+ if ( requestsInWindow == ( COOKIE_BOOTSTRAP_HARD_THRESHOLD + 1 ) )
3166+ {
3167+ _log . Write ( $ "Client '{ remoteEP . Address } ' exceeded DNS cookie bootstrap threshold ({ COOKIE_BOOTSTRAP_HARD_THRESHOLD } /{ COOKIE_BOOTSTRAP_WINDOW_SECONDS } s); dropping client-cookie-only requests.") ;
3168+ }
3169+
3170+ return null ;
3171+ }
30033172 }
30043173 else
30053174 {
0 commit comments