IdIcmpClient.pas 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. { $HDR$}
  2. {**********************************************************************}
  3. { Unit archived using Team Coherence }
  4. { Team Coherence is Copyright 2002 by Quality Software Components }
  5. { }
  6. { For further information / comments, visit our WEB site at }
  7. { http://www.TeamCoherence.com }
  8. {**********************************************************************}
  9. {}
  10. { $Log: 10199: IdIcmpClient.pas
  11. {
  12. { Rev 1.1 2004-04-25 11:49:40 Mattias
  13. { Fixed multithreaded issue
  14. }
  15. {
  16. { Rev 1.0 2002.11.12 10:41:36 PM czhower
  17. }
  18. unit IdIcmpClient;
  19. // MF 25/4/04: Fixed multithreaded issue
  20. // SG 25/1/02: Modified the component to support multithreaded PING and traceroute
  21. // SG 25/1/02: NOTE!!!
  22. // SG 25/1/02: The component no longer use the timing informations contained
  23. // SG 25/1/02: in the packet to compute the roundtrip time. This is because
  24. // SG 25/1/02: that information is only correctly set in case of ECHOREPLY
  25. // SG 25/1/02: In case of TTL, it is incorrect.
  26. interface
  27. uses
  28. Classes,
  29. IdGlobal,
  30. IdRawBase,
  31. IdRawClient,
  32. IdStack,
  33. IdStackConsts,
  34. SysUtils;
  35. const
  36. DEF_PACKET_SIZE = 32;
  37. MAX_PACKET_SIZE = 1024;
  38. // TODO: move ICMP_MIN to IdRawHeaders
  39. ICMP_MIN = 8;
  40. const
  41. iDEFAULTPACKETSIZE = 128;
  42. iDEFAULTREPLYBUFSIZE = 1024;
  43. const
  44. Id_TIDICMP_ReceiveTimeout = 5000;
  45. type
  46. TReplyStatusTypes = (rsEcho, rsError, rsTimeOut, rsErrorUnreachable, rsErrorTTLExceeded);
  47. TReplyStatus = record
  48. BytesReceived: integer; // number of bytes in reply from host
  49. FromIpAddress: string; // IP address of replying host
  50. MsgType: byte;
  51. SequenceId: word; // sequence id of ping reply
  52. // TODO: roundtrip time in ping reply should be float, not byte
  53. MsRoundTripTime: longword; // ping round trip time in milliseconds
  54. TimeToLive: byte; // time to live
  55. ReplyStatusType: TReplyStatusTypes;
  56. end;
  57. TCharBuf = array [1..MAX_PACKET_SIZE] of char;
  58. TICMPDataBuffer = array [1..iDEFAULTPACKETSIZE] of byte;
  59. TOnReplyEvent = procedure(ASender: TComponent; const AReplyStatus: TReplyStatus) of object;
  60. TIdIcmpClient = class(TIdRawClient)
  61. protected
  62. bufReceive: TCharBuf;
  63. bufIcmp: TCharBuf;
  64. wSeqNo: word;
  65. iDataSize: integer;
  66. FReplyStatus: TReplyStatus;
  67. FOnReply: TOnReplyEvent;
  68. FReplydata: String;
  69. //
  70. function CalcCheckSum: word;
  71. function DecodeResponse(BytesRead: Cardinal; var AReplyStatus: TReplyStatus): boolean;
  72. procedure DoReply(const AReplyStatus: TReplyStatus);
  73. procedure GetEchoReply;
  74. procedure PrepareEchoRequest(Buffer: string = ''); {Do not Localize}
  75. procedure SendEchoRequest;
  76. public
  77. constructor Create(AOwner: TComponent); override;
  78. procedure Ping(ABuffer: String = ''; SequenceID: word = 0); {Do not Localize}
  79. function Receive(ATimeOut: Integer): TReplyStatus;
  80. //
  81. property ReplyStatus: TReplyStatus read FReplyStatus;
  82. property ReplyData: string read FReplydata;
  83. published
  84. property ReceiveTimeout default Id_TIDICMP_ReceiveTimeout;
  85. property Host;
  86. property Port;
  87. property Protocol Default Id_IPPROTO_ICMP;
  88. property OnReply: TOnReplyEvent read FOnReply write FOnReply;
  89. end;
  90. implementation
  91. uses
  92. IdException
  93. , IdResourceStrings, IdRawHeaders;
  94. { TIdIcmpClient }
  95. constructor TIdIcmpClient.Create(AOwner: TComponent);
  96. begin
  97. inherited;
  98. FProtocol := Id_IPPROTO_ICMP;
  99. wSeqNo := 3489; // SG 25/1/02: Arbitrary Constant <> 0
  100. FReceiveTimeOut := Id_TIDICMP_ReceiveTimeout;
  101. end;
  102. function TIdIcmpClient.CalcCheckSum: word;
  103. type
  104. PWordArray = ^TWordArray;
  105. TWordArray = array [1..512] of word;
  106. var
  107. pwa: PWordarray;
  108. dwChecksum: longword;
  109. i, icWords, iRemainder: integer;
  110. begin
  111. icWords := iDataSize div 2;
  112. iRemainder := iDatasize mod 2;
  113. pwa := PWordArray(@bufIcmp);
  114. dwChecksum := 0;
  115. for i := 1 to icWords do begin
  116. dwChecksum := dwChecksum + pwa^[i];
  117. end;
  118. if (iRemainder <> 0) then begin
  119. dwChecksum := dwChecksum + byte(bufIcmp[iDataSize + 1]);
  120. end;
  121. dwCheckSum := (dwCheckSum shr 16) + (dwCheckSum and $FFFF);
  122. dwCheckSum := dwCheckSum + (dwCheckSum shr 16);
  123. Result := word(not dwChecksum);
  124. end;
  125. procedure TIdIcmpClient.PrepareEchoRequest(Buffer: string = ''); {Do not Localize}
  126. var
  127. pih: PIdIcmpHdr;
  128. i: integer;
  129. BufferPos: Integer;
  130. begin
  131. iDataSize := DEF_PACKET_SIZE + sizeof(TIdIcmpHdr);
  132. FillChar(bufIcmp, iDataSize, 0);
  133. pih := PIdIcmpHdr(@bufIcmp);
  134. with pih^ do
  135. begin
  136. icmp_type := Id_ICMP_ECHO;
  137. icmp_code := 0;
  138. icmp_hun.echo.id := word(CurrentProcessId);
  139. icmp_hun.echo.seq := wSeqNo;
  140. icmp_dun.ts.otime := GetTickcount;
  141. i := Succ(sizeof(TIdIcmpHdr));
  142. // SG 19/12/01: Changed the fill algoritm
  143. BufferPos := 1;
  144. while (i <= iDataSize) do
  145. begin
  146. // SG 19/12/01: Build the reply buffer
  147. if BufferPos <= Length(Buffer) then
  148. begin
  149. bufIcmp[i] := Buffer[BufferPos];
  150. inc(BufferPos);
  151. end
  152. else
  153. bufIcmp[i] := 'E'; {Do not Localize}
  154. Inc(i);
  155. end;
  156. icmp_sum := CalcCheckSum;
  157. end;
  158. // SG 25/1/02: Retarded wSeqNo increment to be able to check it against the response
  159. end;
  160. procedure TIdIcmpClient.SendEchoRequest;
  161. begin
  162. Send(Host, Port, bufIcmp, iDataSize);
  163. end;
  164. function TIdIcmpClient.DecodeResponse(BytesRead: Cardinal; var AReplyStatus: TReplyStatus): Boolean;
  165. var
  166. // RTTime: longword;
  167. pip, pOriginalIP: PIdIPHdr;
  168. picmp, pOriginalICMP: PIdICMPHdr;
  169. iIpHeaderLen: Cardinal;
  170. ActualSeqID: word;
  171. begin
  172. if BytesRead = 0 then begin
  173. // Timed out
  174. AReplyStatus.BytesReceived := 0;
  175. AReplyStatus.FromIpAddress := '0.0.0.0'; {Do not Localize}
  176. AReplyStatus.MsgType := 0;
  177. AReplyStatus.SequenceId := wSeqNo;
  178. AReplyStatus.TimeToLive := 0;
  179. AReplyStatus.ReplyStatusType := rsTimeOut;
  180. result := true;
  181. end else begin
  182. AReplyStatus.ReplyStatusType := rsError;
  183. pip := PIdIPHdr(@bufReceive);
  184. iIpHeaderLen := (pip^.ip_verlen and $0F) * 4;
  185. if (BytesRead < iIpHeaderLen + ICMP_MIN) then begin
  186. // RSICMPNotEnoughtBytes 'Not enough bytes received' {Do not Localize}
  187. raise EIdIcmpException.Create(RSICMPNotEnoughtBytes);
  188. end;
  189. picmp := PIdICMPHdr(@bufReceive[iIpHeaderLen + 1]);
  190. {$IFDEF LINUX}
  191. // TODO: baffled as to why linux kernel sends back echo from localhost
  192. {$ENDIF}
  193. // Check if we are reading the packet we are waiting for. if not, don't use it in treatement and discard it {Do not Localize}
  194. case picmp^.icmp_type of
  195. Id_ICMP_ECHOREPLY, Id_ICMP_ECHO:
  196. begin
  197. AReplyStatus.ReplyStatusType := rsEcho;
  198. FReplydata := Copy(bufReceive, iIpHeaderLen + SizeOf(picmp^) + 1, Length(bufReceive));
  199. // result is only valid if the seq. number is correct
  200. end;
  201. Id_ICMP_UNREACH:
  202. AReplyStatus.ReplyStatusType := rsErrorUnreachable;
  203. Id_ICMP_TIMXCEED:
  204. AReplyStatus.ReplyStatusType := rsErrorTTLExceeded;
  205. else
  206. raise EIdICMPException.Create(RSICMPNonEchoResponse);// RSICMPNonEchoResponse = 'Non-echo type response received' {Do not Localize}
  207. end; // case
  208. // check if we got a reply to the packet that was actually sent
  209. case AReplyStatus.ReplyStatusType of //
  210. rsEcho:
  211. begin
  212. result := picmp^.icmp_hun.echo.seq = wSeqNo;
  213. ActualSeqID := picmp^.icmp_hun.echo.seq;
  214. // RTTime := GetTickCount - picmp^.icmp_dun.ts.otime;
  215. end
  216. else
  217. begin
  218. // not an echo reply: the original IP frame is contained withing the DATA section of the packet
  219. pOriginalIP := PIdIPHdr(@picmp^.icmp_dun.data);
  220. // move to offset
  221. pOriginalICMP := Pointer(Cardinal(pOriginalIP) + (iIpHeaderLen));
  222. // extract information from original ICMP frame
  223. ActualSeqID := pOriginalICMP^.icmp_hun.echo.seq;
  224. // RTTime := GetTickCount - pOriginalICMP^.icmp_dun.ts.otime;
  225. result := pOriginalICMP^.icmp_hun.echo.seq = wSeqNo;
  226. end;
  227. end; // case
  228. if result then
  229. begin
  230. with AReplyStatus do begin
  231. BytesReceived := BytesRead;
  232. FromIpAddress := GStack.TInAddrToString(pip^.ip_src);
  233. MsgType := picmp^.icmp_type;
  234. SequenceId := ActualSeqID;
  235. // MsRoundTripTime := RTTime;
  236. TimeToLive := pip^.ip_ttl;
  237. end;
  238. end;
  239. end;
  240. end;
  241. procedure TIdIcmpClient.GetEchoReply;
  242. begin
  243. FReplyStatus := Receive(FReceiveTimeout);
  244. end;
  245. procedure TIdIcmpClient.Ping(ABuffer: String = ''; SequenceID: word = 0); {Do not Localize}
  246. var
  247. RTTime: Cardinal;
  248. begin
  249. if SequenceID <> 0 then
  250. wSeqNo := SequenceID;
  251. PrepareEchoRequest(ABuffer);
  252. RTTime := getTickCount;
  253. SendEchoRequest;
  254. GetEchoReply;
  255. RTTime := GetTickDiff(RTTime,GetTickCount);
  256. Binding.CloseSocket;
  257. FReplyStatus.MsRoundTripTime := RTTime;
  258. DoReply(FReplyStatus);
  259. Inc(wSeqNo); // SG 25/1/02: Only incread sequence number when finished.
  260. end;
  261. function TIdIcmpClient.Receive(ATimeOut: Integer): TReplyStatus;
  262. var
  263. BytesRead : Integer;
  264. Size : Integer;
  265. StartTime: Cardinal;
  266. begin
  267. FillChar(bufReceive, sizeOf(bufReceive),0);
  268. Size := sizeof(bufReceive);
  269. StartTime := GetTickCount;
  270. repeat
  271. BytesRead := ReceiveBuffer(bufReceive, Size, ATimeOut);
  272. GStack.CheckForSocketError(BytesRead);
  273. if DecodeResponse(BytesRead, Result) then
  274. begin
  275. break
  276. end
  277. else
  278. begin
  279. // The received reply wasn't for this request, so make sure we don't
  280. // report it as such in case we time out after this
  281. result.BytesReceived := 0;
  282. result.FromIpAddress := '0.0.0.0'; {Do not Localize}
  283. result.MsgType := 0;
  284. result.SequenceId := wSeqNo;
  285. result.TimeToLive := 0;
  286. result.ReplyStatusType := rsTimeOut;
  287. ATimeOut := Cardinal(ATimeOut) - GetTickDiff(StartTime,getTickCount); // compute new timeout value
  288. end;
  289. until ATimeOut <= 0;
  290. end;
  291. procedure TIdIcmpClient.DoReply(const AReplyStatus: TReplyStatus);
  292. begin
  293. if Assigned(FOnReply) then begin
  294. FOnReply(Self, AReplyStatus);
  295. end;
  296. end;
  297. end.