Rate This Document
Findability
Accuracy
Completeness
Readability

Controls

In DSL, controls are core components used to define data packet processing logic. They operate on the headers extracted by the parser, executing corresponding actions based on table lookup results to implement functions such as packet forwarding, modification, or dropping.

Functions of a Control Block

  • Connecting the parser to the forwarding logic

    Initiates packet processing upon completion of packet header parsing. It utilizes extracted header field information to perform table lookup, which determines the specific action to be executed on the packet.

  • Table lookup

    Defines the match table and application logic. Table lookup is performed by matching lookup keys against packet header fields.

  • Action execution

    Executes the action based on the lookup result. A table miss triggers the miss action.

Basic Syntax of a Control Block

control <control_name> (param1, param2, ...) {
    @<pre/main/post>
    table <table_name> {
        key = {
            <field_name>: <match_type>;
            ...
        }
        actions = {
            <action_name0>;
            <action_name1>;
            ...
        }
        default_action = <default_action>
        size = <val>
        timer_en = <true/false>
        aging_time = <val>        
    }
    apply {
        //apply_result res = xxx.apply();
        if(<table_name>.apply().hit){
          ...
        }
    }
}

Table

The table in a control block is used to define the flow table structure and describe a match-action collection.

  • The @pre, @main, or @post annotation must be marked before the table to indicate the table type. For details about the specifications and constraints of different table types, see OVS Specifications.
  • The @rss_fastpath annotation can be marked before the table to enable the Receive-Side Scaling (RSS) fast path.

The key list specifies the construction rule and matching mechanism for match fields. This version supports exact and ternary matching. Ternary matching is also known as fuzzy match. During the matching, AND operations are applied to the packet fields and a mask; the results are used for flow table lookup. You can construct a key using basic, header, and header_union types. If you construct a key using headers (with the @dpdk annotation) provided in hydra_builtin.hdr, standard DPDK enumerations are utilized. Table 1 lists the DPDK enumerations supported by the architecture file.

Table 1 Supported DPDK enumerations

Header

Included Field

Corresponding DPDK Enumeration

@dpdk header rte_eth_hdr_t

bit<16> eth_type

RTE_FLOW_ITEM_TYPE_ETH

macAddr src

macAddr dst

@dpdk header rte_vlan_hdr_t

bit<16> vlan_tci

RTE_FLOW_ITEM_TYPE_VLAN

@dpdk header rte_ipv4_hdr_t

bit<8> protocol

RTE_FLOW_ITEM_TYPE_IPV4

bit<32> src_ip

bit<32> dst_ip

@dpdk header rte_ipv6_hdr_t

bit<8> protocol

RTE_FLOW_ITEM_TYPE_IPV6

bit<128> src_ip

bit<128> dst_ip

@dpdk header rte_tcp_hdr_t

bit<16> src_port

RTE_FLOW_ITEM_TYPE_TCP

bit<16> dst_port

@dpdk header rte_udp_hdr_t

bit<16> src_port

RTE_FLOW_ITEM_TYPE_UDP

bit<16> dst_port

@dpdk header rte_icmp_hdr_t

bit<8> type

RTE_FLOW_ITEM_TYPE_ICMP

bit<8> code

bit<16> ident

@dpdk header rte_icmp6_hdr_t

bit<8> type

RTE_FLOW_ITEM_TYPE_ICMP6

bit<8> code

@dpdk header rte_vxlan_hdr_t

bit<24> vni

RTE_FLOW_ITEM_TYPE_VXLAN

@dpdk header rte_port_id_hdr_t

bit<16> port_id

RTE_FLOW_ITEM_TYPE_PORT_ID

@dpdk header rte_geneve_hdr_t

bit<16> ver_opt_len_o_c_rsvd0

RTE_FLOW_ITEM_TYPE_GENEVE

bit<16> protocol

bit<24> vni

bit<8> rsvd1

  • This version does not support the use of expressions for key construction; for example, "a + b : exact" is not supported.
  • The isValid() method of headers cannot be used to construct keys.
  • Enumeration types cannot be used to construct keys.
  • The number of custom keys cannot exceed 64.
  • When utilizing the header_union type for key construction, the protocols corresponding to the header members cannot co-exist (e.g., a packet may contain either IPv4 or IPv6, not both).
  • Key construction supports IPv4/IPv6 multiplexing at L3 and TCP/UDP/ICMP/ICMPv6 multiplexing at L4. The following default header_union structures are provided:
    header_union ip_union {
        rte_ipv4_hdr_t dpdk_ipv4;
        rte_ipv6_hdr_t dpdk_ipv6;
    }
    
    header_union port_icmp_union {
        rte_tcp_hdr_t dpdk_tcp;
        rte_udp_hdr_t dpdk_udp;
        rte_icmp_hdr_t dpdk_icmp;
        rte_icmp6_hdr_t dpdk_icmp6;
    }
    
    header_union port_union {
        rte_tcp_hdr_t dpdk_tcp;
        rte_udp_hdr_t dpdk_udp;
    }
    
    header_union icmp_union {
        rte_icmp_hdr_t dpdk_icmp;
        rte_icmp6_hdr_t dpdk_icmp6;
    }
  • When utilizing the header_union type for key construction, setValid() must be explicitly called in the apply block to mark the protocol type that takes effect in header_union. The following uses ip_union as an example:
    control Pipe(in headers hdr){
          ip_union l3_union;
          table t{
                ...      
                key = {
                      l3_union : exact;
                      ...
                }
                ...
          }
          apply{
                if (hdr.l3.ipv4.isValid()) {
                      l3_union.dpdk_ipv4.setValid();
                      l3_union.dpdk_ipv4.protocol = hdr.l3.ipv4.protocol;
                      l3_union.dpdk_ipv4.sip = hdr.l3.ipv4.sip;
                      l3_union.dpdk_ipv4.dip = hdr.l3.ipv4.dip;
                } else if (hdr.l3.ipv6.isValid()) {
                      l3_union.dpdk_ipv6.setValid();
                      l3_union.dpdk_ipv6.next_header = hdr.l3.ipv6.next_header;
                      l3_union.dpdk_ipv6.sip = hdr.l3.ipv6.sip;
                      l3_union.dpdk_ipv6.dip = hdr.l3.ipv6.dip;
                }
                ...
                t.apply();
          }
    }
Table 2 Parameter description

Parameter

Description

Key collection

Collection of information used for flow table matching, including built-in keys and user-defined keys.

Action collection

Actions executed upon table hits, including built-in actions and user-defined actions.

default_action

Action executed when the key has no matching entry.

  • The default action cannot be customized and can only be one of flexda_no_action, flexda_upcall, and flexda_drop provided in the architecture file.
  • If no default action is specified, flexda_no_action is used.

size

Optional. Number of entries in a table. The default value is 1024.

size can only be a literal. Variables and expressions are not supported. The following is an example:
size = 0x1000000; // legal
size = 16 * 1024 * 1024; // illegal

aging_time

Optional. Aging time, in seconds. The default value is 30. If the aging function is not required, set aging_time to 0. Its value ranges from 0 to 1098.

  • The programming framework supports the use of aging_time to customize hardware flow table aging intervals. The DPAK's original aging configuration item flow_max_idle is no longer effective under this framework.
  • For data beyond the committed constraints, the compiler automatically truncates the data during compilation, which may lead to unexpected errors.

apply

The apply method serves as the entry point for a control. In the apply block, you can invoke the apply method of a table to perform key construction and lookup operations. This method returns a structure containing {bool hit; bool miss; bool error;}. This structure type is declared in the architecture file.

The following MyPrePipe control defines a main table named fullnat_exact. It utilizes standard DPDK enumerations for IPv4 and IPv6, along with custom src_port and dst_port fields as the key. The number of flow entries is 2M. In the apply method, variables are populated based on the packet parsing results. If the IPv4 header is valid and the time to live (TTL) is greater than 1, the system initiates a match against the fullnat_exact table. Upon a table hit, the actions defined in the action list are executed; if a table miss occurs, the default action (flexda_upcall) is performed. Example:

control MyPrePipe(in headers hdr) {
    rte_ipv4_hdr_t dpdk_ipv4;
    rte_ipv6_hdr_t dpdk_ipv6;
    bit<16> src_port;
    bit<16> dst_port;
    
    @main
    table fullnat_exact {
        key = {
            dpdk_ipv4 : exact;
            dpdk_ipv6 : exact;
            src_port : exact;
            dst_port : exact;
        }
        actions = {
            ovs_action_mod_tos(hdr);
            ovs_action_mod_ttl(hdr);
            rte_port_id;
            rte_set_ipv4_src;
            rte_set_ipv4_dst;
            rte_set_ipv6_src;
            rte_set_ipv6_dst;
            rte_set_tp_src;
            rte_set_tp_dst;
            rte_set_mac_dst;
            rte_set_mac_src;
        }
        size = 2097152;
        default_action = flexda_upcall;
    }
    apply {
        if (hdr.l3.ipv4.isValid()) {
            dpdk_ipv4.protocol = hdr.l3.ipv4.protocol;
            dpdk_ipv4.sip = hdr.l3.ipv4.sip;
            dpdk_ipv4.dip = hdr.l3.ipv4.dip;
        }
        else if (hdr.l3.ipv6.isValid()) {
            dpdk_ipv6.next_header = hdr.l3.ipv6.next_header;
            dpdk_ipv6.sip = hdr.l3.ipv6.sip;
            dpdk_ipv6.dip = hdr.l3.ipv6.dip;
        }
        if(hdr.l4.tcp.isValid()) {
            src_port = hdr.l4.tcp.src_port;
            dst_port = hdr.l4.tcp.dst_port;
        }
        else if (hdr.l4.udp.isValid()) {
            src_port = hdr.l4.udp.src_port;
            dst_port = hdr.l4.udp.dst_port;
        }
        if (hdr.l3.ipv4.isValid() && hdr.l3.ipv4.ttl > 1) {
            fullnat_exact.apply();
        }
    }
}