perjantai 16. syyskuuta 2011

Netlink soketit

Netlink soketti rajapinta.

Viime keväänä sain tehtäväkseni laajentaa linux:n päälle tehdyn ohjelmistoalustamme IPv4 konfigurointituen koskemaan myös IPv6:tta. Kyseessä on siis reittien, aliasosotteiden, VLAN interfacejen jne konfigurointi. IPv4 osuus oli tehty pääosin linuxin ioctl rajapintaa käyttäen, mutta IPv6 tukea pohtiessani totesin tämän olevan riittämätön.

Pienen tutkinnan ja hutkinnan jälkeen, päädyin netlink soketti rajapinnan käyttämiseen. Man sivut ja useat muut lähteet kehoittivat käyttämään matalan tason netlink API:n sijasta libnl kirjastoa. Kääntelin libnl kirjaston omalle alustalleni, ja aloin tutkimaan miten se toimii. Jostain syystä libnl:n dokumentaatio ei kuitenkaan millään avautunut minulle. Aikani tuskailtuani löin hanskat tiskiin ja totesin että minun on keksittävä jokin toinen keino. Niinpä palasin takaisin raakaan netlink rajapintaan.

Netlink rajapinnalle löysin varsin kattavanoloisen dokumentaation man sivuista, sekä jopa RFC:n joka kuvasi netlink rajapinnan routtaus protokollan. Ei siis muuta kun hihat rullalle ja hommiin... Itse asiassa, perusjutut oli varsin suoraviivaisia, ja moni asia onnistui varsin kivuttomasti, jo pelkän pienen googlailun ja man sivujen tutkinnan perusteella. Lopulta kuitenkin törmäsin aukkoihin dokumentaatiossa, jotka lopulta pakottivat minut kääntämään oman debuggiprinteillä varustetun kernelin... Näitä aukkoja on tarkoitus paikata tässä blogitekstissä. Samalla luultavasti lisään oman pienen palaseni muuten rikkaaseen netlink sokettien perusdokumentaatioon - jota tosin voi olla vaikeaa löytää suomeksi??

Netlink socketit ovat itse asiassa enemmän kuin vain IP konfigurointien rajapinta. Ne ovat varsin geneerinen keino vaihtaa dataa user ja kernelspacen välillä. Netlink sockettien avulla voi myös mm. säätää palomuureja ja "neighbour cachea". Tässä blogissa keskityn kuitenkin vain kolmeen netlink sockettien viestityyppiin/konfiguroitavaan ominaisuuteen.

1. Linkkeihin (kuten eth0, vlan linkit kuten eth0.20 jne), jotka ovat ikäänkuin ovia systeemin ulkopuolelle / ulkopuolelta.
2. Osoitteisiin (kuten 192.168.255.2/24 tai fe80::211:43ff:fe26:2b6c/64), jotka ovat ikäänkuin lappuja ovissa, kertoen systeemin ulkopuolelle kuka oven takana on, ja systeemin sisäpuolelle, mihin ulkopuolelta pääsee.
3. Reitteihin, (kuten 0.0.0.0 via dev eth0, tai 10.34.143.0 255.255.255.0 gw 192.168.1.55 metric 2), jotka voivat osoitteiden lisäksi antaa erityisiä ohjeita siitä, mistä ovesta mihinkin kohteeseen matkalla oleva paketti täytyy potkaista menemään.

Nyt minulle tuli pakoittava tarve kertoa hiukan tarkemmin reiteistä. Saattaa olla, että Vesa ei ihan ymmärtänyt mitä tuo reitti tarkoittaa ;) Jos reitit on tuttua kauraa, tai eivät kiinnosta, niin ole hyvä ja skippaa neljä seuraavaa kappaletta.

Reittien ideana on sanoa, että "tällaiseen osoitteeseen matkalla oleva paketti täytyy tuupata tuosta interfacesta ulos". Jos palaamme stackissa muutaman rivin taaksepäin, huomaamme kohdan jossa kerroin osoitteen olevan ikäänkuin lappu siitä, mitä jonkin linkin (interfacen) takana on. Ts. jos eth0 linkkimme osoite on 192.168.255.2/24, tämä kertoo meille että oven (linkin) eth0 takana on "verkko" (osoitteet) 192.168.255.XXX. Miksi? Koska osoitteen loppuosa /24 kertoo ns netmaskin. Se siis kertoo että oven takana oleva verkko, johon siis meidänkin ovemme kuuluu, koostuu osoitteista joissa 24 ensimmäistä bittiä on kiinteitä, loppujen 8 vaihtuessa. Ts, 192.168.255 on verkkomme vakio-osa. Jokainen pisteiden välissä oleva numerosarja kun voi IPv4 osoitteessa vaihdella 0-255 välillä - Ts. muodostuu kahdeksasta bitistä. 4 tällaista osaa tarkoittaa 4*8 = 32 bittiä, joka siis on koko osoite. Esimerkissämme 24 ensimmäistä bittiä on kiinteitä, ts. kolme ensimmäistä numerosarjaa on kiinteitä. Jos maski olisi /23, tarkoittasi se että 23 bittiä olisi kiinteitä. Tällöin osoitteet verkossa voisivat olla 192.168.255.XXX tai 192.168.254.XXX /8 tarkoittaisi osoitteiden olevan 192.XXX.XXX.XXX jne.

Nyt siis osaamme ilman erillisiä reittejä jo sanoa, että osoitteeseen 192.168.255.66 matkalla oleva paketti, tulisi esimerkkitapauksessamme tuupata ovesta eth0 ulos. Mutta entäs jos meillä olisi useampi linkki (esim. useampi verkkokortti, tai vaikka VLAN virtuaali-interface) vaikkapa eth0 ja eth1, olkoot osoitteet vaikka 192.168.255.2/24 ja 192.168.1.2/24, ja pakettimme olisi menossa osoitteeseen 10.24.45.22? Nyt kummankaan linkin osoite ei kerro että linkin takaa löytyisi kyseinen kohde... Tarvitaan siis erillinen reititysohje kertomaan kummanko linkin kautta paketti tulee paiskata pihalle. Katsotaampa siis ensimmäistä esimerkkireittiä:

0.0.0.0 via dev eth0
0.0.0.0 on reitin kohde osoite. 0.0.0.0 on erikoisasemassa oleva osoite, ja kohdeosoitteena se siis tarkoittaa ns. oletusreittiä. Ts. kyseinen reitti kertoo, että mikäli mikään muu reititysohje ei paketin kanssa toimi, tulee se toimittaa eth0 linkkiin.

Toinen esimerkkireitti oli 10.34.143.0 255.255.255.0 gw 192.168.1.55 metric 2
Tässä kohdeosoite on 10.34.143.0, ja nettimaski on 255.255.255.0. Tämä tarkoittaa samaa kuin 255.255.255.0/24, eli maski kertoo merkitsevät numerot osoitteessa. Gw tarkoittaa gatewayta, joka kertoo, että paketti tulee toimittaa koneen 192.168.1.55 kautta, ja tuo kone tuntee tien lopulliseen määränpäähän. Metric 2 kertoo kuinka suosittu reitti on. Ts, mikäli johonkin osoitteeseen on määritetty useampi kuin yksi reitti, valitaan ensisijaisesti reitti jolla on tarkempi maski. Esim jos paketti on lähetetty osoitteeseen 192.168.255.124, ja reitteja on määritetty osoitteeseen 192.168.255.0 maskilla 255.255.255.0, sekä reitti osoitteeseen 192.168.255.124 maskilla 255.255.255.255, valitaan ensisijaisesti jälkimmäinen reitti, joka siis on tarkka. Ns. "hostireitti". Mikäli kuitenkin reittejä olisi kaksi vaihtoehtoista, eikä maskeissa olisi eroja, voidaan "suosituimmuus" määrittää metric arvolla. Mitä pienempi metric, sitä suositumpi reitti. Ts, paketti toimitetaan ennemmin pienemmän metricin omaavaan reittiin.

IPv6:n kanssa reititys on hieman monimutkaisempaa, sillä itse osoitteet kantavat osan aliverkkotiedosta. Mutta se reiteistä. Mennäänhän päivän epistolaan. Netlink rajapinta on siis tyypillinen viestirajapinta. Sockettiin lähetetään pyyntöjä, ja vastauksia kuunnellaan. Tässä jutussa käsittelen kolmen eri tyypin pyyntöjä/vastauksia - Osoite (ADDRESS), reitti (ROUTE) ja linkki (LINK). Kaikille perheille on määritelty viestityypit joilla voidaan

luoda uusi < osoite/linkki/reitti >
poistaa olemassaoleva < osoite/linkki/reitti >
hakea olemassaolevat < osoitteet/linkit/reitit >

Tarkat viestin headeriin täytettävät viestityypit ovat

RTM_NEWADDR (uusi osoite)
RTM_DELADDR (osoitteen poisto)
RTM_GETADDR (olemassaolevien osoitteiden haku)

RTM_NEWROUTE (uusi reitti)
RTM_DELROUTE (...poisto)
RTM_GETROUTE (...haku)

RTM_NEWLINK (uusi linkki)
RTM_DELLINK (...poisto)
RTM_GETLINK (...haku)

ja linkeille myös

RTM_SETLINK

jolla luodun linkin tilaa voidaan säätää.

Tässäpä minulle tulikin ensimmäiset ongelmat... Vasta kernelin koodeja tutkimalla opin, että NEWLINK reqiestit joissa struct ifinfomsg viestistructissa oli kenttiä täytetty - hylättiin.

Varsinainen netlink viesti koostuu:

1. normaali viestittelyssä käytetystä messuheaderista tyyppiä struct msghdr. Oletan että tämä on tuttu.
2. netlinkin messuheaderista tyyppiä struct nlmsghdr (alla)
3. viestityypistä (esim route, addres tai link) riippuva structi (tässä esitelty struct ifaddrmsg / struct ifinfomsg / struct rtmsg)
4. mahdollisesti sekalainen joukko atribuutteja.

Netlink message header
struct nlmsghdr
{
    __u32     nlmsg_len; /* Viestin pituus headeri mukaanlukien */
    __u16     nlmsg_type; /* Viestin tyyppi (kts. yllä) */
    __u16     nlmsg_flags;    /* Flagit */
    __u32     nlmsg_seq; /* Sequenssi numero */
    __u32     nlmsg_pid; /* Lähettävän processin portti ID */
};

1. Eli nlmsg_len siis kertoo koko viestin pituuden, karvoinen kaikkineen. Netlink viestien dynaamisten kokojen takia tämä kenttä on hyvin keskeinen.
2. Viestin tyyppi, esim RTM_NEWLINK.
3. Flagit...

...Suoraan /usr/include/linux/netlink.h:sta kaiveltuna/käänneltynä:

/* Flagit */

NLM_F_REQUEST        /* Viesti on requesti */
NLM_F_MULTI        /* Useampiosainen viesti, viimeinen tyyppiä NLMSG_DONE */
NLM_F_ACK         /* ack - viesti, sisältönä nolla tai virhekoodi */
NLM_F_ECHO         /* Pyyntö "echottaa" viesti */

/* GET requestin liput */
NLM_F_ROOT     /* palauta kaikki data, ei vain yhtä "entryä"*/
NLM_F_MATCH    /* palauta kaikki täsmäävät - todennäköisesti ei toteutettu */
NLM_F_ATOMIC     /* atominen "snapshotin" haku */
NLM_F_DUMP /* Dumppaa koko taulu */

/* NEW requestin liput */
NLM_F_REPLACE     /* Korvaa olemassaoleva */
NLM_F_EXCL     /* Älä koske jos on jo olemassa */
NLM_F_CREATE     /* Luo jos ei ole olemassa */
NLM_F_APPEND     /* Lisää listan hännille */

Esimerkiksi kaikkien pyyntöjen tulee olla varustettuna lipulla NLM_F_REQUEST. NLM_F_MATCH ei luultavasti ole toteutettuna, ja GET requestisi palauttanee kaikki mahdolliset reitit/linkit tai osoitteet jotka on tehtynä - riippumata niistä tarkennuksista joita requestisi structissa/atribuuteissa mainitsit. Lisätarkennusta flageihin saatat löytää man sivuilta.

4. sequenssinumero, joka auttaa parittamaan vastaukset oikeiden pyyntöjen kanssa - tyypillisesti juokseva numerointi (esim atomisesti kasvatettava globaali).
5. prosessin identifiointi. Tyypillisesti
(pthread_self() < < 16|getpid());
Kernelin vastauksissa pid on 0.

Huomaathan että oletusarvoisesti kerneli ei lähetä vastausta onnistuneisiin pyyntöihin (paitsi tietysti GET pyyntöihin). Minä kuitenkin haluan saada varmistuksen sille, että pyyntöni on kuultu. Siksi höystänkin kaikki muut paitsi GET requestini NLM_F_ACK lipulla. Tämä lippu saa kernelin vastaamaan muihinkin kuin GET pyyntöihin. Onnistunut suoritus kuitataan viestillä, jonka tyyppi on NLMSG_ERROR, ja jossa nlmsghdr structin perässä on struct nlmsgerr, jossa error kenttä sisältää arvon 0. Samaa viestiä kerneli käyttää raportoimaan virheet, mutta silloin error kenttä sisältää virhekoodin. Virhetilanteet raportoidaan NLM_F_ACK lipusta riippumatta.

struct nlmsghdr:n jälkeen seuraa requestikohtainen structi.

Osoitteille:

struct ifaddrmsg
{
    __u8        ifa_family;
    __u8        ifa_prefixlen; /* Prefixin pituus (maski)         */
    __u8        ifa_flags; /* Liput            */
    __u8        ifa_scope; /* Osoitteen skooppi        */
    __u32     ifa_index; /* Linkin ID numero (ifindex)          */
};

jossa ifa_family on siis IPv4 tilanteessa AF_INET, ja IPv6 tilanteessa AF_INET6.
ifa_prefixlen on maskin pituus bitteinä - IPv4 tapauksessa 0-32 ja IPv6 tapauksessa 0-128 (eli tämä siis kertoo millainen osoiteavaruus on sen interfacen takana, jonne linkki johon osoite on sidottu johtaa.
ifa_flags - liput

/* ifa_flags */
IFA_F_SECONDARY    
IFA_F_TEMPORARY

IFA_F_NODAD    
IFA_F_OPTIMISTIC
IFA_F_DADFAILED    
IFA_F_HOMEADDRESS
IFA_F_DEPRECATED    
IFA_F_TENTATIVE    
IFA_F_PERMANENT    


Joista en ole oikeastaan koskaan tarvinnut yhtään.

ifa_scope - "skooppi" - en oikeastaan ole jaksanut tutkia mitä tämä pitää sisällään. Tähänasti olen tyytynyt asettameen skoopin nollaksi.
ifa_index - sen linkin indexinumero johon osoite on sidottu. (kts man sivut funkkarille unsigned if_nametoindex(const char *ifname) jos tämä on outo juttu)


Linkit:

struct ifinfomsg
{
    unsigned char ifi_family;
    unsigned char __ifi_pad;
    unsigned short ifi_type;     /* ARPHRD_* */
    int     ifi_index;     /* Link index */
    unsigned    ifi_flags;     /* IFF_* flags */
    unsigned    ifi_change;     /* IFF_* change mask */
};

Kas niin. Murheenkryynini. Kuten jo mainitsin, hakkasin itse päätä seinään tekemällä RTM_NEWLINK requestin jossa struct ifinfomsg:n kenttiä oli täyteltynä. VLAN interfaceni luonti hylättiin tylysti kerta toisensa jälkeen.. Lopulta kernelikoodeja lukemalla tajusin, että NEWLINK requestissa ei oikeastaan saa olla mitään näistä täytettynä, vain attribuutteja kertomassa mitä oikeastaan yritetään. Myöhemmin linkin tilan voi muuttaa RTM_SETLINK requestilla. Itse käytin RTM_SETLINK:iä vain muuttamaan linkin tilan "ylös" tilaan (IFF_UP). Ja tämänkin tein laiskasti tuuppaamalla vain requestiin ifi_index kentäksi luodun linkin id:n, ifi_changen 0xffffffff:ksi (kuten man sivut neuvoo) ja ifi_flags kentän suoraan IFF_UP:ksi. Oikeampi tapa olisi varmaan ollut lukea linkin tila, ja sitten lisätä tai-operaatiolla (|) IFF_UP bitit lippuun.

Reitit:

struct rtmsg
{
    unsigned char     rtm_family;
    unsigned char     rtm_dst_len;
    unsigned char     rtm_src_len;
    unsigned char     rtm_tos;

    unsigned char     rtm_table; /* Routtaus taulun id */
    unsigned char     rtm_protocol; /* Routtaus protokolla (alla) */
    unsigned char     rtm_scope; /* skooppi (alla) */
    unsigned char     rtm_type; /* tyyppi (alla) */

    unsigned        rtm_flags;
};


Missä tyyppi voi olla:


    RTN_UNSPEC, /* määrittelemätön */
    RTN_UNICAST,        /* Gateway tai suora reitti */
    RTN_LOCAL,     /* Paikallinen      */
    RTN_BROADCAST,     /* Paikallinen bcast,
                 lähetä broadcastina */
    RTN_ANYCAST,        /* Hyväksy paikallisesti broadcastina,
                 mutta lähetä unicastina */
    RTN_MULTICAST,     /* Multicast reitti     */
    RTN_BLACKHOLE,     /* Tiputa paketit;          */
    RTN_UNREACHABLE,    /* Määränpää ei tavoitettavissa */
    RTN_PROHIBIT,     /* Pääsy kielletty */
    RTN_THROW,     /* Ei tässä taulussa        */
    RTN_NAT,        /* Muunna NAT:illa */
    RTN_XRESOLVE,     /* Käytä erillsitä paikannusta - ei mahda olla toteutettu    */

Luultavasti normaaleille reiteille haluat käyttää RTN_UNICASTia. Vaikkakin toisinaan spammin määrää ihmetellessä RTN_BLACKHOLEn käyttö olisi houkuttelevaa... ;)

Protokollat (kertovat siitä miten reitti on syntynyt):


#define RTPROT_UNSPEC 0 /* määrittelemätön */
#define RTPROT_REDIRECT 1 /* ICMP redirectien tekemä reitti;
                 Ei tällähetkellä IPv4:llä käytössä */
#define RTPROT_KERNEL 2 /* kernelin tekemä reitti        */
#define RTPROT_BOOT 3 /* bootissa tehty reitti      */
#define RTPROT_STATIC 4 /* Adminin tekemä reitti */

/*
Kerneli ei tutki protokollia > = RTPROT_STATIC;
*/

#define RTPROT_GATED    8 /* GateD */
#define RTPROT_RA 9 /* RDISC/ND router advertisements */
#define RTPROT_MRT 10 /* Merit MRT */
#define RTPROT_ZEBRA    11 /* Zebra */
#define RTPROT_BIRD 12 /* BIRD */
#define RTPROT_DNROUTED 13 /* DECnet routing daemon */
#define RTPROT_XORP 14 /* XORP */
#define RTPROT_NTK 15 /* Netsukuku */
#define RTPROT_DHCP 16     /* DHCP client */


Minä käytin arvoa RTPROT_STATIC, joka siis vastaa tilannetta jossa käyttäjä luo reitin käyttämällä komentoa "ip route add".

skoopit:

    RT_SCOPE_UNIVERSE=0,
/* User defined values */
    RT_SCOPE_SITE=200,
    RT_SCOPE_LINK=253,
    RT_SCOPE_HOST=254,
    RT_SCOPE_NOWHERE=255

Jos reittisi ei ole tarkoitettu paikalliseksi, lienee syytä käyttää RT_SCOPE_UNIVERSEa.


/* rtm_flags */

#define RTM_F_NOTIFY        0x100 /* Ilmoita jos reitti muuttuu */
#define RTM_F_CLONED        0x200 /* Kloonattu reitti     */
#define RTM_F_EQUALIZE     0x400 /* Multipath equalizer: NI */
#define RTM_F_PREFIX        0x800 /* Prefix addresses     */

Minä asetin liput nollaksi - en siis tarvinnut näitä omassa käytössäni.


Nyt viestimme siis koostuu netlink headerista, jossa ilmoitetusta tyypistä riippuen meillä on viestissä tyyppikohtainen strukti. Jotta homma ei jäisi liian simppeliksi, lisätään hieman muuttuvia osasia.. =)

Attribuutit.

Viestin loppuun voidaan lisätä sekalainen joukko attribuutteja joilla tarkennetaan luotavan/poistettavan reitin/linkin/osoitteen ominaisuuksia. Attribuutit alkavat structilla:

struct rtattr
{
    unsigned short rta_len;
    unsigned short rta_type;
};

jossa siis kerrotaan attribuutin koko, ja tyyppi. Structin jälkeen on varsinainen attribuutin data, jota siis on rta_len-sizeof(struct rtattr) verran. Attribuutit on vielä alignoitu 4:n tavun mukaan. Kuten C-koodari äkkiä huomaa, tällaisesta virityksestä saattaa tulla sekavahko hanskattava. Siksipä meillä onkin valmiit makrot attribuuttien hanskaukseen:

RTA_ALIGN(len)
RTA_OK(rta,len)
RTA_NEXT(rta,attrlen)
RTA_LENGTH(len)
RTA_DATA(rta)
RTA_PAYLOAD(rta)

RTA_ALIGN(len) Pyöristää annetun pituuden neljän tavun alignmenttiin. (Miten tuo sanotaan suomeksi???) Eli siis
RTA_ALIGN(1) palauttaisi 4 kuten myös RTA_ALIGN(4). RTA_ALIGN(5) taas palauttaisi 8 jne.

RTA_OK(rta,len) tarkastaa että annettu attribuutti rta on Ok. Yleinen tapa läpikäydä viestijonon attribuutit on hakea aina seuraava attribuutti makrolla RTA_NEXT, ja tarkistaa että atribuuttia on turvallista tutkia, kutsumalla RTA_OK:ta. Makroille annettavan pituuden (len), on ensimmäisellä kutsulla oltava attribuuttibufferin koko. Jokaisella RTA_NEXT kutsulla kokoa päivitetään.

RTA_LENGHT(len) palauttaa koon, joka tarvitaan jotta voidaan muodostaa atribuutti jonka datan koko on len. Eli RTA_LENGHT siis lisää annettuun kokoon struct rtattr:n koon ja alignoi sen sitten neljän tavun mukaiseksi.

RTA_DATA(rta) palauttaa pointterin attribuutin rta datan alkuun.
RTA_PAYLOAD(rta) palauttaa attribuutin rta sisältämän datan pituuden.

Kuulostaa sekavalta? Älä huoli, näytän myöhemmin esimerkin jolla attribuutteja voi läpikäydä.


Nyt kun selvät asiat on läpikäyty, voidaan mennä seikkaan joka sai minun hermoni melkein riekaleiksi.. Tieto siitä miten netlink sokettien avulla tehdään VLAN interface, eli virtuaalinen linkki joka lähettää VLAN tagilla varustettuja paketteja, ei löytynyt ihan helpolla. Google lauloi kun etsin:
"VLAN interface netlink sockets", "RTM_NEWLINK VLAN". "Create VLAN via netlink". "VLAN netlink attribute"... Näillä hauilla löysin tarpeeksi tietoa kertomaan, että kyllä - VLAN linkin pystytys on ilmeisesti mahdollista, ja että se onnistunee jollain attribuutilla... No lopullinen selvuus löytyi lisäämällä debuggiprinttejä kerneliin, ja yrittämällä & erehtymällä. Eli tavanomaisten attribuuttien lisäksi on ns. nested attribuutteja, eli sisäkkäisiä attribuutteja. Mutta katsellaanpa tuota myöhemmin.

Attribuutit joita eri requestit tukevat:

Osoitteet:
nbsp;   IFA_UNSPEC,
    IFA_ADDRESS,
    IFA_LOCAL,
    IFA_LABEL,
    IFA_BROADCAST,
    IFA_ANYCAST,
    IFA_CACHEINFO,
    IFA_MULTICAST,


Reitit:


    RTA_UNSPEC,
    RTA_DST,
    RTA_SRC,
    RTA_IIF,
    RTA_OIF,
    RTA_GATEWAY,
    RTA_PRIORITY,
    RTA_PREFSRC,
    RTA_METRICS,
    RTA_MULTIPATH,
    RTA_PROTOINFO, /* no longer used */
    RTA_FLOW,
    RTA_CACHEINFO,
    RTA_SESSION, /* no longer used */
    RTA_MP_ALGO, /* no longer used */
    RTA_TABLE,


Linkit:


    IFLA_UNSPEC,
    IFLA_ADDRESS,
    IFLA_BROADCAST,
    IFLA_IFNAME,
    IFLA_MTU,
    IFLA_LINK,
    IFLA_QDISC,
    IFLA_STATS,
    IFLA_COST,
    IFLA_COST
    IFLA_PRIORITY,
    IFLA_PRIORITY
    IFLA_MASTER,
    IFLA_MASTER
    IFLA_WIRELESS,    
    IFLA_WIRELESS
    IFLA_PROTINFO,    
    IFLA_PROTINFO
    IFLA_TXQLEN,
    IFLA_TXQLEN
    IFLA_MAP,
    IFLA_MAP
    IFLA_WEIGHT,
    IFLA_WEIGHT
    IFLA_OPERSTATE,
    IFLA_LINKMODE,
    IFLA_LINKINFO,
    IFLA_LINKINFO
    IFLA_NET_NS_PID,

osaan löydät dokumentaation rtnetlinkin man sivuilta sektiosta 7 - osaan et...

Ja nyt helpotukseksi heille jotka taistelevat VLAN linkkien teon kanssa... Minä sain VLAN linkin pystytettyä seuraavien attribuuttien avulla.

IFLA_LINK, datan koko on intin verran. Sisältää VLANin alla olevan oikean interfacen indexin.
IFLA_IFNAME, uuden VLAN linkin nimi. Itse käytin muotoa < alkuperäinen_interface > . < vlan Id >

sisäkkäiset attribuutit:
IFLA_LINKINFO joka siis sisältää
    attribuutin IFLA_INFO_KIND, jossa datans stringi 'vlan', pituuden ollessa tekstin 'vlan' pituus + 4:n tavun alignointi.
    toinen nested attribute IFLA_INFO_DATA, joka sisältää:
        attribuutin IFLA_VLAN_ID, jossa datana 16-bittinen vlan id.

Sekavan kuulosita? Jep.. Toisinaan ihmettelen onko insinöörit todella olleet NOIN humalassa speksatessaan rajapintoja... (http://xkcd.com/323/)

No helpotuksena(?) pätkä koodia jolla atribuutit voi hanskata (pahoittelut kielestä - en jaksa ainakaan nyt kääntää suomeksi, jos joku muu viitsii, niin otan kiitollisena päivitetyn koodin vatsaan! (kommenttina tai osoitteessa Mazziesaccount@gmail.com)




#define NLMSG_BOTTOM(nlmsg) ((struct rtattr *)(((void *)(nlmsg)) + NLMSG_ALIGN((nlmsg)- > nlmsg_len)))


static int addAttr(struct nlmsghdr *nl_req, int attrlabel, const void *data, int datalen)
{
    struct rtattr *attr=NLMSG_BOTTOM(nl_req));
    unsigned int attrlen=RTA_LENGTH(datalen); /* sizeof(struct rtattr) + datalen + align */
    if(NULL==nl_req || (datalen > 0 && NULL==data))
    {
        printf("NULL arg detected!");
        return -1;
    }
    attr- > rta_type=attrlabel;
    attr- > rta_len=attrlen;
    memcpy(RTA_DATA(attr),data,datalen);

    nl_req- > nlmsg_len=NLMSG_ALIGN(nl_req- > nlmsg_len)+RTA_ALIGN(attrlen);
    return 0;
}


static struct rtattr * addNestedAttr(struct nlmsghdr *nl_req, int attrlabel)
{
    struct rtattr *nested = NLMSG_BOTTOM(nl_req);

    if(!addAttr(nl_req, attrlabel, NULL, 0))
        return nested;
    return NULL;
}

static void endNestedAttr(struct nlmsghdr *nl_req, struct rtattr *nested)
{
    nested- > rta_len = (void *)NLMSG_BOTTOM(nl_req) - (void *)nested;
}


/* ...snip - Add attributes to the nlmsg msg */


        struct rtattr *attr1, *attr2;
        if(addAttr(msg,IFLA_LINK,&orig_ifindex,sizeof(int)))
        {
            printf("IFLA_LINK %d adding as rtattr to req failed!",orig_ifindex);
            retval=-1;
        }
        else if(addAttr(msg,IFLA_IFNAME,ifname,strlen(ifname)))
        {
            printf("IFLA_IFNAME %s adding as rtattr to req failed!",ifname);
            retval=-1;
        }
        else if(NULL==(attr1=addNestedAttr(msg,IFLA_LINKINFO)))
        {
            printf("addNestedAttr IFLA_LINKINFO FAILED!");
            retval=-1;
        }
        else if(addAttr(msg,IFLA_INFO_KIND,"vlan", strlen("vlan")))
        {
            printf("IFLA_INFO_KIND \"vlan\" adding FAILED!");
         retval=-1;
        }
        else if(NULL==(attr2=addNestedAttr(msg,IFLA_INFO_DATA)))
        {
            printf("addNestedAttr IFLA_INFO_DATA FAILED!");
            retval=-1;
        }
        else if(addAttr(msg,IFLA_VLAN_ID,&vlanid,sizeof(unsigned short)))
        {
            printf("IFLA_VLAN_ID %hu adding as rtattr to req failed!",vlanid);
            retval=-1;
        }
        else
        {
            endNestedAttr(msg,attr2);
            endNestedAttr(msg,attr1);
            printf("VLAN ID %hu, orig ifindex %d and new ifname %s added as attrs",vlanid,orig_ifindex,ifname);
        }


Tämä koodi olettaa että nlmsghdr structissa oleva viestin pituus on ajantasalla. Ts, ensimmäistä atribuuttia lisättäessä, pituuden on oltava nlmsghdr struktin koko + requesti spesifisen structin koko. Jokainen attribuutin lisäys kutsu edelleen päivittää tätä pituuskenttää. (kts. makro NLMSG_BOTTOM() )

Nyt alkaa tämän lyhyen intron loppu häämöttää. Esitän vielä oikeaa/pseudokoodia jotta messujen lähetyksen/vastaanoton idea tulisi selväksi.



struct sockaddr_nl kernproc;
struct msghdr msg;
struct nlmsghdr *netlinkresp;
struct iovec iov;

memset(&msg,0,sizeof(msg));

memset(&kernproc,0,sizeof(kernproc));
memset(&iov,0,sizeof(iov));

kernproc.nl_family = AF_NETLINK;

msg.msg_name=(void *)&kernproc;
msg.msg_namelen=sizeof(kernproc);

netlinkresp= < vastaukselle allokoitu bufferi > ;

/* Lisää NLMSG_F_ACK jos vastausta ei ole odotettavissa */

iov.iov_base=(void *)netlinkresp;
iov.iov_len= < nlmsg_len > ;

msg.msg_iov=&iov;
msg.msg_iovlen = 1; /* vain yksi iov structi */
iov.iov_len= < vastausbufferin koko >


retry:
    retval=recvmsg(sock, &msg, 0);
    if(0 > =retval)
    {
        if(errno==EINTR)
            goto retry;
        else
        {
            printf("Error when receiving from netlink sock!");
            /* handle error */
        }
    }
    else
    {
        /* ...reply received */
    }



Siis
1. Tarkista vastauksen pituus recv:n paluuarvosta - älä ylitä sitä.
2. Tarkista msghdr (EI SIIS nlmsghdr) nähdäksesi sopiko koko viesti vastaanottobufferiin.
    if(msg.msg_flags&MSG_TRUNC)
     ... varaa lisää tilaa ja yritä uudelleen...
3. Tallenna pointteri nlmsghdr messun headeriin.
4. aloita silmukka ja tarkista että messu on ok NLMSG_OK() makrolla. Jos ei, sinulla ei ole mitään hanskattavaa.
5. Tarkista nlmsg:n tyyppi, ja pituus. jos pituus on pidempi tai yhtäsuuri kuin nlmsg+tyyppi specifinen headeri, käytä
NLMSG_DATA makroa saadaksesi pointterin varsinaiseen messuun. Castaa ja tallenna pointteri.
5. Tarkista informaatio jota olit vailla. jos koko messu ei ole vielä käsitelty (pituus), silloin perässä on todennäköisesti attribuuttijoukkio...
6. Ota pointteri ensimmäiseeb attribuuttiin lisäämällä messu spesifisen structin koko to NLMSG_DATA();n palauttamaan osoittimeen.
7. aloita silmukka ja tarksista atribuutti RTA_OK() makrolla. Jos testi epäonnistuu, hyppää kohtaan 9
8. Tarkista attribuutin tyyppi ja data.
RTA_NEXT - > lopeta silmukka ja hyppää kohtaan 7.
9. Kun viimeinen attribuutti on tarkistettu, katso onko lippu NLM_F_MULTI asetettu nlmsghdr headerissa, ja että onko viimeisen viestin tyyppi NLMSG_DONE jos ei, hae seuraava nlmsg makrolla NLMSG_NEXT() ja hyppää uudelleen silmukkaan kohtaan 4







Viestin lähetys käyttää samaa generistä iovec mekanismia:

    struct sockaddr_nl kernproc;
    struct msghdr msg;
    struct nlmsghdr *netlinkreq;
    struct iovec iov;

    memset(&msg,0,sizeof(msg));this- > mypid
            
    memset(&kernproc,0,sizeof(kernproc));
    memset(&iov,0,sizeof(iov));

    kernproc.nl_family = AF_NETLINK;

    msg.msg_name=(void *)&kernproc;
    msg.msg_namelen=sizeof(kernproc);

    netlinkreq= < pointteri varattuun ja täytettyyn viestiin > ;

    netlinkreq- > nlmsg_pid= (pthread_self() < < 16|getpid());
    netlinkreq- > nlmsg_seq= atomicallyIncrementSeqId(seqid);
    
    #ifdef debug
    debugprint_msg(netlinkreq);
    #endif

    iov.iov_base=(void *)netlinkreq;
    iov.iov_len=netlinkreq- > nlmsg_len;

    msg.msg_iov=&iov;
    msg.msg_iovlen = 1; /* vain yksi iov struct */

    retval =sendmsg(sock,&msg,0);
    if(retval < =0)
    {
        printf("sendmsg() FAILED!");

    }
    return retval;


Debuggiprinttifunktioni läpikäy lähetettävän viestin seuraavalla koodilla:

    if(netlinkreq- > nlmsg_flags & NLM_F_ACK)
    {
        printf("Msg contains f_ack!");
    }
    if(!NLMSG_OK(netlinkreq,netlinkreq- > nlmsg_len))
    {
        printf("Looks like we're sending invalid nlmsg!! NLMSG_OK() == false at send!");
    }
    else
    {
        printf
        (
            "sending NLMSG: len %u, type %hu, flags %hu, seq %u pid %u",
            netlinkreq- > nlmsg_len,
            netlinkreq- > nlmsg_type,
            netlinkreq- > nlmsg_flags,
            netlinkreq- > nlmsg_seq,
            netlinkreq- > nlmsg_pid
        );
        switch(netlinkreq- > nlmsg_type)
        {
            case RTM_NEWROUTE:
            case RTM_DELROUTE:
            case RTM_GETROUTE:
            {
                printf
                (
                    "Req is route req (new %u, del %u, get %u)",
                    RTM_NEWROUTE,
                    RTM_DELROUTE,
                    RTM_GETROUTE
                );
                printf
                (
                    "family %u, dstlen %u, srclen %u, tos %u, table %u, proto %u, scope %u, type %u, flags %u",
                    (unsigned int)((struct rtmsg *) NLMSG_DATA(netlinkreq) )- > rtm_family,
                    (unsigned int)((struct rtmsg *) NLMSG_DATA(netlinkreq) )- > rtm_dst_len,
                    (unsigned int)((struct rtmsg *) NLMSG_DATA(netlinkreq) )- > rtm_src_len,
                    (unsigned int)((struct rtmsg *) NLMSG_DATA(netlinkreq) )- > rtm_tos,
                    (unsigned int)((struct rtmsg *) NLMSG_DATA(netlinkreq) )- > rtm_table,
                    (unsigned int)((struct rtmsg *) NLMSG_DATA(netlinkreq) )- > rtm_protocol,
                    (unsigned int)((struct rtmsg *) NLMSG_DATA(netlinkreq) )- > rtm_scope,
                    (unsigned int)((struct rtmsg *) NLMSG_DATA(netlinkreq) )- > rtm_type,
                    (unsigned int)((struct rtmsg *) NLMSG_DATA(netlinkreq) )- > rtm_flags
                );
                {
                    int len=netlinkreq- > nlmsg_len;
                    struct rtattr *at=(struct rtattr *)((char *)NLMSG_DATA(netlinkreq)+sizeof(struct rtmsg));
                    while(NULL!=at && RTA_OK(at,len))
                    {
                        char tmp[100];
                        switch(at- > rta_type)
                        {
                            case RTA_DST:
                                printf
                                (
                                    "dst is set to %s",
                                    inet_ntop
                                    (
                                        (at- > rta_len > 8)?AF_INET6:AF_INET,
                                        RTA_DATA(at),
                                        tmp,
                                        100
                                    )
                                );
                                break;
                            case RTA_SRC:
                                 printf
                                (
                                    "src is set to %s",
                                    inet_ntop
                                    (
                                        (at- > rta_len > 8)?AF_INET6:AF_INET,
                                        RTA_DATA(at),
                                        tmp,
                                        100
                                    )
                                );
                                break;
                            case RTA_GATEWAY:
                                printf
                                (
                                    "gw is set to %s",
                                    inet_ntop
                                    (
                                        (at- > rta_len > 8)?AF_INET6:AF_INET,
                                        RTA_DATA(at),
                                        tmp,
                                        100
                                    )
                                );
                                break;
                            case RTA_OIF:
                                printf
                                (
                                    "OIF is set to %u",
                                    *(unsigned int *)RTA_DATA(at)
                                );
                                break;
                            case RTA_PRIORITY:
                                printf
                                (
                                    "Priority is set to %u",
                                    *(unsigned int *)RTA_DATA(at)
                                );
                                break;
                            default:
                                printf("rta_type %u, len %u",at- > rta_type,at- > rta_len);
                                break;
                        }
                        at=RTA_NEXT(at,len);
                    }
                }
                break;
            }
            case RTM_NEWADDR:
            case RTM_GETADDR:
            case RTM_DELADDR:
                printf
                (
                    "Req is ADDR req (new %u, del %u, get %u)",
                    RTM_NEWADDR,
                    RTM_DELADDR,
                    RTM_GETADDR
                );
                printf
                (
                    "ifa_family %u, ifa_prefixlen %u, ifa_flags %u, ifa_scope %u, ifa_index %d",
                    (unsigned int)((struct ifaddrmsg *) NLMSG_DATA(netlinkreq) )- > ifa_family,
                    (unsigned int)((struct ifaddrmsg *) NLMSG_DATA(netlinkreq) )- > ifa_prefixlen,
                    (unsigned int)((struct ifaddrmsg *) NLMSG_DATA(netlinkreq) )- > ifa_flags,
                    (unsigned int)((struct ifaddrmsg *) NLMSG_DATA(netlinkreq) )- > ifa_scope,
                    (int)((struct ifaddrmsg *) NLMSG_DATA(netlinkreq) )- > ifa_index
                );
                {
                    int len=netlinkreq- > nlmsg_len;
                    struct rtattr *at=(struct rtattr *)((char *)NLMSG_DATA(netlinkreq)+sizeof(struct ifaddrmsg));
                    while(NULL!=at && RTA_OK(at,len))
                    {
                        char tmp[100];
                        switch(at- > rta_type)
                        {
                            case IFA_ADDRESS:
                                printf
                                (
                                    "IFA_ADDRESS is set to %s",
                                    inet_ntop
                                    (
                                        (at- > rta_len > 8)?AF_INET6:AF_INET,
                                        RTA_DATA(at),
                                        tmp,
                                        100
                                    )
                                );
                                break;
                            case IFA_LOCAL:
                                printf
                                (
                                    "IFA_LOCAL is set to %s",
                                    inet_ntop
                                    (
                                        (at- > rta_len > 8)?AF_INET6:AF_INET,
                                        RTA_DATA(at),
                                        tmp,
                                        100
                                    )
                                );
                                break;
                            case IFA_BROADCAST:
                                printf
                                (
                                    "IFA_BROADCAST is set to %s",
                                    inet_ntop
                                    (
                                        (at- > rta_len > 8)?AF_INET6:AF_INET,
                                        RTA_DATA(at),
                                        tmp,
                                        100
                                    )
                                );
                                break;
                            case IFA_LABEL:
                                printf
                                (
                                    "IFA_LABEL is set to '%s'",
                                    (char *)RTA_DATA(at)
                                );
                                break;
                            case IFA_ANYCAST:
                                printf
                                (
                                    "IFA_ANYCAST is set to %s",
                                    inet_ntop
                                    (
                                        (at- > rta_len > 8)?AF_INET6:AF_INET,
                                        RTA_DATA(at),
                                        tmp,
                                        100
                                    )
                                );
                                break;
                            default:
                                printf("rta_type %u, len %u",at- > rta_type,at- > rta_len);
                                break;
                        }
                        at=RTA_NEXT(at,len);
                    }
                }
                break;
.
./* Tänne voi lisätä uusia viestityyppejä */
        }
.
.
.



Tähän loppuu lyhyt introni netlink viestien maailmaan. Lisäinfoa löytyy mm. man sivuilta man 7 rtnetlink ja man 3 netlink

Jos sattuu että ĺöysit jotain hyödyllistä täältä, tai jos sinulla on jotain
parannusehdotuksia / suomenkielisiä koodiesimerkkejä, niin nakkaa minua
viestillä täällä tai osoitteessa Mazziesaccount@gmail.com

Ja jos käytät koodiani jossain, tai jos julkaiset sitä netissä, niin ole
ystävällinen ja mainitse minut (Maz) ja tämä sivusto lähteenäsi =)

Have fun!

1 kommentti:

Maz kirjoitti...

voit myös ladata nsn (network state notifier) projektini lähdekoodit. Siellä on helpohkosti ymmärrettävä esimerkki netlink sokettien käytöstä. Nsn myös tulostaa ruudulle yksityiskohtaista tietoa netolink sokettien kautta tulevista viesteistä. Löydät nsn:n esittelypostini blogini sisällysluettelosta. Postissa on linkki kehitys repositoryyn josta voit ladata lähdekoodit.