Hlavní navigace

Sokety a C/C++: promiskuitní režim

27. 10. 2003
Doba čtení: 8 minut

Sdílet

Dnes si ukážeme, jak nastavit promiskuitní režim. Existují dva způsoby, můžeme použít ioctl, nebo setsockopt. V ukázkovém příkladu si napíšeme jednoduchý program, který bude monitorovat síť.

Síťové rozhraní (síťová karta) v „normálním“ stavu ignoruje linkové rámce, které jsou určeny pro jiné rozhraní. Lze nastavit takzvaný promiskuitní režim síťového rozhraní, kdy síťové rozhraní přijímá všechny rámce linkové vrstvy. Využití promiskuitního režimu je vhodné pro různou diagnózu sítě a hledání chyb.

Nastavení promiskuitního režimu

Existují dva způsoby, jak nastavit promiskuitní režim síťového adaptéru. Pomocí ioctl nebo pomocí volby soketů.

Ioctl instrukce SIOCSIFFLAGS

V minulém dílu Sokety a C/C++ – síťové rozhraní jsme se seznámili s ioctl instrukcí SIOCGIFFLAGS, která nám vrátila 2 byte příznaků o stavu síťového adaptéru. Pomocí SIOCSIFFLAGS (zkratka pro Socket IOCtl Set IF_FLAGS) lze příznaky nastavit. Chceme-li nastavit příznak promiskuitního režimu, nastavíme v příznacích odpovídající bit na 1. Použijeme při tom makro IFF_PROMISC. Hodnota makra IFF_PROMISC vyjádřena binárně má samé nuly, jen jednu jedničku na pozici, na které musíme nastavit jedničku v příznacích.

Nejprve musíme zjistit aktuální 2 byte příznaků, poté pomocí operátoru binární OR (zavolaného nad získanými příznaky a hodnotou makra IFF_PROMISC) získat novou hodnotu pro příznaky. Novou hodnotu zapíšeme do atributu ifr_flags anonymní unie struktury if_req. Struktura if_req musí mít dále vyplněný atribut ifr_name, ve kterém je jednoznačný název síťového rozhraní. Poté můžeme zavolat funkci ioctl.

Máme-li síťové rozhraní v promiskuitním režimu, který chceme vypnout, postupujeme obdobně. Jen použijeme operátor binární AND a hodnotu makra IFF_PROMISC binárně znegujeme.

Nastavení promiskuitního režimu pomocí ioctl

Proměnné jsou stejné jako v příkladu, kde používám druhou možnost nastavení promiskuitního módu. Stačilo by vyměnit tento cyklus za cyklus v příkladu.

Z příkazové řádky vezmeme všechny nepovinné parametry. Postupně je projdeme. U každého se pokusíme (pokud takové síťové rozhraní existuje) nastavit promiskuitní mód.

Nejprve musíme zjistit platný (aktuální) příznak síťového rozhraní (atribut ifr_flags). To jsme si ukázali v minulém dílu. Potom změníme hodnotu bitu na 1 na odpovídajícím místě (dáno makrem IFF_PROMISC). S údaji můžeme pracovat jako s binární maskou. Tedy zavoláme binární operátor OR (log. součet) na získaný atribut a hodnotou makra IFF_PROMISC. Nový příznak nastavíme pomocí ioctl instrukce SIOCSIFFLAGS.

for(int i = 2; i < argc; i++)
{
  strncpy(interface.ifr_name, argv[i], IFNAMSIZ);
  if (ioctl(sock, SIOCGIFFLAGS, &interface) == -1)
  {
    perror("Problém s ioctl");
  }
  // Přidáme příznak pro promiskuitní režim.
  interface.ifr_flags |= IFF_PROMISC;
  if (ioctl(sock, SIOCSIFFLAGS, &interface) == -1)
  {
    perror("Problém s ioctl");
  }
}

Vypnutí promiskuitního režimu pomocí ioctl

Proměnné jsou opět deklarovány tak jako v příkladu.

V obdobném cyklu jako při nastavování promiskuitního režimu projdeme všechny parametry z příkazové řádky. Nejprve zjistíme aktuální hodnotu příznaků u zadaných síťových rozhraní. Poté nastavíme na nulu bit na odpovídající pozici. Pozice je dána pozicí jedničkového bitu v čísle, které udává makro IFF_PROMISC. Číslo musíme znegovat a zavolat binární AND (log. součin) pro negaci makra IFF_PROMISC a původní hodnotu příznaku.

for(int i = 2; i < argc; i++)
{
  strncpy(interface.ifr_name, argv[i], IFNAMSIZ);
  if (ioctl(sock, SIOCGIFFLAGS, &interface) == -1)
  {
    perror("Problem s ioctl");
  }
  // Vynulujeme příznak pro promiskuitní režim.
  interface.ifr_flags &= ~IFF_PROMISC;
  if (ioctl(sock, SIOCSIFFLAGS, &interface) == -1)
  {
    perror("Problem s ioctl");
  }
}

Tato možnost má jednu stinnou stránku. Představte si, že dva procesy nastaví odpovídající bit na 1 a tím přepnou síťovou kartu do promiskuitního režimu. Prostě máme dva programy a oba chtějí mít síťovou kartu v promiskuitním režimu. Potom jeden proces již promiskuitní režim nepotřebuje (třeba proto, že končí, tak ho po sobě vypne – což je slušnost). Nastaví výše popsaným způsobem bit v příznacích na 0. Problém je, že vypne promiskuitní mód „natvrdo“. Vypne ho i druhému procesu. Takové chování se sice někdy může hodit, ale většinou je nežádoucí. Proto je zde druhá možnost.

Volba soketu PACKET_ADD_MEM­BERSHIP úrovně SOL_PACKET

Operační systém si eviduje něco jako skupinu procesů, které chtějí mít dané síťové rozhraní v promiskuitním režimu. Je-li ve skupině alespoň jeden proces, síťové rozhraní je v promiskuitním režimu. Jestliže neexistuje žádný člen, promiskuitní režim se vypíná. Procesy nedělají nic jiného, než že se pro každé síťové rozhraní zaregistrují do skupiny procesů, a až promiskuitní režim nepotřebují, tak se opět odhlásí. Vše se dělá pomocí voleb soketů úrovně SOL_PACKET. Pro přihlášení do skupiny se použije volba PACKET_ADD_MEM­BERSHIP, pro odhlášení se použije volba PACKET_DROP_MEM­BERSHIP. Argumentem volby (třetí parametr funkce setsockopt) je ukazatel na instanci struktury packet_mreq.

Struktura packet_mreq

Struktura slouží mimo jiné k přihlášení procesu do skupiny procesů požadující promiskuitní režim po nějakém síťovém rozhraní nebo odhlášení z ní. Pro nás podstatné atributy jsou:

  • int mr_ifindex – jednoznačný index síťového rozhraní.
  • unsigned short mr_type – akce, kterou požadujeme na síťovém rozhraní. My budeme požadovat promiskuitní režim, proto budeme zadávat hodnotu makra PACKET_MR_PRO­MISC.

Další možnosti využití struktury a další atributy jsou vmanuálových stránkách.

Typy rámců

Ještě před příkladem si povíme něco o „typech“ rámců. Struktura sockaddr_ll obsahuje mimo jiné atribut sll_pkttype. Instanci struktury nám vyplní funkce recvfrom. Hodnota atributu sll_pkttype může nabývat hodnot maker:

  • PACKET_HOST – rámec je určen jen pro tento počítač.
  • PACKET_BROADCAST – rámec je vyslán broadcastem. Je určen pro všechny.
  • PACKET_MULTICAST – rámec je vyslán multicastem. Je určen pro skupinu.
  • PACKET_OTHERHOST – rámec je určen pro jiný počítač. Naším síťovým rozhraním byl přijat jen díky nastavení promiskuitního režimu.
  • PACKET_OUTGOING – tento rámec se mi nepodařilo v mých experimenech přijmout.

Funkce bind u rodiny protokolů PF_PACKET

I u soketů z rodiny protokolů PF_PACKET lze použít funkci bind. Funkce nám „sváže“ soket se zadaným síťovým rozhraním. Druhým parametrem funkce bind je v takovém případě ukazatel na instanci struktury sockaddr_ll. Tuto možnost ale v našem příkladu nevyužijeme.

Příklad

Příklad z dílu Sokety a C/C++ – rodina protokolů PF_PACKET, který přijímal všechny IP pakety doručené počítači, rozšíříme o nové prvky. Umožníme uživateli zadat názvy síťových rozhraní, které mají běžet v promiskuitním módu. Díky tomu obdržíme všechny IP pakety, které putují sítí, k níž je síťová karta připojena. Povinný parametr z příkazové řádky bude počet přijatých rámců. Dále budou následovat nepovinné parametry označující názvy síťových rozhraní, které se mají přepnout do promiskuitního režimu. Názvy síťových rozhraní lze zjistit například použitím ukázkového příkladu z článku Sokety a C/C++ – síťové rozhraní.

Formality

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

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netpacket/packet.h>
#include <linux/if_ether.h>

#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/ip.h>

#define MAX 65536

int main(int argc, char *argv[])
{
  ifreq interface;
  int sock, lenght, count;
  unsigned short int frag_off;
  char buffer[MAX];
  sockaddr_ll addr;
  size_t size;
  iphdr *ip;
  char *addrString;
  register char mf;
  register short int offset;
  fd_set mySet;

  if (argc < 2)
  {
    fprintf(stderr, "Syntaxe:\n\t%s N [seznam
    rozhraní] \n\t\tN je počet paketů, které
    má program přijmout.]\n\t\tNepovinný seznam
    rozhraní udává rozhraní, která budou nastavena
    do promiskuitního režimu.\n", argv[0]);
    return -1;
  }
  sscanf(argv[1], "%d", &count);

Vytvoření soketu

Soket bude z rodiny protokolů PF_PACKET, bude datagramového typu s použitým protokolem ETH_P_IP.

if ((sock = socket(PF_PACKET, SOCK_DGRAM,
   htons(ETH_P_IP))) == -1)
{
  perror("Problém se socket");
  return -1;
}

Nastavení promiskuitního režimu

Postupně projdeme všechny parametry příkazové řádky. U každého řetězce se pokusíme zjistit index síťového rozhraní a k tomuto rozhraní se zaregistrujeme do skupiny procesů, které chtějí mít rozhraní v promiskuitním režimu.

for(int i = 2; i < argc; i++)
{
  strncpy(interface.ifr_name, argv[i], IFNAMSIZ);
  // Zjistíme index rozhraní
  if (ioctl(sock, SIOCGIFINDEX, &interface) == -1)
  {
    perror("Problém s ioctl");
  }
  // Přihlásíme se k členství
  memset(&pmr, 0, sizeof(pmr));
  pmr.mr_ifindex = interface.ifr_ifindex;
  pmr.mr_type =  PACKET_MR_PROMISC;
  if (setsockopt(sock, SOL_PACKET,
     PACKET_ADD_MEMBERSHIP, (char *)&pmr,
     sizeof(pmr)) == -1)
  {
    perror("Problem se setsockopt");
  }
}

Příjem dat

Nyní můžeme začít přijímat rámce. Nebudu zde opisovat zdrojový text příkladu z článku Sokety a C/C++ – rodina protokolů PF_PACKET. Jen napíšu novou část, kde rozpoznáme z atributu sll_pkttype, o jaký „typ“ rámce se jedná. Celý příklad je na konci článku ke stažení.

  printf("Typ rámce: %d\n", addr.sll_pkttype);
  printf("Jedná se o rámec určený pro ");
  switch (addr.sll_pkttype)
  {
    case PACKET_HOST: printf("tento počítač");break;
    case PACKET_BROADCAST: printf("všechny");break;
    case PACKET_MULTICAST: printf("skupinu");break;
    case PACKET_OTHERHOST: printf("někoho jiného"); break;
    case PACKET_OUTGOING: printf("?");break;
  }


Vypnutí promiskuitního režimu

Opět postupně projdeme všechny parametry příkazové řádky a pro každý řetězec nejprve zjistíme index síťového rozhraní a potom se odhlásíme ze členství ve skupině vyžadující promiskuitní režim.

for(int i = 2; i < argc; i++)
{
  strncpy(interface.ifr_name, argv[i], IFNAMSIZ);
  // Zjistíme index rozhraní
  if (ioctl(sock, SIOCGIFINDEX, &interface) == -1)
  {
    perror("Problém s ioctl");
  }
  // Odhlásíme se ze skupiny
  memset(&pmr, 0, sizeof(pmr));
  pmr.mr_ifindex = interface.ifr_ifindex;
  pmr.mr_type =  PACKET_MR_PROMISC;
  if (setsockopt(sock, SOL_PACKET,
     PACKET_DROP_MEMBERSHIP, (char *)&pmr,
     sizeof(pmr)) == -1)
  {
    perror("Problem se setsockopt");
  }
}

Konec

  close(sock);
  return 0;
}

Tím jsme vlastně napsali jednoduchý program pro monitorování provozu sítě. Nyní by to možná ještě chtělo vypisovat více informací o protokolech vyšší vrstvy (transportní), přenášených v IP paketech, které sledujeme. To už by ale neměl být problém dodělat, lze při tom vycházet z předchozích dílů seriálu. Stejně jako by neměl být problém začít třeba filtrovat zobrazované informace podle adres (síťových nebo linkových) odesílatele nebo příjemce, podle protokolu, nebo dodělat jiná vylepšení.

root_podpora

Příklad ke stažení

Tabulka č. 510
Soubor OS
lin34.tgz Linux

Tím se seriál dostal do finále. Příště ještě splatím „dluh“ a napíšu něco o funkci poll, což jsem v diskusi pod jedním z předchozích dílů slíbil. V dílu, který bude následovat potom, seriál ukončím.

Byl pro vás článek přínosný?