Skip to content

Adding a New Match Field

Fifate edited this page Oct 11, 2021 · 5 revisions

In this tutorial, you will learn how to add support for a new matching field in BOFUSS.
The idea is to add support to TCP flags, since it is not present in the OpenFlow 1.3 specification.
(Support to TCP flags was added to OpenFlow 1.5 and it is usual to receive questions if BOFUSS supports it.)

Topics

What do I need to know?
Overview of a NetPDL description
OXM Definition
Parsing the OXM Field
Extracting the TCP flags from the packet

List of files (mentioned or extended)

customnetpdl.xml file
openflow.h
oflib/oxm-match.def
oflib/oxm-match.h
oflib/oxm-match.c
nbee_link/nbee_link.ccp


What do I need to know?

  • The switch uses an external library called Netbee to perform packet parsing.
    Netbee can quite a pain when it comes to installing in newer version of Linux distros, but it is makes life easier when it comes to extending the parsing functionality of the switch.

  • There is a file named customnetpdl.xml file in the root directory of the switch. It contains the description of protocols needed by Netbee to perform packet parsing.

  • You do not need to be an expert in XML or be experienced with Netbee to be able to add a new protocol or a new field. You just need to base your code in a similar protocol already included in the file. This guide will also give an overview of the XML fields that matter for the switch. If you are not happy about it and want to know a lot about it, I do recommend checking the article that describes the NetPDL format.

  • OpenFlow Extended Match (OXM).
    The OpenFlow Extended Match (OXM) is a flexible Type-Value-Lenght format defined to support extensible match fields. Each field has a header composed of Class, Field Type, OXM Hasmask flag and the Lenght. For example, the IP source is defined as: class = 0x8000, Type = 11, HasMask = 0 and Lenght = 4.
    If the field is masked HasMask should be 1 and the Lenght of the value is 8, because there are IP and its respective mask.
    It is recommended to read the specification to make it clear you become familiar with the idea. It is the basic knowledge to create your own match field.

  • This code was not implemented at all. The idea is to have pointers to the files and necessary modifications. Let me know if something does not look right or can be improved.


Overview of a NetPDL description

A very simple and easy to understand example is UDP. The XML presented below is a full NetPDL description of the UDP fields required to parse the protocol with Netbee. Do not bother about the NetPDL field, as you will find it in the customnetpdl.xml file file. We only care about the protocol description.

<?xml version="1.0" encoding="utf-8"?>
<netpdl name="nbee.org NetPDL Database" version="0.9" creator="nbee.org" date="09-04-2007">
<protocol name="udp" longname="UDP (User Datagram protocol)" showsumtemplate="udp">
        <format>
                <fields>
                        <field type="fixed" name="sport" longname="{0x8000 15}" size="2" showtemplate="FieldDec"/>
                        <field type="fixed" name="dport" longname="{0x8000 16}" size="2" showtemplate="FieldDec"/>
                        <field type="fixed" name="len" longname="Payload length" size="2" showtemplate="FieldDec"/>
                        <field type="fixed" name="crc" longname="Checksum" size="2" showtemplate="FieldHex"/>
                </fields>
        </format>

        <visualization>
                <showsumtemplate name="udp">
                        <section name="next"/>
                        <text value="UDP: port "/>
                        <protofield name="sport" showdata="showvalue"/>
                        <text value=" => "/>
                        <protofield name="dport" showdata="showvalue"/>
                </showsumtemplate>
        </visualization>
</protocol>
</netpdl>

In the code above, the first line describing the protocol should be straightforward, as it just presents the short and long name of the protocol. For the switch the most important part is the name, as it is used to access the parsed fields.

The protocol format comes next with the description of fields. For example, the description of the source port is as follows:

<field type="fixed" name="sport" longname="{0x8000 15}" size="2" showtemplate="FieldDec"/>
  • Name is used to access the parsed field.
  • size must refelect the of the field in the protocol header, so fields are parsed correctly.
  • longname value is little hack. Previously the parsing required to specify the class of the OpenFlow match and the OXM type defined by the specification. In the case of the UDP source port, the class is 0x8000 and the type is 15. Regardless of its presence, it is not used for the parsing in the current version of the switch.

TCP flags in the NetPDL description

The TCP protocol is already fully described in the customnetpdl.xml file. Let's take a look how the description of TCP flags look like.

<field type="bit" name="flags" longname="Flags" mask="0x003F" size="2" showtemplate="FieldHex">
    <field type="bit" name="urg" longname="Urgent pointer" mask="0x0020" size="2" showtemplate="FieldBin"/>
    <field type="bit" name="ackf" longname="Ack valid" mask="0x0010" size="2" showtemplate="FieldBin"/>
    <field type="bit" name="push" longname="Push requested" mask="0x0008" size="2" showtemplate="FieldBin"/>
    <field type="bit" name="rst" longname="Reset requested" mask="0x0004" size="2" showtemplate="FieldBin"/>
    <field type="bit" name="syn" longname="Syn requested" mask="0x0002" size="2" showtemplate="FieldBin"/>
    <field type="bit" name="fin" longname="Fin requested" mask="0x0001" size="2" showtemplate="FieldBin"/>
</field>

The TCP flags require a mask field, because it is part of the 16 bits that compose the TCP data offset(4bits), reserved(3 bits). Then, come the 9 bits of the TCP flags. Masks also make it possible and easy to parse the value of each individual flag as seem above.


OXM definition

To make things easier, this tutorial will use the same values defined for the TCP flags from [OpenFlow 1.5].

Find the enum enum oxm_ofb_match_fields in openflow.h and add OFPXMT_OFB_TCP_FLAGS at the end:

/* OXM Flow match field types for OpenFlow basic class. */
enum oxm_ofb_match_fields {
    OFPXMT_OFB_IN_PORT        = 0,  /* Switch input port. */
    ...
    OFPXMT_OFB_TCP_FLAGS = 42, /* TCP flags. */
};

In the same file, add the definitions that uses the macros OXM_HEADER and OXM_HEADER_W. These macros build the regular and masked OXM header in an uint32_t, by shifting the header fields to their respective starting and positions.

/* The flags in the TCP header.
*
* Prereqs:
* OXM_OF_ETH_TYPE must be either 0x0800 or 0x86dd.
* OXM_OF_IP_PROTO must match 6 exactly.
*
* Format: 16-bit integer with 4 most-significant bits forced to 0.
*
* Masking: Bits 0-11 fully maskable. */
#define OXM_OF_TCP_FLAGS OXM_HEADER (0x8000, OFPXMT_OFB_TCP_FLAGS, 2)
#define OXM_OF_TCP_FLAGS_W OXM_HEADER_W(0x8000, OFPXMT_OFB_TCP_FLAGS, 2)

Did you notice the comments about the Prereqs of the field? OpenFlow requires some OXM fields from layer 3 and 4 to have valid protocols in the inferior layers. For example, to match IPv4 fields, one must also add the field OXM_OF_ETH_TYPE = 0x0800 to the whole match.

The switch has some macros that define the supported fields and contain the pre requisites of a match. The definitions are oflib/oxm-match.def.

Add the following definition for the non masked and masked TCP flags OXM

DEFINE_FIELD_M  (OF_TCP_FLAGS,  OXM_DL_IP_ANY,   IPPROTO_TCP,    true)

That definition simply tells the field name, the requisites (OXM_DL_IP_ANY == IPv4 or IPv6) and true says it is a maskable field.

That definition will also create the definition for the non masked OXM. For this reason the number of supported fields must be increased by two. Change the in oflib/oxm-match.h the value of NUM_OXM_FIELDS.

enum oxm_field_index {
#define DEFINE_FIELD(HEADER,DL_TYPES, NW_PROTO, MASKABLE) \
        OFI_OXM_##HEADER,
#include "oxm-match.def"
    NUM_OXM_FIELDS = 58
};

Parsing the OXM field

Search for the function parse_oxm_entry In the file oflib/oxm-match.c. Handle the masked and non masked cases for TCP flags:

case OFI_OXM_OF_TCP_FLAGS:{
    uint16_t* tcp_flags = (uint16_t*) value;
    ofl_structs_match_put16(match, f->header, ntohs(*tcp_flags));
    return 0;
}

case OFI_OXM_OF_TCP_FLAGS_W:{
     uint16_t tcp_flags = ntohs((uint16_t*) value));
     uint16_t tcp_flags_mask = ntohs(*((uint16_t*) mask));

     if (check_bad_wildcard16(tcp_flags, tcp_flags_mask)){
         return ofp_mkerr(OFPET_BAD_MATCH, OFPBMC_BAD_WILDCARDS);
     }
     /* According to OpenFlow only the bits, from the least significant bit, 0 to 11 are valid
        while the remaining should be always set to 0.
      */
     if (tcp_flags & F000){
         return ofp_mkerr(OFPET_BAD_MATCH, OFPBMC_BAD_VALUE);
     }
  
     ofl_structs_match_put16m(match, f->header, tcp_flags, tcp_flags_mask);
}

Extracting the TCP flags from the packet

The last required step is to extract the parsed field from Netbee. In the file nbee_link/nbee_link.cpp, look for the code that gets the TCP_SRC and TCP_DST fields

if (protocol_Name.compare("tcp") == 0 && pkt_proto->tcp == NULL){
   pkt_proto->tcp = (struct tcp_header *) ((uint8_t*) pktin->data + proto->Position);
   PDMLReader->GetPDMLField(proto->Name, (char*) "sport", proto->FirstField, &field);
   nblink_extract_proto_fields(pktin, field, pktout, OXM_OF_TCP_SRC);
   PDMLReader->GetPDMLField(proto->Name, (char*) "dport", proto->FirstField, &field);
   nblink_extract_proto_fields(pktin, field, pktout, OXM_OF_TCP_DST);
   /* Add TCP flags */
    PDMLReader->GetPDMLField(proto->Name, (char*) "flags", proto->FirstField, &field);
    nblink_extract_proto_fields(pktin, field, pktout, OXM_OF_TCP_FLAGS);
}

The flags will come with the header length and the reserved bits, so you need to turn off these bits in the function *nblink_extract_proto_fields*

```C
if (field->Mask != NULL)
{
    uint8_t i;
    uint8_t *masked_field;
    if (header == OXM_OF_VLAN_VID){
    ...
    }
    else if (header == OXM_OF_TCP_FLAGS){
        uint16_t value;
        sscanf(field->Value, "%hx", &value);
        ofl_structs_match_put16(pktout, header, value & field->Mask);
    }
    ...
}

And that is it. After this point the switch should be able to match on TCP flags. You may want to add TCP_FLAGS to the printing helper functions, but this is out of the scope of this tutorial.

If you find any mistake, or think something is not well explained, drop me a line. Even better, if you feel you can improve this tutorial, feel free to add your changes :-)