creative commons
Introduction à la programmation wifi en C sous NetBSD
icone programmation
Signature :
GNU/Linux Magazine
Sommaire de l'article :
Tu aimes Dave, tu aimes aussi Sheila, tu es fan de Dalida, tu as toujours rêvé de savoir danser sur un damier illuminé et tu répètes "Parole" depuis des années devant le miroir de ton armoire. Si c'est bien ce que tu cherches, cet article n'est pas pour toi, mais je suis enchanté de faire ta connaissance. Cependant, si tu as un peu de temps (tu répèteras plus tard devant ton miroir), je vais te faire rencontrer des lutins, des lutins magiques, des miettes de réseau qui volent dans l'air que tu respires chaque jour. Je vais te parler de code et de wireless sous NetBSD. Il s'agit d'une introduction, je répète une introduction, à la programmation 802.11 en utilisant l'API unifiée (/usr/include/net80211/*) ieee80211 de NetBSD. C'est probablement ce dont j'aurais eu besoin lors de mes premières idées de programmes wifi ou de portages d'applications sans fil Linux vers NetBSD (comme toi quand tu as répété "L'été indien" devant ton miroir, imagine si tu avais pu avoir Joe Dassin à tes côtés). Nous allons tenter d'être pragmatique dans notre approche (oui, pour apprendre à danser, il faut danser, mais ça tu le sais déjà) : montrer concrètement comment effectuer les opérations de base et indiquer où se renseigner sur d'autres opérations que tu aimerais réaliser. Cet article n'expliquera pas le fonctionnement du protocole 802.11 et ses variantes ; le lecteur possède déjà les concepts de programmation de base en C, une compréhension générale des réseaux wireless, ainsi qu'une bonne connaissance de la variété française (Pouuurr le plAaAAAaiiiSIiiiiiIrrrr). De plus, le code ne se veut pas exhaustif, ni "secure", ni audité, ni même utilisable dans le cadre d'une application destinée à tourner en production. C'est une simple illustration de la manipulation de cette API.

1. Wireless : le standard

Je ne vais pas rentrer dans les détails, Wikipédia en parlera mieux que moi : En gros, c'est génial, plus de fils, plus de trucs qui traînent (pas comme chez moi), etc.

2. Modes d'opération courants

Voici la liste des modes dans lesquels une carte wifi peut se trouver :
  • ibss (independant bss or infrastructure bss), connu aussi sous d'autres termes : managed, client, adhoc, etc. ;
  • hostap : la carte se comporte comme un point d'accès (access point) au réseau wireless ;
  • monitor : la carte récupère passivement tous les paquets wireless qu'elle observe ou qui lui passent sous le nez.
C'est vite résumé et simplifié, on est d'accord.

3. NetBSD et le 802.11

Le système d'exploitation NetBSD possède une couche d'abstraction permettant le contrôle de différentes cartes wireless, indépendamment du fabricant. La majorité de cette abstraction est présente dans le noyau et chaque driver wireless propose une partie ou le support complet des différents appels de cette API. Le schéma 1 très simplifié montre son intégration au sein du système. La majorité des drivers récents en font un usage quasi complet, mais certains drivers ne l'utilisent pas complètement. Ils fournissent leurs propres méthodes pour certaines opérations comme configurer la clef WEP (par exemple dans le driver an(4)). Cette API a été écrite par Atsushi Onoe, puis reprise et maintenue par Sam Leffler. FreeBSD et NetBSD travaillent de concert (avec un lead très FreeBSDien) pour la maintenir et la faire évoluer. La personne responsable pour NetBSD est David Young. Résumons donc ! En gros, ça facilite le code, ça rend les choses plus lisibles et plus simples à maintenir et évidemment "small is beautiful", tout ça, tout ça, tout ça... De plus, c'est plus simple à gérer que 36 appels pour chaque driver qui devrait alors implémenter ses propres ioctls, etc. Pour en faire usage ? Simple, il suffit d'utiliser les includes appropriés qui se situent dans le répertoire usr/include/net80211/. Ils définissent aussi bien les différentes options pour demander et configurer les paramètres du driver, que les structures et des macros permettant de créer

/img-articles/lmhs/29/cc-art-wifi/fig-1.jpg

un parseur de paquets 802.11 assez facilement. Étant utilisés par le noyau lui-même pour gérer les paquets arrivant sur la carte, ils sont parfaitement adaptés si on désire gérer le parsing de données à partir de paquets bruts arrivant directement du noyau vers l'espace utilisateur (en monitor mode avec pcap par exemple). Dans les exemples qui suivent, nous aurons uniquement besoin des includes suivants :
  • <net80211/ieee80211.h> contient les structures des différentes trames wireless, des valeurs et quelques macros ;
  • <net80211/ieee80211_ioctl.h> contient les ioctls courants pour l'API ;
  • <net80211/ieee80211_radiotap.h> contient la structure du header RadioTap et les différents champs, mais nous en reparlons dans l'exemple n°5.
Afin d'accéder aux informations des interfaces ou de les configurer, il nous faudra également utiliser les includes relatifs à la gestion des interfaces réseau. Dans les exemples qui suivent, nous utiliserons uniquement les includes suivants :
  • <net/if.h>
  • <net/if_media.h>
Si d'autres informations sont nécessaires, il est très facile d'aller jeter un coup d’œil au code source de l'outil ifconfig et de regarder comment celui-ci s'y prend pour les obtenir ou les modifier. Passons à la pratique. Nous allons passer en revue quelques opérations simples et introduire la manière d'effectuer ces opérations sous NetBSD. Cet article a été écrit avec la version 4.0_BETA2, une carte Intel 3915ABG et une carte Atheros, ainsi qu'une carte Aironet.

4. Exemple n°1 : Activer/Désactiver une interface wireless

Cette opération n'a rien de spécifique au wifi. Elle est commune à toute interface. Il suffit de remplir les membres ifr_name et ifr_flags de la struct ifreq (<net/if.h>). Le flag d'activation d'une interface est IFF_UP.
/*
 * Interface request structure used for socket
 * ioctl's.  All interface ioctl's must have parameter
 * definitions which begin with ifr_name.  The
 * remainder may be interface specific.
 */
struct  ifreq {
  char    ifr_name[IFNAMSIZ]; /* if name, e.g. "en0" */
  union {
    struct  sockaddr ifru_addr;
    struct  sockaddr ifru_dstaddr;
    struct  sockaddr ifru_broadaddr;
    short   ifru_flags;
    int     ifru_metric;
    int     ifru_mtu;
    int     ifru_dlt;
    u_int   ifru_value;
    caddr_t ifru_data;
    struct {
      uint32_t         b_buflen;
      void            *b_buf;
    } ifru_b;
  } ifr_ifru;
/* address */
#define ifr_addr        ifr_ifru.ifru_addr
/* other end of p-to-p link */
#define ifr_dstaddr     ifr_ifru.ifru_dstaddr
/* broadcast address */
#define ifr_broadaddr   ifr_ifru.ifru_broadaddr
/* flags */
#define ifr_flags       ifr_ifru.ifru_flags
/* metric */
#define ifr_metric      ifr_ifru.ifru_metric
/* mtu */
#define ifr_mtu         ifr_ifru.ifru_mtu
/* data link type (DLT_*) */
#define ifr_dlt         ifr_ifru.ifru_dlt
/* generic value */
#define ifr_value       ifr_ifru.ifru_value
/* media options (overload) */
#define ifr_media       ifr_ifru.ifru_metric
/* for use by interface XXX deprecated */
#define ifr_data        ifr_ifru.ifru_data
/* new interface ioctls */
#define ifr_buf         ifr_ifru.ifru_b.b_buf
#define ifr_buflen      ifr_ifru.ifru_b.b_buflen
};
Et voici le code d'exemple wifi_switch.c :
/*
 *  code d'exemple sous license BSD
 *  Eric Auge <eau@gcu-squad.org>
 *
 *  pour compiler :
 *  gcc wifi_switch.c -o wifi_switch
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <sys/ioctl.h>

#include <net/if.h>
#include <net/if_media.h>

#include <net80211/ieee80211.h>
#include <net80211/ieee80211_ioctl.h>

#define IFACE_NAME "wpi0"

int main(int argc, char ** argv)
{
  struct ifreq ifr;
  char * interface = IFACE_NAME;
  int s;

  if (!argv[1])
  {
    printf("no argument\n");
    printf("\n%s <on|off>\n", argv[0]);
    exit(1);
  }

  s = socket(AF_INET, SOCK_DGRAM, 0);
  if  (s <0)
  {
    perror("socket()");
    return -1;
  }

  strlcpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));

  if (strcmp(argv[1], "on") == 0)
  {
    printf("activation de %s\n", interface);
    ifr.ifr_flags |= IFF_UP;
    if (ioctl(s, SIOCSIFFLAGS, (caddr_t)&ifr) == -1)
    {
      perror("ioctl()");
      return -1;
    }
  } else if (strcmp(argv[1], "off") == 0)
  {
    printf("desactivation de %s\n", interface);
    ifr.ifr_flags &= (~IFF_UP);
    if (ioctl(s, SIOCSIFFLAGS, (caddr_t)&ifr) == -1)
    {
      perror("ioctl()");
      return -1;
    }
  } else
  {
    printf("option inconnue\n");
  }
  return 0;
}
A l'exécution :
eau@kamehouse:~/lm $ sudo ./wifi_switch off
desactivation de wpi0
Résultat ifconfig :
[...]
wpi0: flags=8802<BROADCAST,SIMPLEX,MULTICAST> mtu 1500
[...]
On rallume :
eau@kamehouse:~/lm $ sudo ./wifi_switch on
activation de wpi0
Résultat ifconfig :
[...]
wpi0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
[...]
Il n'est pas nécessaire de préciser le média, sachant que, par défaut, le comportement est l'auto-sélection du média et du mode d'opération (OFDM24, OFDM36, 11b, 11g, etc.). Le mode d'opération par défaut est en infrastructure (IBSS). L'ioctl(2) pour obtenir l'état de l'interface est SIOCGIFFLAGS. Il suffit alors de vérifier si le flag IFF_UP est actif dans le membre ifr_flags de la structure ifreq. Requêtes ioctls :
  • SIOCGIFFLAGS : récupérer les flags pour l'interface ;
  • SIOCSIFFLAGS : configurer les flags pour l'interface
Maintenant, on est capable d'allumer et d'éteindre une interface : tu dois déjà te sentir un peu plus proche de tes chanteurs préférés.

5. Exemple n°2 : L'association

C'est très simple, le driver se charge de toute la négociation et des échanges. Côté userland, il suffit de proposer à la carte un SSID (Service Set ID) via l'ioctl(2) SIOCS80211NWID de la couche net80211 et une clef WEP via l'ioctl(2) SIOCS80211NWKEY. Nous n'aborderons pas WPA dans cet article, qui se veut une introduction. Pour configurer le SSID, il vous suffit de :
  1. Remplir la structure ieee80211_nwid définie dans le fichier <net80211/ieee80211_ioctl.h> ;
  2. L'envoyer au driver via l'ioctl SIOCS80211NWID.
Il faut faire de même pour la(les) clef(s) WEP, ce qui nécessitera de remplir une structure ieee80211_nwkey (également définie dans <net80211/ieee80211_ioctl.h>), puis d'envoyer ces informations au driver via l'ioctl SIOCS80211NWKEY. Bon nombre de ces structures attendent qu'un champ contienne le nom de l'interface pour déterminer à quel driver envoyer les informations et les ordres. C'était le cas avec le champ ifr_name de la structure ifreq vue précédemment. Voici un petit exemple wifi_assoc.c, afin d'illustrer la configuration d'un SSID et d'une clef WEP sur une interface nommée wpi0 :
 /*
 *  code d'exemple sous license BSD
 *  Eric Auge <eau@gcu-squad.org>
 *
 *  pour compiler :
 *  gcc wifi_assoc.c -o wifi_assoc
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include <sys/ioctl.h>

#include <net/if.h>
#include <net/if_media.h>

#include <net80211/ieee80211.h>
#include <net80211/ieee80211_ioctl.h>

#define IFACE_NAME "wpi0"

int main(int argc, char ** argv)
{
  struct ifreq ifr;
  /* structure definissant le SSID */
  struct ieee80211_nwid nwid;
  /* structure definissant les clefs WEP */
  struct ieee80211_nwkey nwkey;
  /* interface recevant la configuration */
  char * interface = IFACE_NAME;
  /* structure contenant une clef WEP */
  unsigned char keybuf[16];
  int s;

  if (!argv[1])
  {
    printf("no argument\n");
    printf("\n%s <ssid> [<wepkey>]\n", argv[0]);
    exit(1);
  }

  s = socket(AF_INET, SOCK_DGRAM, 0);
  if  (s <0)
  {
    perror("socket()");
    return -1;
  }

  /* Etre propre sur soi ! */
  memset(&nwid, 0, sizeof(struct ieee80211_nwid));
  memset(&nwkey, 0, sizeof(struct ieee80211_nwkey));

  strlcpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));
  /* Un ptit sanity check a la noix. */
  if (strlen(argv[1]) > IEEE80211_NWID_LEN) {
    printf("ssid too long!\n");
    exit(1);
  }

  /* Copier le ssid dans la structure associee. */
  strlcpy(nwid.i_nwid, argv[1], sizeof(nwid.i_nwid));
  nwid.i_len = strlen(argv[1]);

  /* ifrequest */
  ifr.ifr_data = (void *)&nwid;

  if (ioctl(s, SIOCS80211NWID, (caddr_t) &ifr) == -1)
  {
    perror("ioctl(SIOCS80211NWID)");
    return -1;
  }

  printf("ssid set to '%s'\n", argv[1]);

  if (argv[2])
  {
    if (strlen(argv[2]) > sizeof(keybuf))
    {
      printf("key too long\n");
      exit(1);
    }
    /* Copier le nom de l'interface a configurer. */
    strlcpy(nwkey.i_name, interface, sizeof(nwkey.i_name));
    memcpy(keybuf, argv[2], sizeof(keybuf));

    nwkey.i_wepon = IEEE80211_NWKEY_WEP;
    nwkey.i_defkid = 1;
    nwkey.i_key[0].i_keylen = sizeof(keybuf);
    nwkey.i_key[0].i_keydat = keybuf;

    /* Pas d'autres clefs, juste une seule */
    nwkey.i_key[1].i_keylen = 0;
    nwkey.i_key[2].i_keylen = 0;
    nwkey.i_key[3].i_keylen = 0;
    if (ioctl(s, SIOCS80211NWKEY, &nwkey) == -1)
    {
      perror("ioctl(SIOCS80211NWKEY)");
      return -1;
    }
    printf("wep key #0 is set to '%s'\n", argv[2]);
  }

  return 0;
}
A l'exécution, nous obtenons les résultats suivants : Sans clef WEP :
eau@kamehouse:~/lm $ sudo ./wifi_assoc testor
ssid set to 'testor'
Vérifions avec ifconfig :
eau@kamehouse:~/lm $ ifconfig wpi0
wpi0: flags=e843<UP,BROADCAST,RUNNING,SIMPLEX,LINK1,LINK2,MULTICAST>
mtu 1500
ssid testor
powersave off
address: 00:01:02:03:04:05
media: IEEE802.11 autoselect (DS1 mode 11g)[...]
Avec clef WEP :
eau@kamehouse:~/lm $ sudo ./wifi_assoc testor proutproutproutp
ssid set to 'testor'
wep key #0 is set to 'proutproutproutp'
Vérifions avec ifconfig :
eau@kamehouse:~/lm $ sudo ifconfig wpi0
wpi0: flags=8802<BROADCAST,SIMPLEX,MULTICAST>
        mtu 1500
        ssid testor nwkey proutproutproutp
        powersave off
        address: 00:01:02:03:04:07
        media: IEEE802.11 autoselect (autoselect mode 11g)
[...]
Dans notre cas, le driver wpi(4) va maintenant se charger de faire la sale besogne, c'est-à-dire l'association avec le réseau sans fil. La partie userland se résume donc à ça. Simple, non ? :) Vous pouvez bien entendu récupérer ces informations via les requêtes G (Get) ; les requêtes S (Set) étant présentes pour configurer ("setter") un des paramètres (cf. ioctl(2)). Requêtes ioctl :
  • SIOCG80211NWID : récupérer la valeur du ssid courant
  • SIOCS80211NWID : configurer la valeur du ssid courant
  • SIOCG80211NWKEY : récupérer la valeur de(s) la clef(s) WEP
  • SIOCS80211NWKEY : configurer la valeur de(s) clef(s) WEP
Ces requêtes ioctl(2) et d'autres sont décrites dans le fichier <net80211/ieee80211_ioctl.h>. On y trouve également les structures associées aux requêtes.

6. Exemple n°3 : Scanner activement

Vous pouvez demander à votre driver, par conséquent à votre carte, de "balayer" l'environnement afin de trouver d'autres points d'accès dans son périmètre. La requête se fait au travers de deux ioctl(2) 802.11 génériques :
  • SIOCS80211 : configurer des paramètres 802.11 ;
  • SIOCG80211 : récupérer la valeur des paramètres 802.11.
La structure associée à ces requêtes est ieee80211req définie dans le fichier <net80211/ieee80211_ioctl.h> (eh oui, encore lui !) :
struct ieee80211req {
  char       i_name[IFNAMSIZ];  /* if_name, e.g. "wi0" */
  u_int16_t  i_type;            /* req type */
  int16_t    i_val;             /* Index or simple value */
  int16_t    i_len;             /* Index or simple value */
  void      *i_data;            /* Extra data */
};
Elle définit un champ i_type qui permet de choisir une sous-requête (spécifique aux opérations wireless) à appeler. Voici un exemple de sous-requêtes définies dans le fichier <net80211/ieee80211_ioctl.h> :
 /* driver capabilities */
#define IEEE80211_IOC_DRIVER_CAPS       36
/* key management algorithms */
#define IEEE80211_IOC_KEYMGTALGS        37
/* RSN capabilities */
#define IEEE80211_IOC_RSNCAPS           38
/* WPA information element */
#define IEEE80211_IOC_WPAIE             39
/* per-station statistics */
#define IEEE80211_IOC_STA_STATS         40
/* MAC ACL operation */
#define IEEE80211_IOC_MACCMD            41
/* channel info list */
#define IEEE80211_IOC_CHANINFO          42
/* max tx power for channel */
#define IEEE80211_IOC_TXPOWMAX          43
/* per-station tx power limit */
#define IEEE80211_IOC_STA_TXPOW         44
/* station/neighbor info */
#define IEEE80211_IOC_STA_INFO          45
[...]
/* beacon interval (ms) */
#define IEEE80211_IOC_BEACON_INTERVAL   53
/* add sta to MAC ACL table */
#define IEEE80211_IOC_ADDMAC            54
/* del sta from MAC ACL table */
#define IEEE80211_IOC_DELMAC            55
Dans le cas du scan, les sous-requêtes nécessaires pour effectuer cette opération sont les suivantes :
  • IEEE80211_IOC_SCAN_REQ
  • IEEE80211_IOC_SCAN_RESULTS
L'application n'a absolument rien à faire, si ce n'est envoyer la demande au driver via ces appels. Le driver se chargera encore et toujours de la sale besogne : il enverra alors des probe requests, afin d'obtenir une réponse des points d'accès environnants. Lors d'une telle requête, le driver va simplement empiler les résultats en utilisant la structure suivante :
 /*
 * Scan result data returned for IEEE80211_IOC_SCAN_RESULTS.
 */
struct ieee80211req_scan_result {
  u_int16_t  isr_len;           /* length (mult of 4) */
  u_int16_t  isr_freq;          /* MHz */
  u_int16_t  isr_flags;         /* channel flags */
  u_int8_t   isr_noise;
  u_int8_t   isr_rssi;
  u_int8_t   isr_intval;        /* beacon interval */
  u_int8_t   isr_capinfo;       /* capabilities */
  u_int8_t   isr_erp;           /* ERP element */
  u_int8_t   isr_bssid[IEEE80211_ADDR_LEN];
  u_int8_t   isr_nrates;
  u_int8_t   isr_rates[IEEE80211_RATE_MAXSIZE];
  u_int8_t   isr_ssid_len;      /* SSID length */
  u_int8_t   isr_ie_len;        /* IE length */
  u_int8_t   isr_pad[5];
  /* variable length SSID followed by IE data */
};
Les résultats sont écrits les uns derrière les autres dans le buffer que vous aurez fourni (dans le champ i_data) lors de la demande des résultats. Le buffer, une fois rempli, devrait ressembler à ça :

/img-articles/lmhs/29/cc-art-wifi/fig-2.jpg

et ainsi de suite. Lors de la demande des résultats, la taille effective des données écrasera la taille initiale du buffer dans le champ i_len. Rien de mieux qu'un petit exemple avec wifi_scan.c :
/*
 *  code d'exemple sous license BSD
 *  Eric Auge <eau@gcu-squad.org>
 *
 *  pour compiler :
 *  gcc wifi_scan.c -o wifi_scan
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include <sys/ioctl.h>

#include <net/if.h>
#include <net/if_media.h>

#include <net80211/ieee80211.h>
#include <net80211/ieee80211_ioctl.h>

#define IFACE_NAME "wpi0"

int main(int argc, char ** argv)
{
  struct ifreq ifr;
  char * interface = IFACE_NAME;
  int s, i;
  struct ieee80211req req;
  char buf[24*1024];
  char ssid[64];
  struct ieee80211req_scan_result * results;

  s = socket(AF_INET, SOCK_DGRAM, 0);
  if  (s <0)
  {
    perror("socket()");
    return -1;
  }

  memset(&req, 0, sizeof(struct ieee80211req));
  memset(buf, 0, sizeof(buf));
  memset(ssid, 0, sizeof(ssid)); 

  /* on demande à la carte de scanner les
     access points environnants */
  strlcpy(req.i_name, interface, sizeof(req.i_name));
  req.i_type = IEEE80211_IOC_SCAN_REQ;
  req.i_val = 0;

  if (ioctl(s, SIOCS80211, &req) < 0)
  {
    perror("ioctl(SIOCS80211)");
    return -1;
  }

  /* on attend un ptit peu, c'est pas joli je
     sais mais on le fait au plus simple pour
     le moment :)  */
  sleep(10);

  /* on demande au driver le résultat du scan */
  memset(&req, 0, sizeof(struct ieee80211req));
  strlcpy(req.i_name, interface, sizeof(req.i_name));
  req.i_type = IEEE80211_IOC_SCAN_RESULTS;
  req.i_len = sizeof(buf);
  req.i_data = &buf;
  if (ioctl(s, SIOCG80211, &req) < 0)
  {
    perror("ioctl(SIOCG80211)");
    return -1;
  }

  printf("taille des resultats: %d octets\n", req.i_len );
  printf("taille d'un resultat: %d\n",
    sizeof(struct ieee80211req_scan_result));  

  /* observons le premier resultat */
  if (req.i_len > 0)
  {
    results = (struct ieee80211req_scan_result *) buf;

    printf("taille du resultat: %d taille du SSID: %d"
      " taille de l'element d'information: %d\n",
        results->isr_len,
        results->isr_ssid_len,
        results->isr_ie_len);
    printf("BSSID: ");
    for ( i = 0 ; i < IEEE80211_ADDR_LEN ; i++)
      printf("%02x ",  results->isr_bssid[i]);
    printf("\n");
    if (results->isr_ssid_len > 0)
    {
      memcpy(ssid, &buf[
        sizeof(struct ieee80211req_scan_result)
        ], results->isr_ssid_len);
      printf("SSID: %s\n", ssid);
    }
  }
  return 0;
}
A l'exécution, nous obtenons les résultats suivants :
root@kamehouse:~/lm # ./wifi_scan
taille des resultats: 48 octets
taille d'un resultat: 40
taille du résultat: 48 taille du SSID:
 8 taille de l'element d'information: 0
BSSID: 00 03 52 ef 72 60
SSID: swisscom
Le point d'accès près de moi au moment du scan est celui auquel j'étais attaché précédemment :
eau@kamehouse:~/lm # ifconfig wpi0
wpi0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST>
        mtu 1500
        ssid swisscom
        powersave off
        bssid 00:03:52:ef:72:60 chan 1
Le BSSID du scan et celui du point d'accès sont identiques. Notez que l'API net80211 est en mouvement ! Des changements sont encore à venir concernant cette interface qui permet aux applications utilisateurs de demander un scan. Il se peut donc que les appels actuels soient déjà obsolètes ou en chantier. De plus, ils ne semblent pas complètement fonctionnels selon les cartes utilisées. Auparavant, des outils comme wimon ou wiconfig utilisaient l'ioctl SIOCGWAVELAN, ainsi que les sous-requêtes associées WI_RID_SCAN_APS et WI_RID_READ_APS.

7. Exemple n°4 : Activer et utiliser le monitoring aka RF_MON

Une grande majorité des cartes réseau sans fil proposent la capacité d'écouter passivement tout le trafic. Ce mode d'opération est souvent appelé "monitor mode" ou RFMON. Il est équivalent au mode "promiscuous" des réseaux filaires. Ce mode est celui utilisé pour la découverte de réseaux ou wardriving par des logiciels comme kismet, airsnort ou wireshark. Regardons comment nous pouvons utiliser ce mode d'opération et l'activer sur notre carte. L'outil ifconfig nous propose de voir et/ou configurer le mode d'opération de la carte.
 wpi0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST>
        mtu 1500
        ssid default
        powersave off
        bssid 00:90:9c:00:0d:2e chan 1
        address: 00:01:02:03:04:11
        media: IEEE802.11 autoselect (OFDM18 mode 11g)
        status: active
La ligne qui nous intéresse est la ligne media, la carte fonctionne en 802.11 en auto-sélection (fréquence, mode, etc.), et aucune option ne semble être présente concernant ce mode de fonctionnement. Pour altérer ces informations concernant l'interface wireless, il suffit de s'y prendre comme à l'exemple n°1. On va passer par notre bonne vieille structure ifreq et on ne va pas altérer les flags, mais le média maintenant. C'est logiquement que je vais mettre les doigts sur le champ ifr_media (hmm, je touche le dernier 33 tours de Claude François, hmmm Cloclo, hmmmMmmMMmmmmm...) et voici les deux flags qui m'intéressent :
  • IFM_IEEE80211
  • IFM_IEEE80211_MONITOR
Allez hop, c'est parti, c'est bien plus parlant avec un exemple. Nous allons éteindre l'interface, activer le mode monitoring, rallumer l'interface et récupérer des données dessus. Pour simplifier, j'ai décidé d'utiliser la libpcap. Elle est simple d'utilisation, nécessite peu de lignes et elle m'est assez familière. Le petit exemple avec wifi_mon.c :
 /*
 *  code d'exemple sous license BSD
 *  Eric Auge <eau@gcu-squad.org>
 *
 *  pour compiler :
 *  gcc wifi_mon.c -o wifi_mon -lpcap
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h> 

/* pcap to the rescue, je suis trop feignant pour
   utiliser autre chose...genre bpf etc.. */
#include <pcap.h>

#include <sys/ioctl.h>
#include <net/if.h>
#include <net/if_media.h>

#include <net80211/ieee80211.h>
#include <net80211/ieee80211_ioctl.h>

#define IFACE_NAME "wpi0"

/*
 * un exemple de _début_ de décodage de paquets
 * c'est un exemple hein !
 */
void ieee80211_decode(u_char * packet, size_t packet_size)
{
  struct ieee80211_frame*i=(struct ieee80211_frame *)packet;
  unsigned char fcs[4];
  int rc;

  printf("Paquet recu (%d bytes) >> ", packet_size);
  if (!i)
    return;

  if (i->i_fc[1] & IEEE80211_FC1_MORE_FRAG)
    printf("IEEE80211_FC1_MORE_FRAG ");

  switch (i->i_fc[0] & IEEE80211_FC0_TYPE_MASK)
  {
    case IEEE80211_FC0_TYPE_MGT:
      printf("IEEE80211 MANAGEMENT FRAME ");
      /*
       rc=ieee80211_decode_mgmt(&ctx,packet,packet_size);
       */
      break;
    case IEEE80211_FC0_TYPE_CTL:
      printf("IEEE80211 CONTROL FRAME ");
      /*
       rc=ieee80211_decode_ctl(&ctx,packet,packet_size);
       */
      break;
    case IEEE80211_FC0_TYPE_DATA:
      printf("IEEE80211 DATA FRAME ");
      /*
       rc=ieee80211_decode_data(&ctx,packet,packet_size);
       */
      break;
    default:
      printf("IEEE80211 UNKNOWN FRAME");
      /*
       rc=ieee80211_decode_unknown(&ctx,packet,packet_size);
       */
      break;
  }
  printf("\n");
}

/*
 * la jolie routine (callback) qui s'occupe de
 * traiter chaque paquet reçu,
 * merci pcap pour ce pain d'epice tout prêt !
 */
void pkt_handler(u_char * dump,
  const struct pcap_pkthdr * phdr, const u_char * pkt)
{
  ieee80211_decode((u_char *)pkt, phdr->caplen);
  return;
}
int main(int argc, char ** argv)
{
  struct ifreq ifr;
  int s;
  /* c'est parti! */
  pcap_t * p;
  char error[2048];
  int rc, i;
  int *dlt;
  int ndlt;

  if (!argv[1])
  {
    printf("no argument\n");
    printf("\n%s <iface>\n", argv[0]);
    exit(1);
  }

  /* il nous faut un file descriptor ouvert oui ! */
  s = socket(AF_INET, SOCK_DGRAM, 0);
  if  (s <0)
  {
    perror("socket()");
    return -1;
  }

  /* nom de l'interface */
  strlcpy(ifr.ifr_name, argv[1], sizeof(ifr.ifr_name));

  /* on switch l'interface down */
  ifr.ifr_flags &= (~IFF_UP);
  if (ioctl(s, SIOCSIFFLAGS, (caddr_t) &ifr) == -1)
  {
    perror("ioctl(SIOCSIFFLAGS:down)");
    return -1;
  }

  /*
   *  on change le options concernant le media,
   *  hola hup barbatruc, carte
   *  en monitor s'il vous plait monsieur le kernel !
   */
  ifr.ifr_media = IFM_IEEE80211|IFM_IEEE80211_MONITOR;
  if (ioctl(s, SIOCSIFMEDIA, (caddr_t)&ifr) == -1)
  {
    perror("ioctl(SIOCSIFMEDIA)");
    return -1;
  }

  /* on re switch l'interface up ! */
  ifr.ifr_flags |= IFF_UP;
  if (ioctl(s, SIOCSIFFLAGS, (caddr_t) &ifr) == -1)
  {
    perror("ioctl(SIOCSIFFLAGS:up)");
    return -1;
  }

  /* ouvrir l'interface pcap ! */
  p = pcap_open_live(argv[1], 1500, 1, 1000, error);
  if (!p)
  {
    perror("pcap_open_live()");
    printf("error: %s\n", error);
  }

  /* qui sommes nous ? */
  ndlt = pcap_list_datalinks(p, &dlt);
  /* que sommes nous capables de supporter ! */
  printf("# dlt: %d\n", ndlt);
  for ( i = 0 ; i < ndlt ; i++) {
    switch (*(dlt+i)) {
      case DLT_IEEE802_11:
        printf("STD IEEE802_11 est supporte!\n");
        break;
      case DLT_PRISM_HEADER:
        printf("PRISM est supporte!\n");
        break;
      case DLT_AIRONET_HEADER:
        printf("HEADER AIRONET supporte!\n");
        break;
    }
  }

  /* on configure et on boucle */
  pcap_set_datalink(p, DLT_IEEE802_11);
  pcap_loop(p, -1, pkt_handler, NULL);

  return 0;
}
A l'exécution :
root@kamehouse:~/lm # ./wifi_mon wpi0
# dlt: 3
STD IEEE802_11 est supporte!
Paquet recu (81 bytes) >> IEEE80211 MANAGEMENT FRAME
Paquet recu (81 bytes) >> IEEE80211 MANAGEMENT FRAME
Paquet recu (81 bytes) >> IEEE80211 MANAGEMENT FRAME
Paquet recu (42 bytes) >> IEEE80211 MANAGEMENT FRAME
Paquet recu (81 bytes) >> IEEE80211 MANAGEMENT FRAME
Paquet recu (81 bytes) >> IEEE80211 MANAGEMENT FRAME
Paquet recu (42 bytes) >> IEEE80211 MANAGEMENT FRAME
Paquet recu (81 bytes) >> IEEE80211 MANAGEMENT FRAME
Paquet recu (81 bytes) >> IEEE80211 MANAGEMENT FRAME
Paquet recu (42 bytes) >> IEEE80211 MANAGEMENT FRAME
Paquet recu (81 bytes) >> IEEE80211 MANAGEMENT FRAME
Youpi, nous voilà donc avec quelque chose qui reçoit les paquets directement depuis le kernel sans modification, à la manière dont le font Kismet et Cie. J'ai inclus un micro-exemple de routine de décodage, histoire de donner des idées :)

8. Exemple n°5 : RadioTap

RadioTap, initialement développé sur NetBSD, fournit une couche de description du signal commune à tous les drivers wireless. Voilà la structure du paquet avec RadioTap activé :

/img-articles/lmhs/29/cc-art-wifi/fig-3.jpg

Ce header transporte une variété d'informations liées à chaque trame reçue comme :
  • la qualité du signal ;
  • la qualité de la réception ;
  • la présence ou non du FCS (Frame Check Sequence, une somme de vérification de l'intégrité du paquet).
Le header RadioTap se trouve dans le fichier <net80211/ieee80211_radiotap.h> et se présente de la manière suivante :
/* XXX tcpdump/libpcap do not tolerate
 * variable-length headers, yet, so we pad every
 * radiotap header to 64 bytes. Ugh.
 */
#define IEEE80211_RADIOTAP_HDRLEN       64

/*
 * The radio capture header precedes the 802.11 header.
 *
 * Note well: all radiotap fields are little-endian.
 */
struct ieee80211_radiotap_header {
/* Version 0. Only increases for drastic changes,
   introduction of compatible new fields does not count. */
u_int8_t        it_version;
u_int8_t        it_pad;

/* length of the whole header in bytes, including
   it_version, it_pad, it_len, and data fields.  */
u_int16_t       it_len;

/* A bitmap telling which
* fields are present. Set bit 31
* (0x80000000) to extend the
* bitmap by another 32 bits.
* Additional extensions are made
* by setting bit 31. */
u_int32_t       it_present;
} __attribute__((__packed__));
Les différents champs possibles sont décrits quelques lignes en dessous dans le même fichier, ainsi que dans la page de manuel "ieee80211_radiotap(9)". Le champ it_present vous indique quelles sont les informations que le driver vous remonte dans le paquet reçu. Il suffit donc de parser ce champ pour savoir exactement quelles informations nous pouvons obtenir du driver en temps réel et pour chaque trame. En exemple, nous allons prendre le programme précédent et rajouter le support RadioTap, en vous laissant, pour le fun, la joie d'écrire un parser approprié pour récupérer les informations dont vous avez besoin dans ce header :) Le header RadioTap à parser se trouve dans le code d'exemple wifi_radiotap.c. Il est nommé r dans la fonction ieee80211_decode :
/*
 *  code d'exemple sous license BSD
 *  Eric Auge <eau@gcu-squad.org>
 *
 *  pour compiler :
 *  gcc wifi_radiotap.c -o wifi_radiotap -lpcap
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pcap.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/if_media.h>
#include <net80211/ieee80211.h>
#include <net80211/ieee80211_ioctl.h>
#include <net80211/ieee80211_radiotap.h>

#define IFACE_NAME "wpi0"

/* AVEC RADIOTAP CETTE FOIS ! */
void ieee80211_decode(u_char * packet, size_t packet_size)
{
  struct ieee80211_radiotap_header * r =
    (struct ieee80211_radiotap_header *) packet;
  struct ieee80211_frame * i =
    (struct ieee80211_frame *)
    ((unsigned char*)packet+IEEE80211_RADIOTAP_HDRLEN);
  unsigned char fcs[4];
  int rc;

  printf("Paquet recu (%d bytes) >> ", packet_size);

  if ((!i) || (packet_size < (sizeof(struct ieee80211_frame)
        +IEEE80211_RADIOTAP_HDRLEN)))
    return;

  if (i->i_fc[1] & IEEE80211_FC1_MORE_FRAG)
    printf("IEEE80211_FC1_MORE_FRAG ");

  switch (i->i_fc[0] & IEEE80211_FC0_TYPE_MASK)
  {
    case IEEE80211_FC0_TYPE_MGT:
      printf("IEEE80211 MANAGEMENT FRAME");
      break;
    case IEEE80211_FC0_TYPE_CTL:
      printf("IEEE80211 CONTROL FRAME");
      break;
    case IEEE80211_FC0_TYPE_DATA:
      printf("IEEE80211 DATA FRAME");
      break;
    default:
      printf("IEEE80211 UNKNOWN FRAME");
      break;
  }

  printf("\n");
}  

void pkt_handler(u_char * dump,
  const struct pcap_pkthdr * phdr,
  const u_char * pkt)
{
  ieee80211_decode((u_char *)pkt, phdr->caplen);
  return;
}

int main(int argc, char ** argv)
{
  struct ifreq ifr;
  char * interface = IFACE_NAME;
  int s;
  /* c'est parti! */
  pcap_t * p;
  char error[2048];
  int rc, i;
  int *dlt;
  int ndlt;

  if (!argv[1])
  {
    printf("no argument\n");
    printf("\n%s <iface>\n", argv[0]);
    exit(1);
  }

  /* il nous faut un file descriptor ouvert oui ! */
  s = socket(AF_INET, SOCK_DGRAM, 0);
  if  (s <0)
  {
    perror("socket()");
    return -1;
  }

  /* nom de l'interface */
  strlcpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));

  /* on switch l'interface down */
  ifr.ifr_flags &= (~IFF_UP);
  if (ioctl(s, SIOCSIFFLAGS, (caddr_t) &ifr) == -1)
  {
    perror("ioctl(SIOCSIFFLAGS:down)");
    return -1;
  }

  ifr.ifr_media = IFM_IEEE80211|IFM_IEEE80211_MONITOR;
  if (ioctl(s, SIOCSIFMEDIA, (caddr_t)&ifr) == -1)
  {
    perror("ioctl(SIOCSIFMEDIA)");
    return -1;
  }  

  /* on re switch l'interface up ! */
  ifr.ifr_flags |= IFF_UP;
  if (ioctl(s, SIOCSIFFLAGS, (caddr_t) &ifr) == -1)
  {
    perror("ioctl(SIOCSIFFLAGS:up)");
    return -1;
  } 

  /* ouvrir l'interface pcap ! */
  p = pcap_open_live(argv[1], 1500, 1, 1000, error);
  if (!p)
  {
    perror("pcap_open_live()");
    printf("error: %s\n", error);
  }

  /* qui sommes nous ? */
  ndlt = pcap_list_datalinks(p, &dlt);

  /* que sommes nous capables de supporter ! */
  printf("# dlt: %d\n", ndlt);
  for ( i = 0 ; i < ndlt ; i++) {
    switch (*(dlt+i)) {
      case DLT_IEEE802_11:
        printf("STD IEEE802_11 est supporte!\n");
        break;
      case DLT_PRISM_HEADER:
        printf("PRISM est supporte!\n");
        break;
      case DLT_AIRONET_HEADER:
        printf("HEADER AIRONET supporte!\n");
        break;
      case DLT_IEEE802_11_RADIO:
        printf("IEEE802_11_RADIO (RADIOTAP) est supporte\n");
        break;
    }
  }

  /* on configure et on boucle */
  pcap_set_datalink(p, DLT_IEEE802_11_RADIO);
  pcap_loop(p, -1, pkt_handler, NULL);
  return 0;
}
A l'exécution, peu de changements :)
root@kamehouse:~/lm # ./wifi_radiotap wpi0
# dlt: 3
IEEE802_11_RADIO (RADIOTAP) est supporte
STD IEEE802_11 est supporte!
Paquet recu (145 bytes) >> IEEE80211 MANAGEMENT FRAME
Paquet recu (145 bytes) >> IEEE80211 MANAGEMENT FRAME
Paquet recu (145 bytes) >> IEEE80211 MANAGEMENT FRAME
Paquet recu (145 bytes) >> IEEE80211 MANAGEMENT FRAME
Paquet recu (145 bytes) >> IEEE80211 MANAGEMENT FRAME
Paquet recu (145 bytes) >> IEEE80211 MANAGEMENT FRAME
Et voilà !

Conclusion

Certaines choses n'ont pas été abordées dans cet article, comme l'envoi de paquets 802.11 depuis les applications utilisateurs ou simplement la gestion de WPA, la gestion de l'énergie, les statistiques, la mesure du signal (même si RadioTap donne déjà ces dernières informations). Cet article se voulait une introduction pragmatique à l'utilisation de cette couche d'abstraction et du wireless en C sous NetBSD pour des applications utilisateurs. J'espère que l'article pourra en aider quelques-uns à démarrer ou à comprendre comment utiliser cette API, l'abstraction étant étroitement partagée entre NetBSD et FreeBSD. Les exemples demanderont un effort minime pour fonctionner également sous FreeBSD. J'espère également que vos connaissances en matière de variété française ont été améliorées et que les nombreux artistes qui ont fait notre culture musicale (merci Gilbert M. !) continueront à vous bercer après la lecture de cet article. Voilà, c'est terminé, amusez-vous bien, vous pouvez maintenant repartir vous entraîner à chanter "L'été indien" devant votre miroir. On irrrrrraaaaa, où tu voudraaaaaAaas quand tuuuuuu vouUUdrrAaaaaaAAAaaaaaas et on s'aimera encoOOOOOOoooooreee, lorsque l'amMMMMooouuuUUUurr sera moOOooort... (Vive les tomates !). Références
Il y a : 0 commentaire(s)

Donnez votre avis

Vous devez avoir ouvert une session pour écrire un commentaire.

Brèves
Édito : Linux Pratique Essentiel N°24
Édito : Linux Pratique HS N°23
Édito : GNU/Linux Magazine 146
Édito : GNU/Linux Magazine HS N°58
Édito : Open Silicium N°5
Communication
Linux Pratique HS 23 – Communiqué de presse
Linux Pratique Essentiel N°24 – Communiqué de presse
Gnu/Linux Magazine sponsor et partenaire de PROLOGIN
Linux Essentiel partenaire des Rencontres du Libre de Lion sur Mer (Normandie)
GNU/Linux Magazine HS 58 – Communiqué de presse
prochainement moteur de recherches des articles
 
:
:
Jours heures minutes secondes
En kiosque
Le tout nouveau Linux Pratique Essentiel est disponible dès maintenant chez votre marchand de journaux et sur notre site...

Lire la suite...

Le tout nouveau Linux Pratique est disponible dès maintenant chez votre marchand de journaux et sur notre site...

Lire la suite...

Le tout nouveau GNU/Linux Magazine est disponible dès maintenant chez votre marchand de journaux et sur notre site...

Lire la suite...

Le tout nouveau GNU/Linux Magazine HS est disponible dès maintenant chez votre marchand de journaux et sur notre site...

Lire la suite...

Le tout nouveau Open Silicium est disponible dès maintenant chez votre marchand de journaux et sur notre site...

Lire la suite...

Le tout nouveau Linux Pratique est disponible dès maintenant chez votre marchand de journaux et sur notre site...

Lire la suite...

Le tout nouveau Misc est disponible dès maintenant chez votre marchand de journaux et sur notre site...

Lire la suite...

Le tout nouveau GNU/Linux Magazine est disponible dès maintenant chez votre marchand de journaux et sur notre site...

Lire la suite...