Visual Basic Simple
Effettuare un Ping tramite WSA e ICMP.DLL
Sincronizza Indice
Sincronizza Indice
Scarica il progetto
Scarica il progetto
Scarica il testo dell'articolo
Testo dell'articolo
Stampa l'articolo
Stampa l'articolo
Ricerca personalizzata

Difficoltà: 3 / 5

Il termine Ping è ormai entrato nel dizionario comune di qualsiasi sistemista e molti utenti di computer; il termine tecnico di questo genere di operazione è ICMP Echo e si tratta di una richiesta (ECHO Request) fatta mediante il protocollo ICMP (Internet Control Message Protocol) a cui il destinatario deve rispondere (ECHO Response) con gli stessi dati ricevuti. Per questa ragione la funzione prende il nome di ICMP Echo (Eco) e la sua implementazione più comune è data dal programma PING.

Il progetto sviluppato in questo articolo utilizza le classi clsFBISocket e clsFBISocketInfo trattate negli articoli precedenti e vi aggiunge una semplice richiesta ICMP Echo e tutto ciò che vi gira attorno (tempo di risposta, dati di ritorno, codici di errore). L'attività di Echo è demandata alla libreria ICMP.DLL presente in quasi tutti i sistemi operativi Windows.

Microsoft scoraggia l'uso della libreria ICMP.DLL e da qualche anno avvisa che nelle versioni future del sistema operativo potrebbe essere assente; tuttavia fino all'ultima attuale versione di Windows (Windows 2003 Server) la libreria è ancora presente. Anche per questa ragione la documentazione sulle funzioni della libreria ICMP sono poche e confuse. Microsoft consiglia invece di utilizzare i cosiddetti socket grezzi (RAW Sockets) disponibili a partire da Windows Socket 2.0. L'implementazione tuttavia richiede notevoli complicazioni che al giorno d'oggi è possibile evitare.

Vediamo quindi il codice della classe clsFBISocketPing, come già detto basata sulle funzionailtà di ICMP.DLL:

  1. Option Explicit
  2. Private Type ICMP_OPTIONS
  3.     TTL As Byte
  4.     Tos As Byte
  5.     Flags As Byte
  6.     OptionsSize As Byte
  7.     OptionsData As Long
  8. End Type
  9. Private Type ICMP_ECHO_REPLY
  10.     Address As Long
  11.     Status As Long
  12.     RoundTripTime As Long
  13.     DataSize As Integer
  14.     Reserved As Integer
  15.     DataPointer As Long
  16.     Options As ICMP_OPTIONS
  17.     Data As String * 256
  18. End Type

Le due strutture ICMP_OPTIONS e ICMP_ECHO_REPLY sono ricavate da informazioni trovate sul web e da numerose prove. Trovare una documentazione ufficiale su queste non è cosa affatto facile. La prima struttura ICMP_OPTIONS definisce le opzioni relative all'invio (ECHO Request) ed alla risposta (ECHO Response). Il più importante di questi valori è il campo TTL (Time To Live) che definisce il numero massimo di salti che il pacchetto potrà fare, ovvero il numero massimo di router che il pacchetto potrà attraversare prima di essere scartato perché considerato scaduto. Nel nostro esempio utilizzeremo il valore massimo 255.

L'altro tipo di dati è ICMP_ECHO_REPLY e costituisce il pacchetto di ritorno dalla funzione che effettuerà il ping. Al suo interno contiene l'indirizzo IP decimale dell'host (Address) che ha risposto alla richiesta, un codice di ritorno (Status), il tempo impiegato dal pacchetto (RoundTripTime), i dati restituiti (Data) e la loro ampiezza (DataSize), assieme alle opzioni con cui il pacchetto di ritorno dall'host è stato inviato (Options).

  1. Private Declare Function IcmpCreateFile Lib "ICMP.DLL" () As Long
  2. Private Declare Function IcmpCloseHandle Lib "ICMP.DLL" (ByVal IcmpHandle As Long) As Long
  3. Private Declare Function IcmpSendEcho Lib "ICMP.DLL" (ByVal IcmpHandle As Long, ByVal DestinationAddress As Long, ByVal RequestData As String, ByVal RequestSize As Integer, RequestOptions As ICMP_OPTIONS, ReplyBuffer As ICMP_ECHO_REPLY, ByVal ReplySize As Long, ByVal Timeout As Long) As Long
  4. Private WithEvents fbiSocket As clsFBISocketInfo
  5. Private echoReply As ICMP_ECHO_REPLY
  6. Private icmpOptions As ICMP_OPTIONS
  7. Private m_Timeout As Long
  8. Public Event Error(ByVal ErrorCode As Long, ByVal Description As String, ByRef Cancel As Boolean)

Affinchè sia possibile inviare il pacchetto di richiesta è necessario ottenere prima un handle e ciò può essere fatto richiamando la funzione IcmpCreateFile. Lo stesso handle deve essere rilasciato mediante la funzione IcmpCloseHandle. Il cuore di tutto questo articolo è invece la terza funzione: IcmpSendEcho, che richiede il numero di handle (IcmpHandle), l'indirizzo di destinazione (DestinationAddress) espresso in maniera decimale, il pacchetto di dati da inviare (RequestData) e la sua ampiezza (RequestSize), la specifica delle opzioni (RequestOption) con cui inviare il pacchetto, una struttura per accogliere i risultati (ReplyBuffer) e la sua relativa ampiezza (ReplySize) ed infine il tempo in millesimi di secondo (Timeout) prima che la richiesta possa essere considerata scaduta.

Abbiamo naturalmente utilizzato un'istanza della classe clsFBISocketInfo trattata nell'articolo precedente, i cui errori saranno inviati al programma utilizzatore della presente classe mediante l'evento Error ridefinito in questa. Il membro echoReply conterrà i dati di risposta del pacchetto di richiesta, mentre icmpOptions sarà utilizzato per indicare le opzioni del pacchetto inviato, nel caso in dettaglio, per specificare il TTL di andata. Il membro m_Timeout conterrà il valore della proprietà Timeout definita in seguito.

  1. Private Sub Class_Initialize()
  2.     Set fbiSocket = New clsFBISocketInfo
  3.     m_Timeout = 1000
  4.     icmpOptions.TTL = 255
  5. End Sub
  6. Private Sub Class_Terminate()
  7.     Set fbiSocket = Nothing
  8. End Sub
  9. Private Sub fbiSocket_Error(ByVal ErrorCode As Long, ByVal Description As String, Cancel As Boolean)
  10.     RaiseEvent Error(ErrorCode, Description, Cancel)
  11. End Sub

Naturalmente durante la fase di inizializzazione è istanziato l'oggetto fbiSocket ed assegnati i valori predefiniti alle proprietà Timeout e TTL. Analogamente alla distruzione dell'oggetto sarà eliminato anche l'oggetto dipendente e quindi liberate le risorse. Come già detto tutti gli errori ricevuti dall'oggetto fbiSocket saranno propagati all'utilizzatore della presente classe che potrà provvedere ad annullare la visualizzazione del messaggio di errore.

  1. Public Function Ping(ByVal Host As String, ByVal Data As String) As Long
  2.     Dim hFile As Long
  3.     Data = Left(Data, Len(echoReply.Data))
  4.     hFile = IcmpCreateFile
  5.     Ping = -1
  6.     If hFile = 0 Then
  7.         Call fbiSocket.Socket.HandleError(21, "Impossibile creare un handle valido")
  8.     Else
  9.         If IcmpSendEcho(hFile, fbiSocket.HostToLong(Host, 0), Data, Len(Data), icmpOptions, echoReply, Len(echoReply), m_Timeout) <> 0 Then _
  10.             Ping = echoReply.Status
  11.         Call IcmpCloseHandle(hFile)
  12.     End If
  13. End Function

Il primo metodo  e quello principale è sicuramente Ping. Da questo dipendono tutti i valori di ritorno e sostanzialmente consiste di poche e semplici righe; richiede unicamente l'host di destinazione ed i dati da inviare; la riga 49 assicura che i dati richiesti non superino l'ampiezza del buffer dedicato nella struttura ICMP_ECHO_REPLY, mentre la riga 51 inizializza il valore di ritorno della funzione a -1, ad indicare un avvenuto errore; in caso di successo (riga 56) questo valore sarà modificato.

Ad ogni utilizzo sarà creato un handle mediante IcmpCreateFile: in caso di insuccesso verrà generato un errore e la funzione restituirà valore -1, mentre in caso di successo lo stesso handle sarà fornito alla funzione IcmpSendEcho, assieme a tutti gli altri dati richiesti. L'indirizzo di destinazione in maniera decimale è ottenuto richiamando il metodo HostToLong della classe clsFBISocketInfo. Con la richiesta saranno specificate anche le opzioni (icmpOptions), il pacchetto di ritorno (echoReply) ed il tempo massimo per eseguire la richiesta (m_Timeout).

Se la richiesta ha avuto buon fine, cioè è stato possibile effettuarla senza errori di sistema, il suo valore è differente da zero e pertanto la funzione restituirà il codice di errore contenuto nel campo Status della pacchetto di risposta (riga 56). In seguito sarà possibile liberare l'handle riservato in precedenza.

Seguono le numerose proprietà relative ordinate per tipologia: le prime indicheranno valori da utilizzare nella richiesta, mentre le ultime corrisponderanno al recupero dei dati restituiti dalla richiesta di echo.

  1. Public Property Get Timeout() As Long
  2.     Timeout = m_Timeout
  3. End Property
  4. Public Property Let Timeout(ByVal newValue As Long)
  5.     m_Timeout = newValue
  6. End Property
  7. Public Property Get TTL() As Byte
  8.     TTL = icmpOptions.TTL
  9. End Property
  10. Public Property Let TTL(ByVal newValue As Byte)
  11.     icmpOptions.TTL = newValue
  12. End Property

Le due proprietà relative alla richiesta sono Timeout e TTL che specificano rispettivamente il tempo massimo ed il numero di salti che il pacchetto è in grado di attraversare prima che la richiesta possa considerarsi scaduta.

  1. Public Property Get EchoAddress() As String
  2.     EchoAddress = fbiSocket.LongToIP(echoReply.Address)
  3. End Property
  4. Public Property Get TripTime() As Long
  5.     If echoReply.Status = 0 Then TripTime = echoReply.RoundTripTime
  6. End Property
  7. Public Property Get Reply() As String
  8.     If echoReply.Status = 0 Then Reply = Left$(echoReply.Data, echoReply.DataSize)
  9. End Property
  10. Public Property Get TTLReply() As Byte
  11.     TTLReply = echoReply.Options.TTL
  12. End Property
  13. Public Property Get Status() As Long
  14.     Status = echoReply.Status
  15. End Property

Le proprietà a sola lettura EchoAddres, TripTime, Reply, TTLReply e Status restituiscono rispettivamente l'indirizzo da cui proviene la risposta, il tempo che il pacchetto ha impiegato per ritornare, i dati contenuti nel pacchetto di ritorno (che per il pacchetto ECHO_RESPONSE dovrebbero essere gli stessi della richiesta ECHO_REQUEST), il TTL del pacchetto restituito ed il codice di ritorno dell'operazione. Un codice di 0 indica un successo mentre tutti gli altri valori indicano errori che trovano la loro descrizione nella proprietà seguente:

  1. Public Property Get StatusDescription() As String
  2.     Select Case echoReply.Status
  3.         Case 0: StatusDescription = "Successo"
  4.         Case 200: StatusDescription = "Tempo massimo superato"
  5.         Case 11001: StatusDescription = "Buffer troppo piccolo"
  6.         Case 11002: StatusDescription = "Rete di destinazione irraggiungibile"
  7.         Case 11003: StatusDescription = "Host di destinazione irraggiungibile"
  8.         Case 11004: StatusDescription = "Protocollo di destinazione irraggiungibile"
  9.         Case 11005: StatusDescription = "Porta di destinazione irraggiungibile"
  10.         Case 11006: StatusDescription = "Risorse insufficienti"
  11.         Case 11007: StatusDescription = "Opzioni errate"
  12.         Case 11008: StatusDescription = "Errore hardware"
  13.         Case 11009: StatusDescription = "Pacchetto troppo grande"
  14.         Case 11010: StatusDescription = "Richiesta scaduta"
  15.         Case 11011: StatusDescription = "Richiesta errata"
  16.         Case 11012: StatusDescription = "Rotta errata"
  17.         Case 11013: StatusDescription = "TTL scaduto nella trasmissione"
  18.         Case 11014: StatusDescription = "TTL scaduto nel riassemblaggio"
  19.         Case 11015: StatusDescription = "Errori nei parametri"
  20.         Case 11016: StatusDescription = "Origine spenta"
  21.         Case 11017: StatusDescription = "Opzioni troppo grandi"
  22.         Case 11018: StatusDescription = "Destinazione errata"
  23.         Case 11019: StatusDescription = "Indirizzo cancellato"
  24.         Case 11050: StatusDescription = "Errore generale"
  25.         Case Else: StatusDescription = "Errore sconosciuto"
  26.     End Select
  27. End Property

Credo che quest'ultima proprietà non necessiti di commenti, una lunga sequenza di controlli di un unico valore determina il messaggio di errore corrispondente.


Figura 1Scriveremo un semplicissimo front-end a questa classe che già svolge totalmente il suo lavoro; si comporrà di un solo form ed una serie di caselle di testo per contenere i dati delle richieste e ricevere quelli delle risposte.

Naturalmente tutta la sezione superiore è dedicata ai dati di input ed inizialmente conterrà i valori predefiniti: TTL di 255 salti e Timeout di 1000 millisecondi (1 secondo). Il campo della destinazione potrà contenere un indirizzo IP oppure il nome di un host da interrogare.

Il suo utilizzo è semplice quanto banale e di fianco è riportato un esempio del suo utilizzo su una macchina in rete locale. Possiamo notare di curioso che le macchine interrogate rispondono sempre con un TTL di 128 passaggi.

Se volessimo simulare il funzionamento dell'applicazione Ping di Windows dovremmo semplicemente impostare un TTL di 255 salti ed un pacchetto dati di tutte le lettere alfabetiche minuscole e consecutive dalla a alla w, eventualmente ripetendo la sequenza se i 23 caratteri non dovessero bastare. L'opzione standard di Ping di Windows prevede un pacchetto dati composto da abcdefghijklmnopqrstuvwabcdefghi per un totale di 32 caratteri.

Come già detto, l'articolo utilizza il protocollo ICMP, e non i protocolli TCP o UDP utilizzabili mediante il controllo OCX Winsock. Questo genere di applicazione è possibile soltanto utilizzando le funzioni WSA oppure appoggiandosi ad un controllo esterno che effettui questo lavoro per conto nostro.

Fibia FBI
28 Marzo 2004

Scarica il progetto
Scarica il progetto
Scarica il testo dell'articolo
Scarica il testo dell'articolo
Stampa l'articolo
Stampa l'articolo
Torna all'indice Client/Server