@@ -289,16 +289,27 @@ enum ServiceState
289289 long _cookieBadcookieSent ;
290290 long _cookieClientOnly ;
291291 long _cookieInvalidDropped ;
292+ long _cookieRateLimited ;
292293
293294 // Hard-coded initial policy for invalid DNS cookie abuse handling.
294295 // Iteration 2 can make these values configurable.
295296 const int COOKIE_FAILURE_WINDOW_SECONDS = 60 ;
296297 const int COOKIE_FAILURE_SOFT_THRESHOLD = 20 ;
298+ const int COOKIE_FAILURE_BADCOOKIE_SLIP_THRESHOLD = 40 ;
299+ const int COOKIE_FAILURE_BADCOOKIE_SLIP_FACTOR = 4 ; // send BADCOOKIE for every Nth request after slip threshold
297300 const int COOKIE_FAILURE_HARD_THRESHOLD = 100 ;
298301 const int COOKIE_FAILURE_RETENTION_SECONDS = 300 ;
302+ const int COOKIE_BOOTSTRAP_WINDOW_SECONDS = 10 ;
303+ const int COOKIE_BOOTSTRAP_HARD_THRESHOLD = 30 ;
304+ const int COOKIE_BOOTSTRAP_REFILL_RATE_PER_SECOND = 3 ; // 30 tokens per 10 seconds
305+ const int COOKIE_BOOTSTRAP_IDLE_EVICT_SECONDS = 120 ;
306+ const int COOKIE_BOOTSTRAP_BUCKET_COUNT = 16384 ; // power-of-two for fast masking
307+ const int COOKIE_BOOTSTRAP_MAX_PROBES = 8 ;
299308
300309 readonly Dictionary < IPAddress , ( long WindowStart , int Count ) > _cookieFailureByClient = new ( ) ;
301310 readonly object _cookieFailureLock = new ( ) ;
311+ readonly object _cookieBootstrapLock = new ( ) ;
312+ readonly CookieBootstrapBucket [ ] _cookieBootstrapBuckets = new CookieBootstrapBucket [ COOKIE_BOOTSTRAP_BUCKET_COUNT ] ;
302313
303314 #endregion
304315
@@ -1633,6 +1644,14 @@ private string ConvertToAbsolutePath(string path)
16331644
16341645 #region cookie
16351646
1647+ private struct CookieBootstrapBucket
1648+ {
1649+ public ulong ClientHash ;
1650+ public uint LastRefillSecond ;
1651+ public uint LastSeenSecond ;
1652+ public int Tokens ;
1653+ }
1654+
16361655 private void InitDnsCookies ( )
16371656 {
16381657 lock ( _saveLock )
@@ -1647,6 +1666,10 @@ private void InitDnsCookies()
16471666
16481667 lock ( _cookieFailureLock )
16491668 _cookieFailureByClient . Clear ( ) ;
1669+ lock ( _cookieBootstrapLock )
1670+ {
1671+ Array . Clear ( _cookieBootstrapBuckets , 0 , _cookieBootstrapBuckets . Length ) ;
1672+ }
16501673
16511674 return ;
16521675 }
@@ -2039,7 +2062,124 @@ private bool ShouldDropInvalidCookie(IPAddress clientAddress, out int failureCou
20392062 }
20402063 }
20412064
2042- return failureCount > COOKIE_FAILURE_HARD_THRESHOLD ;
2065+ if ( failureCount > COOKIE_FAILURE_HARD_THRESHOLD )
2066+ return true ;
2067+
2068+ if ( failureCount > COOKIE_FAILURE_BADCOOKIE_SLIP_THRESHOLD )
2069+ return ( failureCount % COOKIE_FAILURE_BADCOOKIE_SLIP_FACTOR ) != 0 ;
2070+
2071+ return false ;
2072+ }
2073+
2074+ private static ulong GetCookieBootstrapClientHash ( IPAddress clientAddress )
2075+ {
2076+ Span < byte > addressBytes = stackalloc byte [ 16 ] ;
2077+ if ( ! clientAddress . TryWriteBytes ( addressBytes , out int written ) )
2078+ written = 0 ;
2079+
2080+ int offset = 0 ;
2081+ if ( written == 16 )
2082+ {
2083+ bool isV4Mapped =
2084+ addressBytes [ 0 ] == 0 && addressBytes [ 1 ] == 0 && addressBytes [ 2 ] == 0 && addressBytes [ 3 ] == 0 &&
2085+ addressBytes [ 4 ] == 0 && addressBytes [ 5 ] == 0 && addressBytes [ 6 ] == 0 && addressBytes [ 7 ] == 0 &&
2086+ addressBytes [ 8 ] == 0 && addressBytes [ 9 ] == 0 &&
2087+ addressBytes [ 10 ] == 0xff && addressBytes [ 11 ] == 0xff ;
2088+
2089+ if ( isV4Mapped )
2090+ {
2091+ offset = 12 ;
2092+ written = 4 ;
2093+ }
2094+ }
2095+
2096+ // FNV-1a 64-bit.
2097+ ulong hash = 1469598103934665603UL ;
2098+ for ( int i = 0 ; i < written ; i ++ )
2099+ {
2100+ hash ^= addressBytes [ offset + i ] ;
2101+ hash *= 1099511628211UL ;
2102+ }
2103+
2104+ // Reserve zero as "empty bucket" marker.
2105+ return hash == 0 ? 1UL : hash ;
2106+ }
2107+
2108+ private bool ShouldRateLimitCookieBootstrap ( IPAddress clientAddress , out int requestCount )
2109+ {
2110+ uint now = unchecked ( ( uint ) DateTimeOffset . UtcNow . ToUnixTimeSeconds ( ) ) ;
2111+ ulong clientHash = GetCookieBootstrapClientHash ( clientAddress ) ;
2112+ int mask = COOKIE_BOOTSTRAP_BUCKET_COUNT - 1 ;
2113+ int start = ( int ) ( clientHash & ( uint ) mask ) ;
2114+
2115+ lock ( _cookieBootstrapLock )
2116+ {
2117+ int selectedIndex = - 1 ;
2118+ int staleIndex = - 1 ;
2119+
2120+ for ( int probe = 0 ; probe < COOKIE_BOOTSTRAP_MAX_PROBES ; probe ++ )
2121+ {
2122+ int index = ( start + probe ) & mask ;
2123+ ref CookieBootstrapBucket bucket = ref _cookieBootstrapBuckets [ index ] ;
2124+
2125+ if ( bucket . ClientHash == clientHash )
2126+ {
2127+ selectedIndex = index ;
2128+ break ;
2129+ }
2130+
2131+ if ( bucket . ClientHash == 0 )
2132+ {
2133+ selectedIndex = index ;
2134+ break ;
2135+ }
2136+
2137+ if ( ( now - bucket . LastSeenSecond ) >= COOKIE_BOOTSTRAP_IDLE_EVICT_SECONDS && staleIndex < 0 )
2138+ staleIndex = index ;
2139+ }
2140+
2141+ if ( selectedIndex < 0 )
2142+ selectedIndex = staleIndex ;
2143+
2144+ if ( selectedIndex < 0 )
2145+ {
2146+ requestCount = int . MaxValue ; // table pressure fail-closed
2147+ return true ;
2148+ }
2149+
2150+ ref CookieBootstrapBucket state = ref _cookieBootstrapBuckets [ selectedIndex ] ;
2151+
2152+ if ( state . ClientHash != clientHash )
2153+ {
2154+ state . ClientHash = clientHash ;
2155+ state . LastRefillSecond = now ;
2156+ state . LastSeenSecond = now ;
2157+ state . Tokens = COOKIE_BOOTSTRAP_HARD_THRESHOLD ;
2158+ }
2159+ else
2160+ {
2161+ uint elapsed = now - state . LastRefillSecond ;
2162+ if ( elapsed > 0 )
2163+ {
2164+ int refill = ( int ) Math . Min ( int . MaxValue , elapsed * ( uint ) COOKIE_BOOTSTRAP_REFILL_RATE_PER_SECOND ) ;
2165+ state . Tokens = Math . Min ( COOKIE_BOOTSTRAP_HARD_THRESHOLD , state . Tokens + refill ) ;
2166+ state . LastRefillSecond = now ;
2167+ }
2168+
2169+ state . LastSeenSecond = now ;
2170+ }
2171+
2172+ if ( state . Tokens <= 0 )
2173+ {
2174+ requestCount = COOKIE_BOOTSTRAP_HARD_THRESHOLD + 1 ;
2175+ return true ;
2176+ }
2177+
2178+ state . Tokens -- ;
2179+ }
2180+
2181+ requestCount = 0 ;
2182+ return false ;
20432183 }
20442184
20452185 private DnsDatagram HandleInvalidCookieRequest (
@@ -2062,6 +2202,11 @@ private DnsDatagram HandleInvalidCookieRequest(
20622202 {
20632203 Interlocked . Increment ( ref _cookieInvalidDropped ) ;
20642204
2205+ if ( failuresInWindow == ( COOKIE_FAILURE_BADCOOKIE_SLIP_THRESHOLD + 1 ) )
2206+ {
2207+ _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 } .") ;
2208+ }
2209+
20652210 if ( failuresInWindow == ( COOKIE_FAILURE_HARD_THRESHOLD + 1 ) )
20662211 {
20672212 _log . Write ( $ "Client '{ remoteEP . Address } ' exceeded DNS cookie invalid hard threshold ({ COOKIE_FAILURE_HARD_THRESHOLD } /{ COOKIE_FAILURE_WINDOW_SECONDS } s); dropping invalid-cookie requests.") ;
@@ -3204,6 +3349,18 @@ private async Task<DnsDatagram> ProcessRequestAsync(DnsDatagram request, IPEndPo
32043349 if ( requestCookie == null )
32053350 {
32063351 Interlocked . Increment ( ref _cookieMissing ) ;
3352+
3353+ if ( ShouldRateLimitCookieBootstrap ( remoteEP . Address , out int requestsInWindow ) )
3354+ {
3355+ Interlocked . Increment ( ref _cookieRateLimited ) ;
3356+
3357+ if ( requestsInWindow == ( COOKIE_BOOTSTRAP_HARD_THRESHOLD + 1 ) )
3358+ {
3359+ _log . Write ( $ "Client '{ remoteEP . Address } ' exceeded DNS cookie bootstrap threshold ({ COOKIE_BOOTSTRAP_HARD_THRESHOLD } /{ COOKIE_BOOTSTRAP_WINDOW_SECONDS } s); dropping UDP requests without cookie.") ;
3360+ }
3361+
3362+ return null ;
3363+ }
32073364 }
32083365 else
32093366 {
@@ -3248,6 +3405,18 @@ private async Task<DnsDatagram> ProcessRequestAsync(DnsDatagram request, IPEndPo
32483405 if ( scLen == 0 )
32493406 {
32503407 Interlocked . Increment ( ref _cookieClientOnly ) ;
3408+
3409+ if ( ShouldRateLimitCookieBootstrap ( remoteEP . Address , out int requestsInWindow ) )
3410+ {
3411+ Interlocked . Increment ( ref _cookieRateLimited ) ;
3412+
3413+ if ( requestsInWindow == ( COOKIE_BOOTSTRAP_HARD_THRESHOLD + 1 ) )
3414+ {
3415+ _log . Write ( $ "Client '{ remoteEP . Address } ' exceeded DNS cookie bootstrap threshold ({ COOKIE_BOOTSTRAP_HARD_THRESHOLD } /{ COOKIE_BOOTSTRAP_WINDOW_SECONDS } s); dropping client-cookie-only requests.") ;
3416+ }
3417+
3418+ return null ;
3419+ }
32513420 }
32523421 else
32533422 {
0 commit comments