Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

288 lines
6.8 KiB

  1. // Copyright (c) 1996-1999 Microsoft Corporation
  2. //+-------------------------------------------------------------------------
  3. //
  4. // Microsoft Windows
  5. //
  6. // File: denial.cxx
  7. //
  8. // Contents: Code to detect denial of service attacks.
  9. //
  10. // Classes:
  11. //
  12. // Functions:
  13. //
  14. //
  15. //
  16. // History: 18-Nov-96 BillMo Created.
  17. //
  18. // Notes:
  19. //
  20. // Codework:
  21. //
  22. //--------------------------------------------------------------------------
  23. #include <pch.cxx>
  24. #pragma hdrstop
  25. #include "trksvr.hxx"
  26. #define MAX_HISTORY_PERIODS 8 // best to make this a power of two for easy division
  27. #define HISTORY_PERIOD 10 // seconds
  28. #define MAX_MOVING_AVERAGE 20 // 20 notifications per HISTORY_PERIOD
  29. struct ACTIVECLIENT
  30. {
  31. ACTIVECLIENT(const CMachineId & mcidClient) : _mcidClient(mcidClient)
  32. {
  33. _pNextClient = NULL;
  34. memset(_aRequestHistory, 0, sizeof(_aRequestHistory));
  35. _iWrite = 0;
  36. _cNonZero = 0;
  37. }
  38. inline void CountEvent();
  39. inline BOOL IsActive();
  40. inline void NewPeriod();
  41. ULONG Average();
  42. struct ACTIVECLIENT * _pNextClient;
  43. CMachineId _mcidClient;
  44. ULONG _aRequestHistory[MAX_HISTORY_PERIODS];
  45. ULONG _iWrite;
  46. ULONG _cNonZero;
  47. };
  48. void
  49. CDenialChecker::Initialize( ULONG ulHistoryPeriod )
  50. {
  51. _cs.Initialize();
  52. _fInitializeCalled = TRUE;
  53. _pListHead = NULL;
  54. #if DBG
  55. _lAllocs = 0;
  56. #endif
  57. _timer.Initialize(this,
  58. NULL, // No name (non-persistent)
  59. 0, // Context ID
  60. ulHistoryPeriod,
  61. CNewTimer::NO_RETRY,
  62. 0, 0, 0 );
  63. }
  64. // must be called after the worker thread has exitted
  65. // RPC server stopped already
  66. void
  67. CDenialChecker::UnInitialize()
  68. {
  69. if (_fInitializeCalled)
  70. {
  71. _fInitializeCalled = FALSE;
  72. _timer.UnInitialize();
  73. _cs.UnInitialize();
  74. ACTIVECLIENT * pDel = _pListHead;
  75. while (pDel)
  76. {
  77. ACTIVECLIENT * pNext = pDel->_pNextClient;
  78. delete pDel;
  79. #if DBG
  80. _lAllocs--;
  81. #endif
  82. pDel = pNext;
  83. }
  84. TrkLog((TRKDBG_DENIAL, TEXT("CDenialChecker::UnInitialize _lAllocs = %d"), _lAllocs));
  85. }
  86. }
  87. void
  88. CDenialChecker::CheckClient(const CMachineId & mcidClient)
  89. {
  90. __try
  91. {
  92. _cs.Enter();
  93. ACTIVECLIENT * pSearch = _pListHead;
  94. ACTIVECLIENT * pPrevious = NULL;
  95. while (pSearch)
  96. {
  97. if (pSearch->_mcidClient == mcidClient)
  98. {
  99. // move to head of list so that we can quickly find the abuser
  100. // next time
  101. if ( pSearch != _pListHead )
  102. {
  103. TrkAssert(pPrevious != NULL);
  104. pPrevious->_pNextClient = pSearch->_pNextClient;
  105. pSearch->_pNextClient = _pListHead;
  106. _pListHead = pSearch;
  107. }
  108. pSearch->CountEvent();
  109. if (pSearch->Average() > MAX_MOVING_AVERAGE)
  110. {
  111. TrkRaiseException(TRK_E_DENIAL_OF_SERVICE_ATTACK);
  112. }
  113. break;
  114. }
  115. pPrevious = pSearch;
  116. pSearch = pSearch->_pNextClient;
  117. }
  118. if (pSearch == NULL)
  119. {
  120. ACTIVECLIENT * pNew = new ACTIVECLIENT(mcidClient);
  121. TrkLog(( TRKDBG_DENIAL, TEXT("Adding to denial list (%d)"), _lAllocs+1 ));
  122. if (!pNew)
  123. {
  124. TrkRaiseWin32Error(ERROR_NOT_ENOUGH_MEMORY);
  125. }
  126. #if DBG
  127. _lAllocs++;
  128. #endif
  129. // If the list had gone empty, then the timer was stopped,
  130. // so start it back up again.
  131. if( NULL == _pListHead )
  132. {
  133. TrkLog(( TRKDBG_DENIAL, TEXT("Starting denial-checker timer") ));
  134. _timer.SetRecurring();
  135. }
  136. pNew->_pNextClient = _pListHead;
  137. _pListHead = pNew;
  138. }
  139. }
  140. __finally
  141. {
  142. _cs.Leave();
  143. }
  144. }
  145. PTimerCallback::TimerContinuation
  146. CDenialChecker::Timer( ULONG ulTimerId )
  147. {
  148. //
  149. // periodically increment iWrite in each entry
  150. // and throw out entries that are now too old
  151. // (this is when all counters go to zero i.e.
  152. // no activity for MAX_HISTORY_PERIODS periods)
  153. _cs.Enter();
  154. ACTIVECLIENT * pSearch = _pListHead;
  155. ACTIVECLIENT * pPrevious = NULL;
  156. TimerContinuation continuation = CONTINUE_TIMER;
  157. while (pSearch)
  158. {
  159. pSearch->NewPeriod();
  160. if (!pSearch->IsActive())
  161. {
  162. // remove from list and free
  163. ACTIVECLIENT * pNext = pSearch->_pNextClient;
  164. TrkLog(( TRKDBG_DENIAL, TEXT("Paring denial list (%d)"), _lAllocs-1 ));
  165. if (pPrevious)
  166. {
  167. pPrevious->_pNextClient = pNext;
  168. }
  169. else
  170. {
  171. _pListHead = pNext;
  172. }
  173. delete pSearch;
  174. #if DBG
  175. _lAllocs--;
  176. #endif
  177. pSearch = pNext;
  178. }
  179. else
  180. {
  181. pPrevious = pSearch;
  182. pSearch = pSearch->_pNextClient;
  183. }
  184. }
  185. if( NULL == _pListHead )
  186. {
  187. TrkLog(( TRKDBG_DENIAL, TEXT("Stopping denial checker timer") ));
  188. continuation = BREAK_TIMER;
  189. }
  190. _cs.Leave();
  191. //TrkLog((TRKDBG_DENIAL, TEXT("CDenialChecker: SimpleTimer _lAllocs = %d"), _lAllocs));
  192. TrkAssert( CNewTimer::NO_RETRY == _timer.GetRetryType() );
  193. TrkAssert( _timer.IsRecurring() );
  194. return( continuation );
  195. }
  196. inline void
  197. ACTIVECLIENT::CountEvent()
  198. {
  199. _aRequestHistory[_iWrite] ++;
  200. if (_aRequestHistory[_iWrite] == 1)
  201. {
  202. TrkAssert(_cNonZero < MAX_HISTORY_PERIODS);
  203. _cNonZero ++;
  204. }
  205. }
  206. // returns FALSE if we should discard this ACTIVECLIENT
  207. inline void
  208. ACTIVECLIENT::NewPeriod()
  209. {
  210. // skip the write pointer to next
  211. if (_iWrite >= sizeof(_aRequestHistory)/sizeof(_aRequestHistory[0]))
  212. _iWrite = 0;
  213. else
  214. _iWrite ++;
  215. // if what we're writing over is not zero, then keep track of # of zero entries
  216. if (_aRequestHistory[_iWrite] != 0)
  217. {
  218. TrkAssert(_cNonZero != 0);
  219. _cNonZero --;
  220. }
  221. _aRequestHistory[_iWrite] = 0;
  222. }
  223. inline BOOL
  224. ACTIVECLIENT::IsActive()
  225. {
  226. return(_cNonZero != 0);
  227. }
  228. inline ULONG
  229. ACTIVECLIENT::Average()
  230. {
  231. ULONG Sum=0;
  232. for ( ULONG i=0; i<MAX_HISTORY_PERIODS; i++ )
  233. Sum += _aRequestHistory[i];
  234. return(Sum / MAX_HISTORY_PERIODS);
  235. }