ex0ns

about
A Computer Science Student's blog, about programming, algorithms, hack and a lot more.

[C] Détecteur de proximité

28 Jun 2014

Nous revoilà plongé dans le fantastique monde du Bluetooth et de Bluez. Il y a quelque temps j'avais commencé un projet de capteur de proximité, longtemps (1 an) abandonné, j'ai eu une soudaine envie de le continuer.

Pour résumer, le but de cet outil est simple: associer des scripts à la détection/arrêt de détection d'un appareil bluetooth, dans l'optique, par exemple, de verrouiller votre ordinateur lorsque vous vous en éloignez (téléphone en poche), ou alors de démarrer ThunderBird lorsque vous entrez dans la pièce. Il aurait probablement été plus court et efficace de développer ce genre d'outil dans un autre langage, mais il s'agit très bon entrainement pour faire un peu de C.

Jusque là, tout fonctionne très bien si ce n'est quelques détéctions infructueuses de temps en temps, le daemon fonctionne tout à fait correctement, cependant, je voulais rajouter un niveau de difficulté (et donc d'interêt) au projet en y intégrant une notion de distance, les scripts ne se baserai plus sur une détection binaire, mais pourrait être lancés en fonction de la proximité.

Phase de recherche

Rien ne permet cependant de déterminer avec précision la distance à un objet à partir du bluetooth, et cela à cause des interférences, des objets présents dans l'environnement. Pour autant, il existe un indicateur caractérisant la puissance d'un signal, à partir duquel nous pouvons (essayer) d'interpoler une distance plus ou moins (très peu) précise, il s'agit du Received Signal Strength Indication (RSSI). Cet indicateur semble parfait pour réaliser ce que je veux, bien que peu précis. Pour autant, ce n'est pas si facile, en effet, afin de récupérer la valeur du RSSI pour un périphérique bluetooth, il faut être pairé avec le périphérique, c'est ce que propose la fonction : hci_read_rssi. Cependant, compte tenu de la nature du projet je ne pouvais pas me permettre de pairer les périphériques (imagez un même téléphone devant être lié à plusieurs ordinateur, ou bien un ordinateur connecté à plusieurs périphériques). Il faut donc trouver un moyen de récuperer la valeur du RSSI sans pairer les périphériques, et c'est là que ça tout ce complique...

En effet, rien dans le code source de Bluez semble indiquer qu'il est possible d'effectuer une telle opération, et la documentation ne nous aide pas beaucoup nous plus. Cependant, des petits indices laissent penser qu'il existe un moyen de récupérer la valeur du RSSI au moment du scan, par exemple la structure inquiry_info_with_rssi définie dans hci.h

typedef struct {
       bdaddr_t      bdaddr;
       uint8_t       pscan_rep_mode;
       uint8_t       pscan_period_mode;
       uint8_t       dev_class[3];
       uint16_t      clock_offset;
       int8_t        rssi;
} __attribute__ ((packed)) inquiry_info_with_rssi;

Après de nombreuses heures de recherche, je n'ai rien trouvé de plus probant sur le sujet que la mailing list de Bluez (lien, dans lequel la fonction inquirywithrssi est nommée, le code fournit à la fin ne compile en tout cas pas chez moi mais la technique que je vais vous présenter ici en est très fortement inspirée.

Inquiry with rssi

Dans la suite de cet article, je vais vous présenter ma fonction int hci_inquiry_with_rssi(int dev_id, int len, int num_rsp, const uint8_t lap, inquiry_info_with_rssi *ii) , dont le but est le même que hci_inquiry, mais qui doit en plus, se charger de retourner les informations concernant le RSSI. Pour cela, il nous faut le code source de la fonction hci_inquiry (diponible ici. En bref, le code créer une socket de type bluetooth, la remplie avec les paramètres passés à la fonction puis fait un appel à IOCTL afin de transmettre l'information au driver de la carte bluetooth (par l'intermediaire du kernel), notre code est très ressemblant à l'exception même de IOCTL, que nous n'utiliserons pas. Sans plus tarder, voici le début de la fonction:

int hci_inquiry_with_rssi(int dev_id, int len, int num_rsp, const uint8_t *lap, inquiry_info_with_rssi **ii){
    int nrsp = num_rsp, dd, num, nrinfo = 0;
    struct hci_filter flt;
    inquiry_cp cp;
    struct pollfd p;
    unsigned char buf[HCI_MAX_EVENT_SIZE];
    unsigned char *ptr;
    hci_event_hdr *hdr;
    inquiry_info_with_rssi *start_info, *stop_info, *info;
    extended_inquiry_info *start_e_info, *stop_e_info, *e_info; // Petit détail dont je parlerai à la fin


    uint8_t default_lap[3] = { 0x33, 0x8b, 0x9e }; // Je n'ai pas trouvé la signification de ces valeurs, si une âme charitable est prête à m'expliquer !!!

    if(nrsp <= 0){ // Nombre de réponse invalide
        num_rsp = 0;
        nrsp = 255;
    }

    if(dev_id < 0){ // Périphérique bluetooth
        dev_id = hci_get_route(NULL);
        if(dev_id < 0){
            errno = ENODEV;
            return -1;
        }
    }

    dd = hci_open_dev(dev_id); // Ouverture de la socket sur ce périphérique
    if(dd < 0){
        return dd;
    }

    hci_filter_clear(&flt);
    hci_filter_all_events(&flt);
    hci_filter_set_ptype(HCI_EVENT_PKT, &flt);

    if (setsockopt(dd, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) // Ajoute les options prédéfinies à la socket
        return -1;

Pour ce qui suit, je dois avouer être dans le flou, je ne comprend pas la structure "inquiry_cp", ce que le champ 'lap' représente, il ne s'agit donc que d'une pâle copie de la source de hci_inquiry:

memset(&cp, 0, sizeof(cp));
if(lap  != NULL )
    memcpy(&(cp.lap), lap, 3);
else
    memcpy(&(cp.lap), default_lap, 3);
cp.length  = len;
cp.num_rsp = nrsp;

hci_send_cmd(dd, OGF_LINK_CTL, OCF_INQUIRY, INQUIRY_CP_SIZE, &cp);

p.fd = dd;
p.events = POLLIN | POLLERR | POLLHUP;

Maintenant que la socket est ouverte avec les options souhaitées, nous devons récupérer les évènements sont déclanchés sur cette dernière, c'est ce que nous faisons avec la fonction poll et la structure pollfd, que nous plaçons dans une boucle, dont voici le code:

while(1){
    p.revents = 0;
    if (poll(&p, 1, POLL_TIMEOUT) > 0) { // Retourne le nombre d'évenements déclanchés sur les sockets liée à la structure pollfd
        len = read(dd, buf, sizeof(buf));

        if (len < 0)
            continue; /* Malformed packet */
        else if (len == 0)
            break; /* EOF */

        hdr = (void *) (buf + 1);
        ptr = buf + (1 + HCI_EVENT_HDR_SIZE);
        len -= (1 + HCI_EVENT_HDR_SIZE);

        switch (hdr->evt) {
            case EVT_INQUIRY_RESULT:
            break;
            case EVT_INQUIRY_RESULT_WITH_RSSI:
                num = ptr[0];
                start_info = (void*) ptr + 1;
                stop_info = start_info + num;
                for(info = start_info; info < stop_info; info++){
                    nrinfo = add_device(ii, info, nrinfo);
                }
            break;

            case EVT_EXTENDED_INQUIRY_RESULT:
                num = ptr[0];
                start_e_info = (void*)ptr+1;
                stop_e_info = start_e_info + num;
                for(e_info = start_e_info; e_info < stop_e_info; e_info++){
                    nrinfo = add_device(ii, (inquiry_info_with_rssi*) e_info, nrinfo);
                }
            break;

            case EVT_INQUIRY_COMPLETE:
            return nrinfo;
            break;
        }
    }
}

Une fois que nous avons recupéré le packet dans le buffer grâce à read, ils nous faut retrouver les informations à l'aide des différentes structures telles que: hci_event_hdr, contenant le type d'évènement qui s'est déroulé (dans notre cas, ce sont les INQUIRY_RESULT* qui nous intéressent).

Il existe plusieurs types de retour pour ces évènements, qui dépendent de la technologie utilsée par les périphériques (version bluetooth par exemple), les deux types de retour qui nous interessent sont EVT_INQUIRY_RESULT_WITH_RSSI et EVT_EXTENDED_INQUIRY_RESULT" car tout deux contiennent un champs dédié à la valeur du RSSI, le premier octet contenu dans la variable ptr (contenu du paquet) est le nombre de réponses reçues, il suffit de parcourir les données à l'aide des structures adéquates inquiry_info_with_rssi et extended_inquiry_info, la fonction add_device est la suivante:

int add_device(inquiry_info_with_rssi **list, inquiry_info_with_rssi *ii, int nrinfo){
    char addr[18];
    int found = 0, i;
    for(i = 0; i < nrinfo; i++){
        if(bacmp(&(((*list)+i)->bdaddr), &(ii->bdaddr)) == 0){
            found = 1;
            break;
        }
    }
    if(!found){
        ba2str(&(ii->bdaddr), addr);
        printf("Adding device %s\n", addr);
        memcpy((*list)+nrinfo, ii, sizeof(inquiry_info_with_rssi));
        nrinfo++;
    }
    return nrinfo;
}

Cette fonction prend en paramètre la liste des périphériques découverts jusque là, le dernier résultat du scan, ainsi que le nombre de périphériques déjà découvert (la taille de list), le but est de parcourir cette liste et de n'y ajouter que les périphériques qui n'y sont pas encore présent. Si le périphérique est déjà dans la liste, on ne fait rien, mais on pourrait très bien mettre à jour la valeur du RSSI afin d'avoir la valeur la plus récente possible à la fin du scan (exercice laissé au lecteur, tient en 4 lignes). À la fin, la fonction hci_inquiry_with_rssi retourne le nombre de périphériques détéctés le reste des informations ont étées ajoutées dans le tableau inquiry_info_with_rssi **ii passé en paramètre (exactement comme la fonction hci_inquiry de Bluez).

Et voilà ! Nous avons maintenant la possibilitée de récupérer la valeur RSSI simplement grâce à un scan. J'espère qu'il n'y avait pas une fonctionnalité caché de Bluez qui permet de faire ca en quelques lignes, cependant si vous le connaissez, n'hésitez par à le partager.

Sources

Introduction aux sockets : ARPs

05 Nov 2012

Dans cet article j'aborderai un vaste domaine dans la programmation : celui des sockets ! Un si vaste continent que moulte explorateurs y ont perdu la vie (ou du moins une vie). Mon but n'est pas de faire une explication détaillée sur le fonctionnement des sockets, des connexions, du modèle OSI, je ne pourrais de toute facon par rivaliser avec le fantastique guide de Beej (que je vous conseille fortement de lire par la même occasion).

Le point que j'aborderai précisement tout au long de cet article est loins d'être le plus évident lorsque l'on s'attaque au socket, c'est pour quoi je vous conseille de lire le guide avant d'entamer la lecture de cet article qui traite des Raw Sockets! Comme d'habitude, il est fort probable que des erreurs se soient glissées dans cet article. En effet, je ne fais que partager des projets qui m'ont fait découvrir un domaine, je n'ai donc que peut de recul façe au code, à la conception ou à la technique utilisée. Pour autant, j'espère que ces projets inspirent certains d'entre vous.

Le projet

Nous allons créer une "bibliothèque" (ou plutôt un ensemble de fonctions) permettant de gérer les ARPs, tout cela en C.

Un ARP ? C'est quoi ?

Acronyme de "Address Resolution Protocol" qui est le protocol permettant de faire le lien entre une adresse IP et une addresse physique (MAC) ce protocol n'est présent que sur les réseaux locaux et permet aux machines de communiquer entre elles. En effet, les ARPs permettent aux machines connectées au réseau de garder une correspondance entre les addresses IP et les addresses MAC, sans eux les machines ne pourraient pas communiquer (une machine a besoin de connaitre l'addresses MAC de son destinataire afin d'être sûr que ce soit bien lui qui reçoive le message).

Lorsque vous démarrez votre machine, le cache ARP est vide (dans une console, arp -a pour le visionner), ou ne contient qu'une entrée (celle du routeur). Imaginons que nous voulons effectuer un PING sur une autre machine (du réseau local), la machine ayant l'adresse IP 192.168.0.3, votre PC va d'abord regarder dans la table ARP s'il existe une adresse MAC correspondant à cette IP, dans le cas contraire, il va devoir lla récupérer, et c'est là que les ARP interviennent, votre machine va emettre une requête à toutes les machines du réseau local (broadcast) en demandant l'addresse MAC correspondante à l'IP qu'il veut contacter. A ce moment là, la machine 192.168.0.3 voit qu'on essaye de la contacter, et envoi un ARP en réponse à notre machine, contenant son adresse MAC, le PING peut maintenant être envoyé !

Regardez à nouveau la table arp (arp -a) une nouvelle entrée est apparue, associant l'addresse MAC retournée à l'adresse IP 192.168.0.3. Ainsi, à chaque fois que vous contactez un appareil connecté sur le réseau local, un paquet de type ARP est envoyé afin de determiner l'adresse MAC de la cible (imprimante, téléphone, ordinateur, ...).

Création des structures

Pour notre projet, nous aurons besoin de forger des paquets ARP pour cela, il nous faut un structure bien définie, que nous remplierons par la suite, et qui nous servirons de base, vous aurez deviné que la première structure que nous allons devoir créer et celle des paquets ARP, nous pouvons trouver leur composition sur wikipedia, il ne reste plus qu'a coder tout ca:

struct arpHdr {
    unsigned short int htype;          // Hardware type  
    unsigned short int ptype;          // Protocol type  
    unsigned char hlen;               // Hardware address length  
    unsigned char plen;               // Protocol address length  
    unsigned short int oper;           // ARP operation  
    unsigned char sha[6];          // Sender hardware address.  
    unsigned char spa[4];          // Sender protocol address.  
    unsigned char tha[6];          // Target hardware address.  
    unsigned char tpa[4];          // Target protocol address.  
} __attribute__ ((packed));        // Les variables de la structure se suivent dans la mémoire (pas "d'espace vide")

Jusque là, à part le __attribute__ ((packed)), rien de bien compliqué, si ce n'est qu'il faut faire attention à la taille de SHA, THA et SPA, TPA (respectivement 48 et 32 bits) La seconde structure que nous allons créer est une parti du paquet de type Ethernet (celui qui encapsule les données du protocol), il se compose de trois informations: * L'adresse physique de destination sur 6 octets * L'adresse physique de l'émetteur sur 6 octets * Le type/La taille sur 2 octets Ce qui nous donne:

struct ethHdr{
    u_int8_t tha[6];        // Target hardware address
    u_int8_t sha[6];        // Source hardware address
    u_int16_t type;         // Type/Length
} __attribute__ ((packed));

La troisième classe nous permettra de manipuler plus simplement les deux précédentes, elle représentera notre paquet final, celui qui sera envoyé:

struct arpFull{
    struct ethHdr eth;          // Ethernet packet information
    struct arpHdr arp;          // ARP packet information
} arp;

Maintenant que nous avons nos structures, nous pouvons nous attacher à la création des fonctions.

Les fonctions

La première fonction que nous allons créer et celle qui permettra de remplir la structure arphdr et ethhdr de facon à émettre une requête en broadcast (destiné à toutes les machines) sur le réseau, en anglais, cette requête se traduirai par le fameux "Who has 192.168.1.3 ? Tell 192.168.1.2" (dans cet exemple, notre machine se trouve à l'ip 192.168.1.2 et la machine dont nous voulons connaitre l'addresse MAC porte l'IP 192.168.1.3.

void fill_header_request(char *srcip, char *dstip){
    char broadcastAddr[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // FF:FF:FF:FF:FF:FF
    struct in_addr src, dst; // Needed to convert the IP with inet_addr

    memcpy(arp.arp.tha, broadcastAddr, 6); // Set the target hardware address in the ARP
    memcpy(arp.eth.tha, broadcastAddr, 6); // Same for the Ethernet protocol

    arp.eth.type = htons(ETH_P_ARP); // 0x0806 ARP protocol ID

        // now our Ethernet Packet is ready

    arp.arp.htype = htons(1); // 0x1 Ethernet Type
    arp.arp.ptype = htons(ETH_P_IP); // 0x0800 IP packets only
    arp.arp.hlen = 6;
    arp.arp.plen = 4;
    arp.arp.oper = htons(1); // 1 For request, 2 for reply (see previous Wiki page)

    src.s_addr = inet_addr(srcip);
    dst.s_addr = inet_addr(dstip);

    memcpy(&arp.arp.spa, (unsigned char *)&src.s_addr, 4); // Set the source IP address
    memcpy(&arp.arp.tpa, (unsigned char *)&dst.s_addr, 4); // Same for the target   

}

Je n'ai pas grand chose à ajouter sur ce code, il est relativement compréhensible, nous remplissons les structures des paquets ethernet et ARP, il faut juste penser à convertir l'adresse IP grâce à une structure in_addr et la fonction inet_addr. De même, écrivons la fonction que aura comme rôle de remplir ces structures de la même façon, mais pour effectuer une réponse ARP. Une réponse ARP est un paquet qui se traduit par la réponse à la question précédente, c'est à dire : "192.168.1.3 is at 00:01:02:03:04:05", le cache ARP des deux machines est alors mis à jour et elles peuvent communiquer sur le réseaux sans avoir besoin d'envoyer un ARP à chaque fois. Comme vous pouvez vous en douter, les deux fonctions vont avoir de très nombreuses ressemblances :

void fill_header_reply(char *srcip, char *dstip, char *dsthwd){
    struct in_addr src, dst; // Needed to convert the IP with inet_addr
    char hardwareAddr[6] = {0};
    int i;
    for(i=0; i<6 ;i++){
        char *next;
        hardwareAddr[i] = strtol(dsthwd, &next, 16); // Hex value of char between ":"
        dsthwd=++next; // The next one 
    }

    memcpy(arp.eth.tha, hardwareAddr, 6); // Set ther hardware address in Ethernet
    memcpy(arp.arp.tha, hardwareAddr, 6); // Same for ARP protocol

    arp.eth.type = htons(ETH_P_ARP); // 0x0806 ARP protocol ID

    arp.arp.htype = htons(1); // 0x1 Ethernet Type
    arp.arp.ptype = htons(ETH_P_IP); // 0x0800 IP packets only
    arp.arp.hlen = 6;
    arp.arp.plen = 4;
    arp.arp.oper = htons(2); // 1 For request, 2 for reply (see previous Wiki page)

    src.s_addr = inet_addr(srcip);
    dst.s_addr = inet_addr(dstip);

    memcpy(&arp.arp.spa, (unsigned char *)&src.s_addr, 4);
    memcpy(&arp.arp.tpa, (unsigned char *)&dst.s_addr, 4);

}

Ce qui change ici est la première boucle, qui peut paraitre étrange, mais elle convertie très simplement une addresse MAC de la forme 00:01:02:03:04:05 à des valeurs hexadécimales, dans un tableau. Nous n'avons plus qu'à remplir la structure, et changer OPER en 2 afin de préciser qu'il s'agit d'une réponse et non pas d'une requête. Coici maintenant le coeur de programme (problème ?), la création et l'envoi du paquet final (de la requête/réponse ARP), c'est ici que les sockets vont (enfin) intervenir !

int sendArp(char *iface){
    struct sockaddr_ll device;
    struct ifreq ifr;
    int sockfd;
    char buffer[1024];

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    strncpy(ifr.ifr_name, iface, IFNAMSIZ); // Set the interface name (and fill with 0)
    ioctl(sockfd, SIOCGIFINDEX, &ifr);  
    device.sll_ifindex = ifr.ifr_ifindex; // iface index
    ioctl(sockfd, SIOCGIFHWADDR, &ifr);
    memcpy(device.sll_addr, ifr.ifr_hwaddr.sa_data, 6); // iface hardware address

    device.sll_family = AF_PACKET; // Always AF_PACKET
    device.sll_protocol = htons(ETH_P_IP); //  0x0800 IP packets only
    device.sll_hatype = 1; // 0x1 (Ethernet hardware address)
    device.sll_halen = 6;


    /* Now our device structure is ready, we need to fill the sender information in ARP and Ethernet header */

    memcpy(arp.arp.sha, ifr.ifr_hwaddr.sa_data, 6); // Set the sender Mac address in ARP 
    memcpy(arp.eth.sha, ifr.ifr_hwaddr.sa_data, 6); // Same for Ethernet

    close(sockfd); // We want to use it again

    sockfd = socket( PF_PACKET, SOCK_RAW, htons(ETH_P_ARP)); 
    /*
    This is the most interesting point, let's see how it works
        PF_PACKET : Low level packet interface 
        SOCK_RAW : Provides raw network protocol access.
        ETH_P_ARP : ARP protocol
        Now we have a raw socket and we can put everything we want in it !
        The kernel doesn't do anything with your packet, it's all yours     
    */
    memcpy(buffer, &arp, sizeof(struct arpFull)); // Copy our structure (ARP header and EThernet Header to the buffer);
    if(sendto(sockfd, buffer, sizeof(struct arpFull), 0, (struct sockaddr *)&device, (socklen_t) sizeof(struct sockaddr_ll)) == -1){
        printf("Error");
    }

}

Analysons un peu cette fonction, afin de découvrir le fonctionnement des raws sockets. Premièrement, afin de récuperer le numéro de l'interface ainsi que son addresse MAC, nous devons créer un "file descriptor", que nous passerons en argument à la fonction ioctl, il faut ensuite préciser quel type de requête on veut effectuer avec ioctl, SIOCGIFINDEX nous retourne l'index correspondant à l'interface portant le nom contenu dans la structure (dans ce cas, ifr.ifrname, qui nous avons défini une ligne avant), puis nous appellons à nouveau la fonction, mais avec une requete de type _SIOCGIFHWADDR, qui retourne de la même façon l'adresse MAC de l'interface. Il nous faut ensuite compléter notre structure sockaddr_ll que nous passerons en paramètre à la fonction sendto au moment de l'envoi. Le remplissage de cette structure n'a rien de très différent avec ce qu'on a fait avant (protocol, type, taille). La prochaine étape consiste à (enfin) finaliser nos en-tête ARP et Ethernet en y insérant notre adresse MAC récupérée à l'aide IOCTL, nos deux en-têtes sont maintenant complètes, il ne nous reste plus qu'a créer une socket et à les envoyer ! Pour créer la raw socket afin de garder un controle total sur cette dernière, les arguments sont les suivants (également detaillés dans le code) :

  • PF_PACKET : Permet d'utiliser des sockets de bas niveau (le kernel ne fait "plus rien")
  • SOCK_RAW : Fournit l'acces au protocol de type RAW
  • ETHPARP : Celui la ou le connait, c'est le protocol ARP

Nous avons donc notre raw socket, que nous controlons intégralement, il ne nous reste plus qu'a y attacher nos données (les structures remplies précedement), et à envoyer tout ca vers sur le réseau \o/. On copie donc le contenu de nos structures dans notre buffer et on envoie tout ça grâce à sendto, là encore, rien de très complexe (il suffit de lire la doc !).

Ah oui, j'allais oublier, pour ceux qui on eu le courage de me lire jusqu'ici (je vous l'accorde, c'est un acte de bravoure !), voici les #include et dont vous aurez besoin, ainsi qu'un petit main() :)

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <asm/types.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>   

int main(int argc, char *argv[]){
    fill_header_request(argv[1], argv[2]); // argv[1] -> source IP (your IP) argv[2] -> destination IP (your target)
    sendArp("wlan0"); // Change the device name
    return 0;
}

Ajout du 11/01/2013

La source complète du projet (utilisé dans les illustrations suivantes) : arp_forge. On m'a également conseillé de rajouter quelques illustrations, les voici: 192.168.1.51 -> L'adresse IP de mon PC 192.168.1.50 -> L'android que nous voulons ajouter à la liste ARP

Au début, nous avons quelque chose qui ressemble à ca (wireshark est vide, notre table arp incomplète)

Arp empty Arp empty

Après avoir lancé le programme comme ./a.out 192.168.1.51 192.168.1.51 (voir le main donné ci-dessus) qui aura comme conséquence de broadcaster afin d'avoir un retour sur l'addresse MAC de cette IP (obtenir l'adresse MAC à partir de l'adresse IP de l'appareil).

Wireshark dump

AzureWav est la source (mon PC) qui broadcast un "who has" sur tout le réseau local, puis Samsung (l'android) nous réponds en nous donnant son adresse IP, c'est exactement ce que nous voulions, du coté de la table arp, nous avons :

Wireshark dump

En espérant que ces illustrations éclaircissent un peu mes propos confus !