Hlavní navigace

Sokety a C/C++: síťové rozhraní

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

Sdílet

Dnes se podíváme na některé parametry síťového rozhraní. Seznámíme se z funkcí ioctl. V článku je příklad, ve kterém zjistíme všechna síťová rozhraní v počítači a některé informace o každém z nich.

Dnes se naučíme manipulovat s funkcí ioctl. Pomocí ní budeme zjišťovat (a v příštím díle také nastavovat) parametry síťového rozhraní (síťové karty). V každém počítači existuje minimálně jedno síťové rozhraní. Je to „pseodorozhraní“ loopback (zpětná smyčka).

Každé síťové rozhraní má v systému své jednoznačné jméno a také jednoznačný index. O indexu síťového rozhraní jsme mluvili už v minulém dílu (Sokety a C/C++ – rodina protokolů PF_PACKET), když jsme se seznámili s atributem sll_ifindex strukturysockad­dr_ll. Když jsme přijali data soketem z rodiny protokolů PF_PACKET, funkce recvfrom nám toto číslo předala. Tím vlastně máme také jednoznačně určeno síťové rozhraní, kterým přišel rámec.

Funkce ioctl

Funkce ioctl slouží k manipulaci se zařízením charakterizovaným identifikátorem souboru (file descriptor), který je prvním parametrem. Pomocí ioctl lze číst (získávat) parametry zařízení, nebo parametry nastavovat.

  • int ioctl(int d, int request, …); – prvním parametrem je identifikátor otevřeného souboru (v našem případě se bude jednat vždy o identifikátor existujícího soketu). Druhým parametrem je název operace (můžeme si představit jakoby název instrukce nebo příkazu). Budeme zadávat předdefinovaná makra, o kterých si povíme níže. Dále následují parametry instrukce, kterou jsme zadali. Následuje libovolný počet parametrů, což jsou argumenty instrukce. My budeme předávat ukazatel na strukturu if_req. Funkce obvykle vrací 0 v případě úspěšného provedení, jinak –1. (Tato návratová hodnota nemusí být pravidlem, ale v našem případě tomu tak bude vždy.)

Struktura if_req

Struktura charakterizuje jedno síťové rozhraní. Má dva atributy, řetězec ifr_name a anonymní unii. Řetězec ifr_nameje klasický C-řetězec (zakončený 0) obsahující jednoznačný název síťového rozhraní. Unie obsahuje mnoho atributů, z nichž můžeme v jednom okamžiku pracovat pouze s jedním (což je podstata unie). Všechny atributy unie a všechny ioctl operace související se síťovým rozhraním jsou v manuálových stránkách.

Ukazatel na instanci struktury budeme předávat funkci ioctl.

Některé ioctl operace pro práci se síťovým rozhraním.

V příkladu použijeme ioctl instrukce, které popíšu. Při té příležitosti popíšu i atribut anonymní unie strukturyif_req, se kterým bude funkce ioctl manipulovat.

  • SIOCGIFNAME (zkratka pro SocketIOCtlGe­tIF_NAME) Anonymní unie má atribut int ifr_ifindex. Tento musí být před zavoláním ioctl vyplněn jednoznačným indexem síťového rozhraní. Po zavolání ioctl bude v atributu char *if_namestruktury if_req jednoznačné jméno síťového rozhraní. Operace nám na základě zadaného indexu síťového rozhraní vrátí jméno síťového rozhraní.
  • SIOCGIFINDEX (zkratka pro SocketIOCtlGe­tIF_INDEX) Operace dělá přesný opak toho, co operace SIOCGIFNAME. Na základě vyplněného atributu if_name vyplní funkce ioctl atribut ifr_ifindex.
  • SIOCGIFNETMASK (zkratka pro SocketIOCtlGe­tIF_NETMASK) Anonymní unie má atribut struct sockaddr ifr_netmask. Před zavoláním ioctl musí být zaplněn atribut if_name. Po zavolání ioctl je atribut ifr_netmask zaplněn síťovou maskou. Tuto ioctlinstrukci nenajdete na zmíněné manuálové stránce. Já ji nalezl jen na seznamu všech ioctl.
  • SIOCGIFFLAGS zkratka pro SocketIOCtlGe­tIF_FLAGS) Anonymní unie má atribut short ifr_flags. Atribut obsahuje příznaky, které charakterizují stav síťového rozhraní. Před zavoláním ioctl bude mít instance struktury if_req nastavený název síťového rozhraní. Po zavolání funkce ioctl bude atribut ifr_flag obsahovat příznaky. Jednotlivé informace zjistíme pomocí binárních masek, které jsou definovány jako makra. Provedeme-li operaci binárního AND nad získaným číslem (hodnotou atributu ifr_flags) a odpovídající binární maskou, získáme nulovou, nebo nenulovou hodnotu, která značí, zda příznak je, nebo není nastaven. Příznaky, které použijeme:
    • IFF_UP – je nastaveno, jestliže rozhraní běží.
    • IFF_LOOPBACK – je nastaveno, pokud je rozhraní zpětná smyčka. Toto síťové rozhraní má vždy přiřazenou IP adresu 127.0.0.1
    • IFF_POINTOPOINT – je nastaveno, jedná-li se o dvoubodový spoj. Typické pro počítače připojené modemem.
    • IFF_PROMISC – je nastaveno, je-li rozhraní v promiskuitním mó­du.
    Další příznaky jsou na manuálových stránkách.
  • SIOCGIFCONF – na rozdíl od předchozích ioctl instrukcí nepracuje se strukturou ifreq. Třetím parametrem funkce ioctl je v tomto případě ukazatel na instanci struktury ifconf. Struktura ifconf obsahuje dva atributy. Atribut int ifc_len obsahuje velikost bufferu, který je druhým atributem. Druhý atribut je anonymní unie, která obsahuje dva atributy. Pro nás důležitý bude atribut struct ifreq *ifc_req. Je to ukazatel, který se odkazuje na pole struktur ifreq. Práci s touto ioctl instrukcí si blíže popíšeme v příkladu.

Příklad

Informace, jež jsem zde vypsal, nám stačí na napsání jednoduchého příkladu, který svou funkčností připomínat program ifconfig. Pomocí ioctl instrukce SIOCGIFCONF zjistí všechna síťová rozhraní a pomocí ioctl instrukcí, které jsme si popsali, zjistí o jednotlivých rozhraních informace, a ty vypíše.

Formality

Vložíme hlavičkové soubory, začneme funkci main a deklarujeme lokální proměnné.

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

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

#include <string.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
  ifreq interface;
  ifconf interfaceConf;
  int sock;
  unsigned int count = 0;
  sockaddr_in *ipAddr;

Vytvoříme soket

Sice se pohybujeme na úrovni síťových zařízení, soket ale nemusí být nutně z rodiny protokolů PF_PACKET. Můžeme mít například soket rodiny protokolů PF_INET, datagramového typu (SOCK_DGRAM) s použitým protokolem UDP (IPPROTO_UDP). Díky tomu nepotřebuje program pro své spuštění rootovská práva.

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


Získání informací o všech síťových rozhraních

Pomocí ioct instrukce SIOCGIFCONF získáme strukturu se všemi síťovými rozhraními. Instanci struktury ifconf jsem pojmenoval interfaceConf. Před zavoláním ioctl musí mít instance struktury nastaven ukazatel ifc_req na alokovanou paměť. Velikost alokované paměti musí být uložena v atributu ifc_len. Po zavolání

ioctl bude atribut ifc_len obsahovat stejné číslo jako původně, nebo menší. Buffer, na který se odkazuje ukazatel ifc_req, bude obsahovat pole instancí struktury ifreq. Atribut ifc_len po zavolání ioctl obsahuje skutečnou velikost, kterou zabírá pole, na které se odkazuje ifc_req. Atribut ifc_req se zavoláním

ioctl určitě nezvětší. Pokud se ale nezmenší, je riziko, že pole není kompletní, protože se možná do alokovaného bufferu nevleze. Proto budeme v cyklu zvětšovat hodnotu ifc_len a volat ioctl. Cyklus budeme opakovat, dokud se atribut ifc_len voláním ioctl nezmenší.

Do cyklu vstoupíme s hodnotou proměnné count (počet síťových rozhraní) rovnou nule a nastaveným atributem ifc_reqna NULL.

interfaceConf.ifc_req = NULL;
do
{


V každém průchodu cyklem zvýšíme počet rozhraní o 1 a nastavíme atribut ifc_len na počet bytů, které alokujeme. Počet bytů je roven počtu síťových rozhraní vynásobenému počtem bytů na jednu instanci struktury ifreq, která rozhraní charakterizuje.

interfaceConf.ifc_len = ++count * sizeof(ifreq);

Změníme velikost bufferu na odpovídající hodnotu vypočtenou v předchozím kroku. První průchod cyklem není problém, protože realloc se v případě, že první parametr je NULL, chová stejně jako malloc.

interfaceConf.ifc_req = (ifreq *)realloc(interfaceConf.ifc_req, interfaceConf.ifc_len);

Zavoláme ioctl, čímž možná získáme kompletní seznam všech síťových rozhraní. Instrukce je IOCGIFCONF a parametr instrukce je ukazatel na instanci struktury ifreq.

if (ioctl(sock, SIOCGIFCONF, &interfaceConf) == -1)
{
perror("Problém s ioctl");
return -1;
}


Cyklus opakujeme tak dlouho, dokud se nezmění při volání atribut ifreq. Jestliže se zmenší, potom skutečný počet rozhraní je velikost bufferu (ta je po zavolání ioctl v atributu ifc_len) vydělená velikostí jedné instance struktury ifreq. Pochopitelně výsledek bude vždy celočíselný.

} while (interfaceConf.ifc_len / sizeof(ifreq) == count);
count = interfaceConf.ifc_len / sizeof(ifreq);
printf("Počet rozhraní: %u\n", count);


Každá instance struktury v poli, na které se odkazuje ukazatel ifc_req, obsahuje název v atributu ifr_name a přiřazenou IP adresu v atributu ifr_addr anonymní unie ve struktuře ifreq.

Postupné získání informací o jednotlivých rozhraních

Postupně projdeme všechny prvky v poli, na které se odkazuje atribut ifc_req.

for(unsigned int i = 0; i < count; i++)
{


Název rozhraní

Název máme vyplněný již od posledního volání ioctl.

printf("\nNázev rozhraní: %s\n", interfaceConf.ifc_req[i].ifr_name);

IP adresa rozhraní

IP adresu rozhraní máme vyplněnou již od posledního volání ioctl. Je obsažena v atributu ifr_addr anonymní unie ve struktuře ifreq.

ipAddr = (sockaddr_in *)&interfaceConf.ifc_req[i].ifr_addr;
printf("IP adresa rozhraní: %s\n", inet_ntoa((in_addr)ipAddr->sin_addr));


Index rozhraní

Pomocí ioctl instrukce SIOCGIFINDEX získáme index síťového rozhraní na základě zadaného jména rozhraní. Instance struktury má před zavoláním ioctl vyplněný atributifr_name, který obsahuje jméno. Po zavolání ioctl obsahuje atribut ifr_ifindex anonymní unie hodnotu indexu.

strncpy(interface.ifr_name, interfaceConf.ifc_req[i].ifr_name, IFNAMSIZ);
if (ioctl(sock, SIOCGIFINDEX, &interface) == -1)
{
perror("Problém s ioctl");
}
printf("Index rozhraní: %d\n", interface.ifr_ifindex);


Síťová maska

Pomocí ioctl instrukce SIOCGIFNETMASK získáme síťovou masku, jedná se o síťovou masku k IP adrese, kterou má rozhraní přiřazenu.

if (ioctl(sock, SIOCGIFNETMASK, &interface) == -1)
{
perror("Problém s ioctl");
}
ipAddr = (sockaddr_in *)&interface.ifr_netmask;
printf("Síťová maska: %s\n", inet_ntoa((in_addr)ipAddr->sin_addr));


Příznaky síťového rozhraní

Pomocí ioctl instrukce SIOCGIFFLAGS získáme dva byty (atribut ifr_flags anonymní struktury) příznaků. Hodnoty jednotlivých příznaků získáme pomocí binárních masek. Význam maker je popsán výše.

if (ioctl(sock, SIOCGIFFLAGS, &interface) == -1)
{
perror("Problém s ioctl");
}
if (interface.ifr_flags & IFF_UP)
{
printf("Rozhraní \"běží\" (je aktivováno).\n");
}
else
{
printf("Rozhraní \"neběží\" (není aktivováno).\n");
}
if (interface.ifr_flags & IFF_LOOPBACK)
{
printf("Jedná se o zpětnou smyčku.\n");
}
if (interface.ifr_flags & IFF_POINTOPOINT)
{
printf("Jedná se o \"dvoubodový spoj\".\n");
}
if (interface.ifr_flags & IFF_PROMISC)
{
printf("Je v promiskuitním režimu.\n");
}


Konec

Ukončíme závorky, cyklus, uvolníme alokovaný buffer, uzavřeme soket a ukončíme funkci main.

root_podpora

}
free(interfaceConf.ifc_req);
close(sock);
}


Příklady

Tabulka č. 507
Soubor OS
lin33.tgz Linux

Jak asi mnohé, kteří se podívali na manuálové stránky, napadne, promiskuitní mód se nastaví vyplnění atributu ifr_flags a zavoláním ioctl instrukce SIOCGIFFLAGS. Příště si to ukážeme na příkladu. Ukážeme si také jinou možnost, jak nastavit promiskuitní mód. Vysvětlíme si, co to vlastně promiskuitní mód síťové karty znamená, jak přijímat doručené rámce a jak je od sebe rozlišit.

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