P4Runtime-shell with proactive and reactive approach

Good afternoon everybody,

I am trying to develop a network with P4 switches where I install the forwarding rules in runtime. My goal is to have switches that contain a primary set of forwarding rules and, at the same time, a secondary backup set of forwarding rules.

Hence, when there are modifications in the network topology the new rules for packet forwarding are already in the switch. The Controller has just to communicate to the switch to change the primary set of rules with the backup one. The process is smoother and faster in this way.

What I cannot do is actually understand how to implement it. My ideas are:

  1. Create two tables to insert in every switch. forward for the primary set of rules and backup for the secondary one. Then, change the name of the tables through p4runtime-shell when needed in a way like this:
temp = forward
forward = backup
backup = forward

but I think it is not possible to change table names, isn’t it?

  1. Assign a priority to each table, so that in the P4 program the apply would be something like:
if (hdr.ipv4.isValid() && priority ==1){ MyIngress.forward.apply();}
else if (hdr.ipv4.isValid() && priority == 2 {MyIngress.backup.apply();}

In this case, I have the problem that I don’t know how to attach the attribute “priority” to a table and how to declare a variable “priority” in the P4 program that can be effectively usable by the JSON file that I use to initialize the switches and by the p4runtime-shell when I will need the runtime approach.

Thank you for the time you will dedicate to my doubts.

Cheers,

JB

Hi again :slight_smile:

So I think that OpenFlow capable switches had a priority value that was possible to determine but, as far as I know, the rules for P4 tables that have exact or LPM keys cannot be specified with a priority. You cannot have multiple rules with the same values either. The LPM matches use a mask that is an implicit determinant variable to define priority (i.e., 192.168.1.1/32 is more specific than 192.168.1.0/24).

A similar question was asked here. I made almost the same point as Andy and Antonin, but the specify that ternary or range-type keys and rules do have a priority value. Please check the whole 4.3 section from the Portable Switch Architecture, which gives a good view about priorities. I think (and I hope) that the same criteria applies to BMv2 Simple Switch (V1model) target.

My recommendation (which is not necessarily the best) is that you segregate rules by tables and only apply backup when forward is not matched.

if (hdr.ipv4.isValid()){ 
    if(forward.apply().miss){
        backup.apply();
    }
}

In this way, when you remove a rule from forward, the next time the table is applied, no match is found. Therefore, backup table is applied, which should have a match if you have already inserted the rule. If you have fixed your network’s problems and found the same or another primary path for that type of traffic, you can always install a new rule in forward. In this way, you are making your primary choice the first one to be applied.

There might be other ways to do it, but I just came with that idea. If anyone knows a more optimal or better way, please contribute. :slight_smile:

Cheers,

Another way is to take every P4 table that you want to have a primary/backup set of entries, and add another field to their key, e.g. perhaps a field named ‘table_version’ that is 1-bit wide.

Somewhere early in your P4 program, you do a lookup in a table that has no key at all, and it executes a default action that either sets table_version to 0, or it sets table_version to 1.

When you want to switch packet processing from using the primary set of entries (e.g. the ones that match when table_version is 0) to the alternate set of entries (e.g. the ones that match when table_version is 1), you have the controller change the default action of the early table from set_table_version(0) to set_table_version(1).

This idea can be extended to a multiple-bit table_version field, too, if you want more than two versions of the tables.

This can significantly increase the number of entries that must be added to any table that matches on table_version, of course. That is a tradeoff of using such an approach.

This idea is described in this article, too: p4-guide/indirection-helps-with-atomicity.md at master · jafingerhut/p4-guide · GitHub. There the motivation was implementing a way to atomically switch from one set of table entries to another, which this technique also achieves, if that is useful to you.

2 Likes

That’s quite smart. The positive side of Andy’s method is that you do not need to be keeping track of all the rules, removing them, and/or adding primary rules again later on. Instead, you are just changing a default action, which should simplify the process.

Thank you :slight_smile:

Hi,

Thank you both for your answers. In the end, I used Andy’s method and it is working perfectly. I now have three tables: set_prio, A_fwd and B_fwd. The first one is the one that sets the field table_prio to 1 or 0 so that the other two can perform the match even on that field. I just cannot figure out how to change that parameter in set_prio in runtime by using p4runtime-shell. I thought that is possible to insert the entry in runtime instead of initializing it through a JSON file, but then it remains the problem of changing its value when needed.

Unfortunately, I cannot find any example in the official P4 runtime specification document.

I do not know the p4runtime-shell syntax to do it, but I would hope that it has the ability to modify an existing P4 table entry, which should achieve the effect you want here. Of course it must have capabilities to add new table entries, but modifying an existing table entry might be invoked from p4runtime-shell somewhat differently than adding a new entry. The P4Runtime API messages used to communicate between p4runtime-shell and the simple_switch_grpc process definitely has messages specifically designed to modify existing P4 table entries, and hopefully p4runtime-shell exposes that capability in some way.

Did you check out the p4runtime-shell documentation: GitHub - p4lang/p4runtime-shell: An interactive Python shell for P4Runtime ?

You build table entries the same way for insertions or modifications, but instead of invoking <table entry>.insert, you invoke <table entry>.modify.

Hello!

Thank you for your answers, I was trying to develop the solution these days.

Following the documentation, I install the table entries through a Python script as:

te = sh.TableEntry['MyIngress.set_prio')(action = 'MyIngress.set_forwarding_table')
te.action['prio']='value'
te.insert()

The problem arises when I try to ping the hosts in the network: ping does not work. Through the log file of the switch, I can see that the rule is there and the metadata field I use to perform the match actually has the value required, but the match on the table MyIngress.A_fwd produces a miss:

17:43:09.973] [bmv2] [D] [thread 31277] [148.0] [cxt 0] Table 'MyIngress.set_prio': hit with handle 0
[17:43:09.973] [bmv2] [D] [thread 31277] [148.0] [cxt 0] Dumping entry 0
Match key:
Action entry: MyIngress.set_forwarding_table - 2,

[17:43:09.973] [bmv2] [D] [thread 31277] [148.0] [cxt 0] Action entry is MyIngress.set_forwarding_table - 2,
[17:43:09.973] [bmv2] [T] [thread 31277] [148.0] [cxt 0] Action MyIngress.set_forwarding_table
[17:43:09.973] [bmv2] [T] [thread 31277] [148.0] [cxt 0] my_control_plane.p4(113) Primitive meta.table_prio = prio
[17:43:09.973] [bmv2] [T] [thread 31277] [148.0] [cxt 0] Applying table 'MyIngress.A_forward'
[17:43:09.973] [bmv2] [D] [thread 31277] [148.0] [cxt 0] Looking up key:
* hdr.ipv4.ip_src     : a0000101
* hdr.ipv4.ip_dst     : a0000102
* meta.table_prio     : 02

[17:43:09.973] [bmv2] [D] [thread 31277] [148.0] [cxt 0] Table 'MyIngress.A_forward': miss
[17:43:09.973] [bmv2] [D] [thread 31277] [148.0] [cxt 0] Action entry is NoAction - 
[17:43:09.973] [bmv2] [T] [thread 31277] [148.0] [cxt 0] Action NoAction

Above I wrote 'value' in the action field since I tried several possible solution of how to pass that value:

  • 2 as an integer → not accepted, must be a string
  • ‘50’ (ASCII representation for character 2) → value installed is 32, but even changing the match does not work
  • '0x2 (same as 0x02.0x002…) (hexadecimal) → value installed is 2 but does not work (as the script shows)

If I install the entry by the json file at the setup of switches by passing an integer, it works.

I checked the type of value that shell.py installes and it is <class 'p4.v1.p4runtime_pb2.Param'>, but I cannot understand much in that library where it comes from.

Lastly, when I use ‘0x2’ in the Python script, the output to confirm that the rule s installed is:

param_id: 1
value: "\002"

I would be helpful if you provided more information about the script, in particular the exact commands you use to add the entry to MyIngress.set_prio and to add the entry to MyIngress.A_forward. You can also use p4runtime-shell to print the contents of both tables and see if they match what you expect.

BTW, you should be able to use the string “2” to set the parameter. That being said, “0x2” should work just as well.

I thought the same about using “2” or “0x2”, but apparently it does not work.

I insert the entries in A_fwd through a json file during the network configuration (function program_switch_rp4untime in tutorials/run_exercise.py at master · p4lang/tutorials · GitHub , line 260) and this is the snippet:

{
     "target": "bmv2",
     "p4info": "build/my_control_plane.p4.p4info.txt",
     "bmv2_json": "build/my_control_plane.json",
     "table_entries": [
          {
               "table": "MyIngress.A_forward",
               "match": {
                    "hdr.ipv4.ip_src": "160.0.1.2",
                    "hdr.ipv4.ip_dst": "160.0.1.1",
                    "meta.table_prio": 2
               },
               "action_name": "MyIngress.ipv4_forward",
               "action_params": {
                    "port": 1
               }
          },
          {
               "table": "MyIngress.A_forward",
               "match": {
                    "hdr.ipv4.ip_src": "160.0.1.1",
                    "hdr.ipv4.ip_dst": "160.0.1.2",
                    "meta.table_prio": 2
               },
               "action_name": "MyIngress.ipv4_forward",
               "action_params": {
                    "port": 2
               }
          }
     ]
}

while I insert the rules in set_prio with p4runtime-shell with this code:

sh.setup(
    device_id = 6,
    grpc_addr = '127.0.0.1:50057',
    election_id = (0,1),
    config=sh.FwdPipeConfig('build/my_control_plane.p4.p4info.txt','build/my_control_plane.json')
    )

te = sh.TableEntry('MyIngress.set_prio')(action = 'MyIngress.set_forwarding_table')
te.action['prio']='2'
te.insert()

sh.teardown()

I receive the output:

param_id: 1
value: "\002"

but no pings working.

On the other hand, if I insert the entries in set_prio through the json file in this way, it works:

{
     "target": "bmv2",
     "p4info": "build/my_control_plane.p4.p4info.txt",
     "bmv2_json": "build/my_control_plane.json",
     "table_entries": [
          {
               "table": "MyIngress.set_prio",
               "match": {},
               "action_name": "MyIngress.set_forwarding_table",
               "action_params": {
                    "prio": 2
               }
          },
          {
               "table": "MyIngress.A_forward",
               "match": {
                    "hdr.ipv4.ip_src": "160.0.1.2",
                    "hdr.ipv4.ip_dst": "160.0.1.1",
                    "meta.table_prio": 2
               },
               "action_name": "MyIngress.ipv4_forward",
               "action_params": {
                    "port": 1
               }
          },
          {
               "table": "MyIngress.A_forward",
               "match": {
                    "hdr.ipv4.ip_src": "160.0.1.1",
                    "hdr.ipv4.ip_dst": "160.0.1.2",
                    "meta.table_prio": 2
               },
               "action_name": "MyIngress.ipv4_forward",
               "action_params": {
                    "port": 2
               }
          }
     ]
}

Hi,

Have you tried dumping (printing) the flows when you install the rule using the CLI vs when using the JSON? I mean if forwarding works when you install the rules using the JSON, then when you dump the table contents in both cases you should see a difference between the value in set prio when inserting it with CLI vs JSON, I guess.

Cheers,

Hi,

This is the flow installed through CLI (or python script):

action_id: 17859943
params {
  param_id: 1
  value: "\002"
}

While I cannot paste the flows when installed with JSON since as soon as I open the p4runtime shell and I type whatever command (as “tables”), then it completely overwrite my rules, so that the switch is not able anymore to forward packets since there are no more the correct rules

That’s probably your problem actually. Based on your posts, it seems that you want to populate MyIngress.set_prio from p4runtime-shell, but you want to populate MyIngress.ipv4_forward statically from the JSON file. However, when you provide a config parameter to sh.setup like you do in your script, it will “swap” your P4 pipeline and in the process delete your table entries (look for VERIFY_AND_COMMIT in the P4Runtime specification). This is the expected behavior. The solution is simply to not provide a new config when calling setup:

sh.setup(
    device_id = 6,
    grpc_addr = '127.0.0.1:50057',
    election_id = (0,1)
    )

There is no need to provide a new config if your bmv2 switch is already running the correct P4 program…
Also make sure the device_id matches. I notice you are using 6, so I assume you configured bmv2 to use 6 as well.

1 Like

I see, now it is working! Even by using action['prio']='2' as value for the action parameter.

Thank you for your answers and for your patience above all, I am quite new to P4 and some things are still a bit tricky for me.

Cheers,

JB