コンテンツにスキップ

13 08 data extraction

The P4 core library contains the following declaration of a built-in extern type called packet_in that represents incoming network packets. The packet_in extern is special: it cannot be instantiated by the user explicitly. Instead, the architecture supplies a separate instance for each packet_in argument to a parser instantiation.

extern packet_in {
    void extract<T>(out T headerLvalue);
    void extract<T>(out T variableSizeHeader, in bit<32> varFieldSizeBits);
    T lookahead<T>();
    bit<32> length();  // This method may be unavailable in some architectures
    void advance(bit<32> bits);
}

To extract data from a packet represented by an argument b with type packet_in, a parser invokes the extract methods of b. There are two variants of the extract method: a one-argument variant for extracting fixed-size headers, and a two-argument variant for extracting variable-sized headers. Because these operations can cause runtime verification failures (see below), these methods can only be executed within parsers.

When extracting data into a bit-string or integer, the first packet bit is extracted to the most significant bit of the integer.

Some targets may perform cut-through packet processing, i.e., they may start processing a packet before its length is known (i.e., before all bytes have been received). On such a target calls to the packet_in.length() method cannot be implemented. Attempts to call this method should be flagged as errors (either at compilation time by the compiler back-end, or when attempting to load the compiled P4 program onto a target that does not support this method).

In terms of the ParserModel, the semantics of packet_in can be captured using the following abstract model of packets:

packet_in {
    unsigned nextBitIndex;
    byte[] data;
    unsigned lengthInBits;
    void initialize(byte[] data) {
        this.data = data;
        this.nextBitIndex = 0;
        this.lengthInBits = data.sizeInBytes * 8;
    }
    bit<32> length() { return this.lengthInBits / 8; }
}

Fixed-width extraction

The single-argument extract method handles fixed-width headers, and is declared in P4 as follows:

void extract<T>(out T headerLeftValue);

The expression headerLeftValue must evaluate to an l-value (see Section L-values) of type header with a fixed width. If this method executes successfully, on completion the headerLvalue is filled with data from the packet and its validity bit is set to true. This method may fail in various ways—e.g., if there are not enough bits left in the packet to fill the specified header.

For example, the following program fragment extracts an Ethernet header:

struct Result { Ethernet_h ethernet;  /* more fields omitted */ }
parser P(packet_in b, out Result r) {
state start {
    b.extract(r.ethernet);
}
}

In terms of the ParserModel, the semantics of the single-argument extract is given in terms of the following pseudo-code method, using data from the packet class defined above. We use the special valid$ identifier to indicate the hidden valid bit of a header, isNext$ to indicate that the l-value was obtained using next, and nextIndex$ to indicate the corresponding header or header union stack properties.

void packet_in.extract<T>(out T headerLValue) {
   bitsToExtract = sizeofInBits(headerLValue);
   lastBitNeeded = this.nextBitIndex + bitsToExtract;
   ParserModel.verify(this.lengthInBits >= lastBitNeeded, error.PacketTooShort);
   headerLValue = this.data.extractBits(this.nextBitIndex, bitsToExtract);
   headerLValue.valid$ = true;
   if headerLValue.isNext$ {
     verify(headerLValue.nextIndex$ < headerLValue.size, error.StackOutOfBounds);
     headerLValue.nextIndex$ = headerLValue.nextIndex$ + 1;
   }
   this.nextBitIndex += bitsToExtract;
}

Variable-width extraction

The two-argument extract handles variable-width headers, and is declared in P4 as follows:

void extract<T>(out T headerLvalue, in bit<32> variableFieldSize);

The expression headerLvalue must be an l-value representing a header that contains exactly one varbit field. The expression variableFieldSize must evaluate to a bit<32> value that indicates the number of bits to be extracted into the unique varbit field of the header (i.e., this size is not the size of the complete header, just the varbit field).

In terms of the ParserModel, the semantics of the two-argument extract is captured by the following pseudo-code:

void packet_in.extract<T>(out T headerLvalue,
                          in bit<32> variableFieldSize) {
   // targets are allowed to include the following line, but need not
   // verify(variableFieldSize[2:0] == 0, error.ParserInvalidArgument);
   bitsToExtract = sizeOfFixedPart(headerLvalue) + variableFieldSize;
   lastBitNeeded = this.nextBitIndex + bitsToExtract;
   ParserModel.verify(this.lengthInBits >= lastBitNeeded, error.PacketTooShort);
   ParserModel.verify(bitsToExtract <= headerLvalue.maxSize, error.HeaderTooShort);
   headerLvalue = this.data.extractBits(this.nextBitIndex, bitsToExtract);
   headerLvalue.varbitField.size = variableFieldSize;
   headerLvalue.valid$ = true;
   if headerLValue.isNext$ {
     verify(headerLValue.nextIndex$ < headerLValue.size, error.StackOutOfBounds);
     headerLValue.nextIndex$ = headerLValue.nextIndex$ + 1;
   }
   this.nextBitIndex += bitsToExtract;
}

The following example shows one way to parse IPv4 options—by splitting the IPv4 header into two separate headers:

// IPv4 header without options
header IPv4_no_options_h {
   bit<4>   version;
   bit<4>   ihl;
   bit<8>   diffserv;
   bit<16>  totalLen;
   bit<16>  identification;
   bit<3>   flags;
   bit<13>  fragOffset;
   bit<8>   ttl;
   bit<8>   protocol;
   bit<16>  hdrChecksum;
   bit<32>  srcAddr;
   bit<32>  dstAddr;
}
header IPv4_options_h {
   varbit<320> options;
}

struct Parsed_headers {
    // Some fields omitted
    IPv4_no_options_h ipv4;
    IPv4_options_h    ipv4options;
}

error { InvalidIPv4Header }

parser Top(packet_in b, out Parsed_headers headers) {
   // Some states omitted

   state parse_ipv4 {
       b.extract(headers.ipv4);
       verify(headers.ipv4.ihl >= 5, error.InvalidIPv4Header);
       transition select (headers.ipv4.ihl) {
           5: dispatch_on_protocol;
           _: parse_ipv4_options;
       }
   }

   state parse_ipv4_options {
       // use information in the ipv4 header to compute the number of bits to extract
       b.extract(headers.ipv4options,
                 (bit<32>)(((bit<16>)headers.ipv4.ihl - 5) * 32));
       transition dispatch_on_protocol;
   }
}

Lookahead

The lookahead method provided by the packet_in packet abstraction evaluates to a set of bits from the input packet without advancing the nextBitIndex pointer. Similar to extract, it will transition to reject and set the error if there are not enough bits in the packet. When lookahead returns a value that contains headers (e.g., a header type, or a struct containing headers), the headers values in the returned result are always valid (otherwise lookahead must have transitioned to the reject state).

The lookahead method can be invoked as follows:

b.lookahead<T>()

where T must be a type with fixed width. In case of success the result of the evaluation of lookahead returns a value of type T.

In terms of the ParserModel, the semantics of lookahead is given by the following pseudocode:

T packet_in.lookahead<T>() {
   bitsToExtract = sizeof(T);
   lastBitNeeded = this.nextBitIndex + bitsToExtract;
   ParserModel.verify(this.lengthInBits >= lastBitNeeded, error.PacketTooShort);
   T tmp = this.data.extractBits(this.nextBitIndex, bitsToExtract);
   return tmp;
}

The TCP options example from Section Operations on header unions also illustrates how lookahead can be used:

state start {
transition select(b.lookahead<bit<8>>()) {
    0: parse_tcp_option_end;
    1: parse_tcp_option_nop;
    2: parse_tcp_option_ss;
    3: parse_tcp_option_s;
    5: parse_tcp_option_sack;
}
}

// Some states omitted

state parse_tcp_option_sack {
bit<8> n = b.lookahead<Tcp_option_sack_top>().length;
b.extract(vec.next.sack, (bit<32>) (8 * n - 16));
transition start;
}

Skipping bits

P4 provides two ways to skip over bits in an input packet without assigning them to a header:

One way is to extract to the underscore identifier, explicitly specifying the type of the data:

b.extract<T>(_)

Another way is to use the advance method of the packet when the number of bits to skip is known.

In terms of the ParserModel, the meaning of advance is given in pseudocode as follows:

void packet_in.advance(bit<32> bits) {
   // targets are allowed to include the following line, but need not
   // verify(bits[2:0] == 0, error.ParserInvalidArgument);
   lastBitNeeded = this.nextBitIndex + bits;
   ParserModel.verify(this.lengthInBits >= lastBitNeeded, error.PacketTooShort);
   this.nextBitIndex += bits;
}