| 1 | #!/usr/bin/env python |
| 2 | #coding=utf8 |
| 3 | # |
| 4 | # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE |
| 5 | # Version 2, December 2004 |
| 6 | # |
| 7 | # Copyright (C) 2004 Sam Hocevar |
| 8 | # 14 rue de Plaisance, 75014 Paris, France |
| 9 | # Everyone is permitted to copy and distribute verbatim or modified |
| 10 | # copies of this license document, and changing it is allowed as long |
| 11 | # as the name is changed. |
| 12 | # |
| 13 | # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE |
| 14 | # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION |
| 15 | # |
| 16 | # 0. You just DO WHAT THE FUCK YOU WANT TO. |
| 17 | # |
| 18 | |
| 19 | import socket |
| 20 | from struct import pack, unpack |
| 21 | import signal |
| 22 | |
| 23 | ARP_GRATUITOUS = 1 |
| 24 | ARP_STANDARD = 2 |
| 25 | |
| 26 | def val2int(val): |
| 27 | '''Retourne une valeur sous forme d'octet en valeur sous forme |
| 28 | d'entier.''' |
| 29 | |
| 30 | return int(''.join(['%02d'%ord(c) for c in val]), 16) |
| 31 | |
| 32 | class TimeoutError(Exception): |
| 33 | '''Exception levée après un timeout.''' |
| 34 | pass |
| 35 | |
| 36 | def timeout(function, timeout=10): |
| 37 | '''Exécute la fonction function (référence) et stoppe son exécution |
| 38 | au bout d'un certain temps déterminé par timeout. |
| 39 | |
| 40 | Retourne None si la fonction à été arretée par le timeout, et |
| 41 | la valeur retournée par la fonction si son exécution se |
| 42 | termine.''' |
| 43 | |
| 44 | def raise_timeout(num, frame): |
| 45 | raise TimeoutError |
| 46 | |
| 47 | # On mappe la fonction à notre signal |
| 48 | signal.signal(signal.SIGALRM, raise_timeout) |
| 49 | # Et on définie le temps à attendre avant de lancer le signal |
| 50 | signal.alarm(timeout) |
| 51 | try: |
| 52 | retvalue = function() |
| 53 | except TimeoutError: # = Fonction quittée à cause du timeout |
| 54 | return None |
| 55 | else: # = Fonction quittée avant le timeout |
| 56 | # On annule le signal |
| 57 | signal.alarm(0) |
| 58 | return retvalue |
| 59 | |
| 60 | # Classes : |
| 61 | ########### |
| 62 | |
| 63 | class ArpRequest: |
| 64 | '''Génère une requête ARP et attend la réponse''' |
| 65 | |
| 66 | def __init__(self, ipaddr, if_name, arp_type=ARP_GRATUITOUS): |
| 67 | # Initialisation du socket (socket brut, donc besoin d'ê root) |
| 68 | self.arp_type = arp_type |
| 69 | self.if_ipaddr = socket.gethostbyname(socket.gethostname()) |
| 70 | |
| 71 | self.socket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, |
| 72 | socket.SOCK_RAW) |
| 73 | self.socket.bind((if_name, socket.SOCK_RAW)) |
| 74 | |
| 75 | self.ipaddr = ipaddr |
| 76 | |
| 77 | |
| 78 | def request(self): |
| 79 | '''Envois une requête arp et attend la réponse''' |
| 80 | |
| 81 | # Envois de 5 requêtes ARP |
| 82 | for _ in range(5): |
| 83 | self._send_arp_request() |
| 84 | |
| 85 | # Puis attente de la réponse |
| 86 | if timeout(self._wait_response, 3): |
| 87 | return True |
| 88 | else: |
| 89 | return False |
| 90 | |
| 91 | |
| 92 | def _send_arp_request(self): |
| 93 | '''Envois une requête ARP pour la machine''' |
| 94 | |
| 95 | # Adresse logicielle de l'émetteur : |
| 96 | if self.arp_type == ARP_STANDARD: |
| 97 | saddr = pack('!4B', |
| 98 | *[int(x) for x in self.if_ipaddr.split('.')]) |
| 99 | else: |
| 100 | saddr = pack('!4B', |
| 101 | *[int(x) for x in self.ipaddr.split('.')]) |
| 102 | |
| 103 | |
| 104 | |
| 105 | # Forge de la trame : |
| 106 | frame = [ |
| 107 | ### Partie ETHERNET ### |
| 108 | # Adresse mac destination (=broadcast) : |
| 109 | pack('!6B', *(0xFF,) * 6), |
| 110 | # Adresse mac source : |
| 111 | self.socket.getsockname()[4], |
| 112 | # Type de protocole (=ARP) : |
| 113 | pack('!H', 0x0806), |
| 114 | |
| 115 | ### Partie ARP ### |
| 116 | # Type de protocole matériel/logiciel (=Ethernet/IP) : |
| 117 | pack('!HHBB', 0x0001, 0x0800, 0x0006, 0x0004), |
| 118 | # Type d'opération (=ARP Request) : |
| 119 | pack('!H', 0x0001), |
| 120 | # Adresse matériel de l'émetteur : |
| 121 | self.socket.getsockname()[4], |
| 122 | # Adresse logicielle de l'émetteur : |
| 123 | saddr, |
| 124 | # Adresse matérielle de la cible (=00*6) : |
| 125 | pack('!6B', *(0,) * 6), |
| 126 | # Adresse logicielle de la cible (=adresse fournie au |
| 127 | # constructeur) : |
| 128 | pack('!4B', *[int(x) for x in self.ipaddr.split('.')]) |
| 129 | ] |
| 130 | |
| 131 | self.socket.send(''.join(frame)) # Envois de la trame sur le |
| 132 | # réseau |
| 133 | |
| 134 | |
| 135 | def _wait_response(self): |
| 136 | '''Attend la réponse de la machine''' |
| 137 | while 0xBeef: |
| 138 | # Récupération de la trame : |
| 139 | frame = self.socket.recv(1024) |
| 140 | |
| 141 | # Récupération du protocole sous forme d'entier : |
| 142 | proto_type = val2int(unpack('!2s', frame[12:14])[0]) |
| 143 | if proto_type != 0x0806: # On passe le traitement si ce |
| 144 | continue # n'est pas de l'arp |
| 145 | |
| 146 | # Récupération du type d'opération sous forme d'entier : |
| 147 | op = val2int(unpack('!2s', frame[20:22])[0]) |
| 148 | if op != 2: # On passe le traitement pour tout ce qui n'est |
| 149 | continue # pas une réponse ARP |
| 150 | |
| 151 | # Récupération des différentes addresses de la trame : |
| 152 | arp_headers = frame[18:20] |
| 153 | arp_headers_values = unpack('!1s1s', arp_headers) |
| 154 | hw_size, pt_size = [val2int(v) for v in arp_headers_values] |
| 155 | total_addresses_byte = hw_size * 2 + pt_size * 2 |
| 156 | arp_addrs = frame[22:22 + total_addresses_byte] |
| 157 | src_hw, src_pt, dst_hw, dst_pt = unpack('!%ss%ss%ss%ss' |
| 158 | % (hw_size, pt_size, hw_size, pt_size), arp_addrs) |
| 159 | |
| 160 | # Comparaison de l'adresse recherchée avec l'adresse trouvée |
| 161 | # dans la trame : |
| 162 | if src_pt == pack('!4B', |
| 163 | *[int(x) for x in self.ipaddr.split('.')]): |
| 164 | return True # Quand on a trouvé, on arrete de chercher ! |
| 165 | # Et oui, c'est mal de faire un retour dans une boucle, |
| 166 | # je sais :) |
| 167 | |