Integration of Openflow with bmv2

Hello,

Is it possible in some way to offload traffic when there is no match with an entry table(offloading meaning that if the packet that enters the bmv2 switch does not match any action then by default the bmv2 should send it to a controller) similar to what OVS does when there is no match with a packet and send it to the controller we set up.
For instance, Id would like to have an action for my table to encapsulate the packet with OpenFlow or another protocol and send it to a controller, then analyze that traffic and decide what to do with that packet(I don’t wanna do port-mirroring).

Are you aware of something already done that could satisfy this objective of mine? if so Id be grateful for any resource you can share.
Also, if this is something that has not been done before, is there a way I could accomplish this goal, I’d appreciate any ideas.

table forwarding {
key = {
standard_metadata.ingress_port:exact;
}
actions = {
forward;
drop;
NoAction;
}
size = 1024;
default_action = drop();
}

Either via a default action or by simply checking

if(your_table.apply().miss) { // send to controller }

You can send a packet to the controller via the CPU port. See p4-guide/ptf-tests/packetinout at master · jafingerhut/p4-guide · GitHub

Thank u @SteffenLindner

That resource gave me the basics to continue with my objectives, however, the project offers some automized tests. I was wondering whether there are some other resources that can walk u through the creation of a gRPC client to interact with the simple_switch_grpc.

I am trying to connect my simple_switch_grpc with a gRPC client(P4 remote controller) to process pack_in/packet_out packets.
However, I am having a hard time doing so.
I am trying to do it step by step. For instance, first, I have created my topology with mininet and I have run the simple_switch_grpc command to start listening for a connection on port 50051.
I am following this tutorial: p4-guide/ptf-tests/packetinout at master · jafingerhut/p4-guide · GitHub
#simple_switch_grpc
Adding interface s1-eth1 as port 0
Adding interface s1-eth2 as port 1
Server listening on 0.0.0.0:50051
And now, I wanna run a grpc client(controller) that can establish a connection with simple_switch_grpc to receive packet_in messages, and here is where I am having a hard time.
For instance I was trying to run this client I found, but I ve got too many errors trying to run it because either I don’t have all the dependencies or because my program doesn’t find all of them.

“”"
import sys
import grpc
import p4runtime_pb2
import p4runtime_pb2_grpc
from google.protobuf import text_format
def main():
# Replace with the address of your gRPC server
grpc_server = ‘192.168.30.149:50051’
# Set up a gRPC channel and stub
channel = grpc.insecure_channel(grpc_server)
stub = p4runtime_pb2_grpc.P4RuntimeStub(channel)
# Create a StreamChannelRequest for packet-in messages
stream_channel_request = p4runtime_pb2.StreamMessageRequest()
stream_channel_request.packet = p4runtime_pb2.PacketIn()
print(“Listening for packet-in messages…”)
try:
for response in stub.StreamChannel(iter([stream_channel_request])):
if response.HasField(“packet”):
print(“Packet-in message received:”)
print(text_format.MessageToString(response.packet))
except grpc.RpcError as e:
print(f"gRPC call failed: {e}“)
if name == ‘main’:
main()
“””

IS THERE ANY PROJECT THAT YOU KNOW THAT EXPLAINS STEP-BY-STEP HOW TO CODE A GRPC CLIENT THAT CAN INTERACT WITH SIMPLE_SWITCH_GRPC?
I would appreciate any resource that can help me move forward. Thank u!

Did you have a look at the tutorials ?

There are several examples including p4runtime

Hello @SteffenLindner,

I have seen those tutorials before but I as far as I can tell they help u push your compiled P4 program, insert/delete entries to/from the tables in a simple_switch_grpc, but they dont help you parse packet_in messages. I have tested that part and it is working fine(pushing entries to tables/.json files), however, what I am trying to do is to parse the packet_in messages sent by the P4runtime(simple_switch_grpc), for instance, I have a P4 program that is sending packet_in messages to the grpc client(controller) every time a packet does not match with an entry in one of its tables. My P4 program is offloading all those packets to the CPU port 510 and in turn the P4runtime is sending those packets for the controller, however, I need to parse those packets/messages sent by the P4runtime on the controller side, and here is when I am having problems, because I am not sure how to do it(BTW, sniffing on the controller side I can see that the P4runtime is sending those packet_in messages to the controller and I am receiving them in the controller host, but I dont know how to parse them).

This is my code on the controller side, basically pushes the compiled P4 program to the simple_switch_grpc and then tries to listen for packet_in messages to parse them:

import sys
import grpc
import struct
from p4.v1 import p4runtime_pb2_grpc, p4runtime_pb2
import p4runtime_sh.shell as sh
import signal


class PacketInOutTest:
    CPU_PORT = 510
    def __init__(self, grpc_addr, p4info_file, bmv2_json_file):

class PacketInOutTest:
    CPU_PORT = 510

    def __init__(self, grpc_addr, p4info_file, bmv2_json_file):
        # Setup with p4runtime_sh
        self.sh = sh.setup(
            device_id=0,
            grpc_addr=grpc_addr,
            election_id=(1, 0),  # Election ID is required for P4Runtime to grant control
            config=sh.FwdPipeConfig(p4info_file, bmv2_json_file)
        )
        # Now, establish a gRPC channel for packet-in handling with keepalive parameters
        self.channel = grpc.insecure_channel(grpc_addr, options=[
            ('grpc.keepalive_time_ms', 30000),
            ('grpc.keepalive_timeout_ms', 10000),
            ('grpc.keepalive_permit_without_calls', True),
            ('grpc.http2.max_pings_without_data', 0),
            ('grpc.http2.min_time_between_pings_ms', 10000),
        ])
        self.stub = p4runtime_pb2_grpc.P4RuntimeStub(self.channel)

    def on_connectivity_change(self, state):
        print(f"Channel state changed: {state}")

    def listen_for_packet_in(self):
        print("Listening for packet-in messages...")
        self.channel.subscribe(self.on_connectivity_change, try_to_connect=True)

        def request_iterator():
            yield p4runtime_pb2.StreamMessageRequest()

        stream = self.stub.StreamChannel(request_iterator())

        try:
            for response in stream:
                if response.WhichOneof('update') == 'packet':
                    print(f"Packet-in received with payload: {packet.payload.hex()}")
                    return True  # Packet-in message received
        except grpc.RpcError as e:
            print(f"RPC error occurred: {e.code()} - {e.details()}")
        return False  # No Packet-in message received or an error occurred

    def tearDown(self):
        print("Shutting down gracefully...")
        sh.teardown()
        self.channel.close()
def signal_handler(sig, frame):
    print('Interrupt received, shutting down...')
    global test
    test.tearDown()
    sys.exit(0)

if __name__ == '__main__':
    signal.signal(signal.SIGINT, signal_handler)
    test = PacketInOutTest(grpc_addr='192.168.30.149:9559', p4info_file='packetinout.p4info.txt',
                           bmv2_json_file='packetinout.json')
    test.listen_for_packet_in()

And this is my basic P4 program that is sending all the packets to the controller(CPU port 510):

#include <core.p4>
#include <v1model.p4>

#define CPU_PORT 510

struct headers_t {
}

struct metadata_t {
}

parser parserImpl(packet_in packet,
                  out headers_t hdr,
                  inout metadata_t meta,
                  inout standard_metadata_t stdmeta)
{
    state start {
        transition accept;
    }
}

control ingressImpl(inout headers_t hdr,
                    inout metadata_t meta,
                    inout standard_metadata_t stdmeta)
{
    apply {
        // Send all traffic to CPU port
        stdmeta.egress_spec = CPU_PORT;
    }
}

control egressImpl(inout headers_t hdr,
                   inout metadata_t meta,
                   inout standard_metadata_t stdmeta)
{
    apply {
        // No processing required on egress
    }
}

control deparserImpl(packet_out packet,
                     in headers_t hdr)
{
    apply {
    }
}

control verifyChecksum(inout headers_t hdr, inout metadata_t meta) {
    apply {
        // Checksum verification not needed for this example
    }
}

control computeChecksum(inout headers_t hdr, inout metadata_t meta) {
    apply {
        // Checksum computation not needed for this example
    }
}

V1Switch(
    parserImpl(),
    verifyChecksum(),
    ingressImpl(),
    egressImpl(),
    computeChecksum(),
    deparserImpl()
) main;

By parsing the packet_in messages sent by the P4runtime I would process/analyze those packets and take actions accordinly. Id appreacite any help to accomplish my goal.

Note that when I run the python code(controller), these are the outputs, the GOAWAY messages pop out after much later than when I received the packet_in messages sent by the P4runtime, so, thats not originating the error.

root@absuarez-virtual-machine:/home/absuarez/ss_grpc_client/lab3# python3 packetinout5.py
Listening for packet-in messages...
Channel state changed: ChannelConnectivity.IDLE
Channel state changed: ChannelConnectivity.READY
E0310 04:57:59.153710151    3991 chttp2_transport.cc:1216]             ipv4:192.168.30.149:9559: Received a GOAWAY with error code ENHANCE_YOUR_CALM and debug data equal to "too_many_pings". Current keepalive time (before throttling): 30000ms
Channel state changed: ChannelConnectivity.IDLE

Its quite old but you can have a look at: p4-bier/Local-Controller/utils/p4runtime_lib/switch.py at master · uni-tue-kn/p4-bier · GitHub

The important part is

self.stream_out_q = IterableQueue()
self.stream_in_q = IterableQueue()

self.stream = self.client_stub.StreamChannel(self.stream_req_iterator())
self.stream_recv_thread = threading.Thread(
            target=self.stream_recv, args=(self.stream,))

There we also received the packets from the CPU port and handled them in a separate module.

In stream_recv in switch.py the received packets are simply pushed to a handler that is implemented in p4-bier/Local-Controller/libs/MessageInHandler.py at master · uni-tue-kn/p4-bier · GitHub

Did you also correctly set the CPU port in the simple_switch_grpc call?

The PacketIn messages consist of two parts, the first part being optional and under your control from how you write your P4 program:

(a) a collection of metadata fields that your P4 program fills in before sending the packet to the controller. Example: p4-guide/ptf-tests/packetinout/packetinout.p4 at master · jafingerhut/p4-guide · GitHub
(b) the packet received by the switch, perhaps modified by your P4 program before sending it to the controller, or perhaps left unmodified by your P4 program

If you are wondering how to parse the packet (b), then the answer is “however you want to write code to do so”. It is a sequence of bytes, most likely beginning with the first byte of the Ethernet header. You can write custom code to parse it, or you can try to find a library that will help you do so.

If your controller is written in Python, one library that can help you do so is Scapy. There is a lot of published documentation and examples of using Scapy. Some notes I have written up that might be helpful can be found here: p4-guide/README-scapy.md at master · jafingerhut/p4-guide · GitHub

Thank @SteffenLindner I will check out that resource.

I think I did set up the CPU port correctly, this is how I am running simple_switch_grpc:

sudo simple_switch_grpc \
     --log-file ss-log \
     --log-flush \
     --dump-packet-data 10000 \
     -i 0@s1-eth1 \
     -i 1@s1-eth2 \
     --no-p4 \
     --thrift-port 9090 \
     -- --cpu-port 510 &

I can see the packet_in messages in wireshark being sent to the controller, so, I think that part is working alright.

Please see this recently-published example P4 program plus controller written in Python to see if it answers any of your questions: A simple P4 program plus controller in Python for demonstrating PacketIn messages from switch to controller