How tables work?

Hi, can someone plz explain how do tables work and what is the relation between key and actions?

Welcome to the forum :slight_smile:

The short version:

Tables act as a reference for defining keys out of header fields or metadata, to name a few (which will hold values to be matched) and the actions that get executed when there is a match. The proper definition is given in the language specification. Although the concept of a table is not (just) something purely associated to P4.

There is a direct association between the value of a key, and the action that gets executed when this key value is matched.

The long version:

Tables are very useful when you want to decide what to do with a packet. You don’t always want to decide on how to forward packets, so there is more than one purpose for using a table, but let’s assume you want to decide what to do with them in terms of forwarding criteria. Generally, when learning what tables are, the basic exercise from the P4 tutorial comes to mind. So that is a good exercise to start with. See the tutorial here.

Let’s put an example. Imagine you have one switch (S1) and three hosts (Host_1, Host_2 and Host_3), connected to different ports in S1. You know that hosts want to talk to each other. For instance, you know Host_1 is trying to send packets to Host_2. You actually know that Host_1 is connected to port 1 in S1, Host_2 to port 2 and Host_3 to port 3. How do devices decide where to forward the packet? Without going into too much detail about a learning switch or routing protocols, let’s say that some devices look into the destination MAC, others the destination IP and so on. Let’s consider the destination IP address for our example. Now, you could write this code in P4:

# This is not actual P4, just pseudocode


if(hdr.ipv4.dstAddr == 192.168.1.1){ # condtion
  forward_packet(port=1)   # action
}else if (hdr.ipv4.dstAddr == 192.168.1.2){ # another condtion
  forward_packet(port=2)  # action
}else if (hdr.ipv4.dstAddr == 192.168.1.3){ # a third condtion
  forward_packet(port=3)  # action
}

This might look like it can work just fine, but…

  • What if you have 32 or 64 ports (and hosts) connected to the switch? Are you going to create 64 if-else statements? Let’s imagine that you had a 1000 ports and hosts connected to S1… it seems not a very practical way to decide on how to forward packets.
  • What if you want to forward based on different blocks of IPs? This is where LPM type of keys make sense (and the TCAM memory in hardware devices), which you will probably see in the tutorial. If you do not know what a subnetwork or CIDR is, then click on the links.
  • What if you want to change the IP of a particular if statement? Or the output port? You’d have to change the program and load it again into the switch. So not very practical too, right?

Instead of using multiple if statements, you create a table. The controller, which is the part of the network that “thinks” and controls the data plane (i.e., the switch), will tell the switch which entries are going to be inserted or added into the table. For instance, see the table below.

table ipv4_lpm { 
    # ipv4_lpm is the table name
    key = { 
    # one or more keys are similar to the condition(s) in the if statement of the previous example
        hdr.ipv4.dstAddr: lpm; 
        # this is the left term of the condition, the destination IPv4 address (dstAddr) field of the ipv4 header
    }
    actions = { 
    # this is where you define WHAT you when to do when there is a coincidence based on an entry 
        ipv4_forward; 
        # this action name is defining the output port to be used
        drop; 
        # this action will drop the packet if there is a match 
        NoAction; 
        #this action will do nothing
    }
    size = 1024; 
    # this is how many entries the controller can introduce
    default_action = NoAction(); 
    # if there is no match, the we do nothing
}

action drop() {
    mark_to_drop(standard_metadata);
}

action ipv4_forward(egressSpec_t port) {
    standard_metadata.egress_spec = port;
}

The controller (control plane) can tell the switch (data plane) which IPs introduce, acting as keys of the table. Besides, the controller, will associate an action that will be executed every time an IP is matched. Sometimes, controllers will also add an argument to the action, like the number of the output port. For instance, the controller can communicate to the switch to introduce 192.168.1.1 as key, and ipv4_forward with number 1 as argument. The controller can also decide to drop any packet that has IP address 192.168.1.2 as destination. So your table, that stores entries in memory, would look like this:

Table: ipv4_lpm
Key (Header-Field) Value (Action)
192.168.1.1 ipv4_forward(port=1)
192.168.1.2 drop

And this is the best way I found on how to explain tables. For sure, I missed many details and ways to explain tables. So…feel free to ask.

Cheers,

PD: And just to be clear, I call a P4 device a switch, but it can parse L2, L3 and L4 headers, and even application layers headers if that makes sense to your use case. So this is not only a typical L2 switch but rather a device that acts in pretty much all layers. Also, there might be other P4 devices that are not called switch es, like when SmartNIcs or NetFPGAs are used as physical hardware.

1 Like