Jazyk P4 jako budoucnost SDN (dokončení)

27. 1. 2016
Doba čtení: 10 minut

Sdílet

Jazyk P4 je moderní programovací jazyk, který se používá pro abstraktní definici síťového zařízení. Díky němu je možné vyřešit nevýhody aktuální implementace SDN, protokolu OpenFlow, mezi jehož nevýhody patří omezená rozšiřitelnost zpracování paketů nebo počet podporovaných protokolů.

předchozím článku jsme se zmínili o konceptu SDN, který slibuje efektivnější a lehčí správu pokročilé síťové infrastruktury. Aktuální implementace, protokol OpenFlow, má však i nevýhody, které jsou spojeny s rozšiřitelností zpracování paketů nebo počtem podporovaných protokolů. Tento problém se snaží vyřešit jazyk P4, na který se podíváme dnes.

Jazyk P4: budoucnost nastává

P4 (Programming Protocol-independent Packet Processors)  je vysokoúrovňový jazyk pro programování protokolově nezávislých síťových procesorů, který je nedávným příspěvkem do SDN ekosystému. Jeho hlavním úkolem je možnost volně definovat zpracování paketů v síťových zařízeních. Klade důraz nejen na programovatelnost, ale také protokolovou a platformní nezávislost. P4 rozlišuje dva režimy práce zařízení: konfiguraci a normální provoz (viz obrázek). Během konfigurace se do zařízení nahrává program v jazyce P4 a interně se kompiluje do reprezentace, která je pro dané zařízení nejvhodnější. To znamená, že program v jazyce P4 je možné přeložit pro běh na prakticky libovolném síťovém zařízení (které podporuje P4), bez ohledu na jeho vnitřní architekturu (např. CPU, GPU, síťový procesor, hradlové pole FPGA, …). Po naprogramování zařízení se přejde do normálního provozu, ve kterém je režim práce podobný OpenFlow – je zde opět centrální kontrolér, který řídí všechna zařízení prostřednictvím plnění tabulek.

Režimy P4 zařízení

Vlastní síťové zařízení můžeme v P4 popsat pomocí definice pěti základních operací síťového zařízení:

  1. Specifikace formátu hlaviček protokolů, které bude síťové zařízení podporovat.

  2. Specifikace procesu parsování. V jazyce P4 se pro specifikaci parsování používá konečný automat, kterým je možném vyjádřit další postup zpracování v závislosti na hodnotách políček v aktuálně zpracovávaném protokolu.

  3. Specifikace tabulek, které mapují toky na akce. Mají podobnou funkcionalitu jako flow tabulky v OpenFlow, ale mají větší volnost ohledně specifikace spouštených akcí. Navíc přináší i více různých režimů pro proces porovnávání extrahovaných dat s hodnotami v tabulce. Máme tedy dostupné režimy jako LPM (Longest Prefix Match), maskování (stejné jako v případě OpenFlow), definovaný rozsah hodnot a jiné. Každý záznam v tabulce spouští právě jednu akci.

  4. Specifikace akcí, které se budou provádět nad daným tokem. Samotný jazyk P4 obsahuje určitou množinu takzvaných primitivních akcí, které se poté mapují na dostupné zdroje dané cílové platformy. Jazyk umožňuje volat i uživatelsky definované akce, které se většinou skládají z primitivních a dalších uživatelských akcí.

  5. Specifikace kontrolního programu dává vše předchozí dohromady. Především umožňuje definování kriterií, která se použijí pro výběr následující tabulky. Jsou dostupné i různé režimy jako je výběr podle spuštěné akce, výběr na základě nalezení/nenalezení záznamu v tabulce, atd.

Těchto základních pět operací můžeme identifikovat v každém síťovém zařízení. Následující text se bude detailněji věnovat jednotlivým částem tohoto jazyka.

Specifikace formátu hlaviček

Deklarace hlavičky je velmi podobná deklaraci struktury v jazyce C. V jazyce P4 se jednotlivé části hlavičky zapisují ve formátu název : šířka v bitech;. Jako příklad můžeme uvést deklaraci ethernetové hlavičky:

header_type ethernet_t {
    fields {
        dstAddr : 48;
        srcAddr : 48;
        etherType : 16;
    }
}

Předchozí příklad ukazuje definici hlavičky, kde jsou předem známé velikosti jednotlivých políček a celkovou délku je tedy možné vypočítat jako sumu všech jejích členů. Tento přístup však není možný u protokolů, které přímo v hlavičce obsahují celkovou délku – například rozšiřující hlavičky IPv6 protokolu. Jazyk P4 myslí i na tyto případy a programátor tak může definovat vzorec, který se použije pro výpočet délky pomocí klíčového slova length. Jako příklad můžeme uvést deklaraci rozšiřující hlavičky IPv6:

header_type ipv6_ext_t {
    fields {
        nextHdr     : 8;
        totalLen    : 8;
        frag        : 12;
        padding     : 3;
        fragLast    : 1;
    }
    length : (totalLen + 1) * 8;
    max_length : 1024; // Bytes
}

Specifikace parsování

Proces parsování je v jazyce P4 popsán pomocí konečného automatu. Ten se typicky skládá z definice stavů a přechodů do následujících stavů. Vše důležité vysvětlíme na následující ukázce:

header ethernet eth;
parser ethernet {
   extract(eth);
   switch(eth.ethertype) {
      case 0xAB00: ingress_control;
      case 0x8100: vlan;
      case 0x9100: vlan;
      case 0x800: ipv4;
      case 0xA100 mask 0xF100 : myProto;
   }
}

Klíčové slovo header slouží pro deklaraci proměnné eth. Dále se definuje stav parseru s názvem ethernet, kde se provádí samotná extrakce dat do proměnné eth (klíčové slovo extract) a přechod do nového stavu. Pro samotný přechod se používají vyparsovaná data, která jsou použita jako parametr do switch konstrukce. Ten může obsahovat více políček, které jsou odděleny operátorem “,” a tvoří tak jeden binární vektor. Ten je následně použit pro porovnání s hodnotou v case.

Mohou však nastat i situace, kdy nechceme použít exaktní hodnotu z aktuálně zpracovávaného paketu. Jazyk P4 tento problém řeší pomocí klíčového slova mask. V tomto případě se použije aktulního hodnota, provede se logický součin s maskou a výsledek se porová s hodnotou vlevo. V našem případě se použije maska s hodnotou 0×F100 a výsledek se porovnává s hodnotou 0×A100.

Následujícím stavem nemusí být pouze parser (v našem případě vlan,ipv4 nebo myProto), ale také kontrolní program (v našem případě ingress_control). V takovém případě je parsování ukončeno a spuštěno následující zpracování v tabulkách.

Vstupním bodem celého P4 programu je vždy parser s názvem start, který může vypadat následovně:

parser start {
    return parse_eth;
}

Specifikace tabulek

Tabulka slouží k mapování vyparsovaných políček na danou akci (anglicky match+action). Při specifikaci tabulky se uvádí:

  1. Políčka hlaviček protokolů, která se použíjí pro identifikaci nejvhodnějšího záznamu v tabulce + jejich algoritmy na porovnávání hodnot. Zápis je ve formátu políčko : algoritmus;

  2. Seznam podporovaných akcí, které se mohou po nalezení záznamu v tabulce spustit.

Ukázka zápisu tabulky v jazyce P4:

table filter {
    reads {
        ipv4.srcAddr : lpm;
        tcp.dstPort  : exact;
    }
    actions {
        PushVlan;
        _permit;
        NoOp;
    }
}

Jednotlivá políčka, na která bude tabulka reagovat, se udávají do části reads. Samotný seznam možných akcí je pak uveden v části actions. V případě nalezení záznamu v tabulce se spustí právě jedna akce.

Tabulky jsou po zapnutí zařízení prázdné a záznamy musí být založeny při běhu.

Specifikace akcí

Jazyk P4 definuje množinu primitivních akcí, které mohou být použity pro konstrukci složitějších uživatelsky definovaných akcí. Následující ukázka byla převzata:

action add_mTag(up1, up2, down1, down2, egr_spec) {
    add_header(mTag);
    // Copy VLAN ethertype to mTag
    copy_field(mTag.ethertype, vlan.ethertype);

   // Set VLAN’s ethertype to signal mTag
  set_field(vlan.ethertype, 0xaaaa);
  set_field(mTag.up1, up1);
  set_field(mTag.up2, up2);
  set_field(mTag.down1, down1);
  set_field(mTag.down2, down2);
  // Set the destination egress port as well
  set_field(metadata.egress_spec, egr_spec);
}

Akci si můžeme představit jako funkci v jazyce C (jejich zápis je poměrně podobný). Stejným rysem je i možnost předání parametrů. V jazyce P4 jsou parametry vyčteny z tabulky a předány akci, která byla popsána v předchozí části textu (tyto parametry jsou vloženy v průběhu zakládání záznamu).

V naší ukázce se za VLAN hlavičku vloží mTag (uživatelsky definovaná hlavička), zkopíruje se hodnota políčka ethertype (z VLAN tagu) do stejného políčka v mTag (aby bylo jasné, co bude následovat). Poté se políčko ethertype u VLAN tagu nastaví na hodnotu 0×aaaa, aby se signalizovala přítomnost mTag hlavičky. Další řádky kódu poté nastavují další políčka protokolu mTag a definují výstupni port (políčko metadata.egress_spec). Akce add_header, copy_field a set_field patří mezi primitivní akce. Jazyk P4 však obsahuje větší počet takových základních funkcí. Jejich kompletní seznam je možné nalézt v kompletní specifikaci jazyka.

Specifikace kontrolního programu

V této fázi máme definován proces parsování, uživatelské akce a tabulky. Posledním krokem je tak specifikace využívání jednotlivých tabulek. Tohoto můžeme docílit v jazyce P4 specifikací control bloku. Následující ukázka byla převzata z P4C-GRAPH:

control main() {
    // Verify mTag state and port are consistent
    apply(source_check);
    // If no error from source_check, continue
    if (!defined(metadata.ingress_error)) {
        // Attempt to switch to end hosts
        apply(local_switching);
        if (!defined(metadata.egress_spec)) {
            // Not a known local host; try mtagging
            apply(mTag_table);
        }
        // Check for unknown egress state or
        // bad retagging with mTag.
        apply(egress_check);
    }
}

Spouštění jednotlivých tabulek je v případě jazyka P4 realizována pomocí klíčového slova apply. Uvedený příklad slouží pouze jako ukázka definice této části programu. Pro lepší pochopení si v následující části textu uvedeme jednoduchý příklad firewallu v jazyce P4.

Popis jednoduchého firewallu v jazyce P4

Na ukázku jsme si vybrali popis primitivního firewallu, který zahodí veškerý síťový tok, který nebyl povolen. Pro rozhodování o osudu paketu budeme využívat zdrojovou IPv4 adresu a cílový TCP port. Validní tok budeme posílat na výstup, který bude definován v egress_spec. Tento parametr akce se bude zadávat při vytváření záznamu tabulky. Celý P4 program musíme vždy začít definicí parseru a potřebných protokolů. Začneme tedy deklarací hlaviček:

// Here's an ethernet header to get started.
//Ethernet --------------------------------------
header_type ethernet_t {
    fields {
        dstAddr : 48;
        srcAddr : 48;
        etherType : 16;
    }
}
//IPv4 ------------------------------------------
header_type ipv4_t {
    fields {
        version : 4;
        ihl : 4;
        diffserv : 8;
        totalLen : 16;
        identification : 16;
        flags : 3;
        fragOffset : 13;
        ttl : 8;
        protocol : 8;
        hdrChecksum : 16;
        srcAddr : 32;
        dstAddr: 32;
    }
}
//TCP -------------------------------------------
header_type tcp_t {
    fields {
        srcPort : 16;
        dstPort : 16;
        seqNo : 32;
        ackNo : 32;
        dataOffset : 4;
        res : 4;
        flags : 8;
        window : 16;
        checksum : 16;
        urgentPtr : 16;
    }
}

Nyní budeme pokračovat specifikací parseru:

#define IPV4_TYPE 0x0800
header ethernet_t ethernet;
parser parse_eth {
    extract(ethernet);
    return select (ethernet.etherType) {
        IPV4_TYPE : parse_ipv4;
        default : ingress;
    }
}

// Define parsing for the IPv4
#define IP_PROTOCOLS_TCP 6
header ipv4_t ipv4;
parser parse_ipv4 {
    extract(ipv4);
    return select(latest.fragOffset, latest.protocol) {
        IP_PROTOCOLS_TCP : parse_tcp;
        default: ingress;
    }
}

// Define parsing for the TCP
header tcp_t tcp;
parser parse_tcp {
    extract(tcp);
    return ingress;
}


// Entry point ....
parser start {
    return parse_eth;
}

Dále můžeme pokračovat specifikací tabulek a akcí:

action _permit(egress_spec) {
       modify_field(standard_metadata.egress_spec, egress_spec);
}

action _drop() {
    drop();
}

// Filter table. Reacts on source IP address and destination TCP port
table filter {
    reads {
        ipv4.srcAddr            : lpm;
        tcp.dstPort             : exact;
    }
    actions {
        _permit;
    }
}

//Drop table (table without read block => start the action)
table drop {
     actions{
        _drop;
    }
}

control ingress {
    // Try to find the rule in the filter table
    apply(filter) {
       // In the case of table miss … destroy the packet!
        miss {apply(drop);}
    }
}

Tento příklad je sice velmi jednoduchý, ale ukazuje možnost vytvoření vlastního P4 zařízení, které bude dělat jenom to, co mu naprogramujete. V případě dostupnosti překladače kódu z P4 do VHDL si tak například můžete vytvořit hardwarové zařízení bez znalosti a zkušeností hardwarového návrhu. Jazyk P4 je v tomto ohledu velmi jednoduchý (až primitivní) a naučíte se ho v řádu pár dní.

Dostupné nástroje

Skupina vývojářů kolem jazyka P4 již vytvořila základní nástroje, které mohou být použity v jiných projektech. Všechny tyto nástroje jsou veřejně dostupné ve formě zdrojových kódů. Jeden z hlavních projektů je P4-HLIR. Jedná se o část P4 překladače, která vytvoří model pomocí objektů v jazyce Python. Výsledná objektová reprezentace je dostatečně dokumentována, aby programátor mohl vytvořit zbytek překladače. Můžeme tak vytvořit překladač z P4 do libovolné cílové technologie a využít k tomu již hotového lexikálního a syntaktického analyzátoru.

Ukázkou projektů, které využívají P4-HLIR, může být P4C-GRAPHS a P4C-BEHAVIORAL. Jedná se o ukázky překladače do kódu programu DOT (grafické znázornění P4 programu) a C++. Více zdrojových kódů, nástrojů a detailů je možné nalézt na stránkách projektu P4.

Využitím jazyka P4 se zabývá i CESNET. Výzkumníci z projektu Liberouter (www.liberouter.org) využili projektu P4-HLIR a vytvořili překladač, který generuje VHDL kód hardwarového parseru pro hradlové pole FPGA. Během pár sekund tak lze vygenerovat parser, který je schopen zpracovávat pakety na rychlosti nejnovějšího Ethernetového standardu 100 Gb/s. Více informací je možné získat v článku Automatic Generation of 100 Gbps Packet Parsers from P4 Description [PDF], který byl prezentován na konferenci Supercomputing 2015 v USA.

ict ve školství 24

Závěr

Zatímco technologie OpenFlow je již dnes poměrně zaběhlá a vyspělá, jazyk P4 je horkým tématem pro výzkum a vývoj kvalitativně nových typů síťových zařízení. Pokud se koncept P4 prosadí, tak nás čeká poměrně zajímavá budoucnost v oblasti počítačových sítí. Síťový hardware bude dělal přesně to, co do něj naprogramujeme. Budeme tak moci z jediné “P4 krabice” vytvořit P4 switch, router, firewall nebo analyzátor síťového toku, to vše s podporou prakticky libovolné kombinace protokolů. Syntaxe jazyka P4 je navíc poměrně jednoduchá a pochopitelná.

Pokud vás jazyk P4 zaujal, tak neváhejte a navštivte oficiální stránku P4 (www.p4.org), kde najdete projekt P4FACTORY. Ten obsahuje vše důležité, co budete pro implementaci svého softwarového P4 zařízení potřebovat. V tomto balíku jsou dostupné i zdrojové kódy, které můžete využít jako inspiraci ke svému vlastnímu projektu.

Autor článku

Pracuje jako výzkumník v hardwarovém oddělení projektu Liberouter ve sdružení CESNET. Navrhuje architektury a využití vysokoúrovňové syntézy pro zpracování vysokorychlostního síťového provozu.

Pracuje jako výzkumník a zástupce vedoucího projektu Liberouter ve sdružení CESNET. Navrhuje algoritmy zpracování vysokorychlostního síťového provozu a jejich mapování na výpočetní prostředky.