Sending metrics to controller

Hello,
I am writing a P4 program in bmv2. I would like that every time a packet is processed and forwarded, another one is sent to the controller with relevant metrics about the processed packet, like timestamps or information about whether the packet has been dropped or not.

Is something like that possible? I have looked at the packetinout example in the p4-guide repository but I cannot figure out the way of implementing this behavior when forwarding through a switch port.

Thanks,
Javier.

Hi Javier,

In simple terms, you need to clone the packet at either Ingress or Egress (imagine making a copy and processing it again). Once you make a copy, you can detect if this is a cloned packet and send a report to the controller. Maybe using an encapsulated packet or a special header. This reminds, to some extent, to the INT protocol and how people deal with reports to the collector. Check out this repository or this line of the GEANT’s INT repository. You probably need something similar to either Andy’s or GEANT’s code.

Consider that this will generate a tremendous amount of traffic, pretty much doubling the traffic. The controller might be able to process some packets. But in a real use case, I doubt that the controller can even parse even half of the packets that a switch ASIC could process. I would recommend some kind of arbitrary sampling to generate reports, just sometimes. In the long run, it will be statistically similar.

If anyone has something to say please add an answer! :slight_smile:

Cheers,

Thanks for your answer. I have followed the referenced repositories and it worked, my program clones the received packet and sends it to the controller through the CPU port. However, I cannot find a way to read these packets to check if the data in the packet is sent correctly. I am using a remote machine that acts as a controller by using the p4runtime-shell. At the end of the python program that populates the tables of the switches, I included the PacketIn.sniff function as it is described in the Packet IO example but no results were printed. Am I missing something?

Moreover, I am interested in what you proposed about generating reports, only sometimes. Could you develop the idea or provide me with an example of how to do it?

Thanks again for your help

Hi @jvm m

It turns out that when you send a packet via de control channel to the controller, the first bytes that the controller will see are the @controller_header("packet_in") bytes of that header. Let’s say you have a program with these headers:

@controller_header("packet_in")
header packet_in_header_t {
    bit<9> ingress_port;
    bit<7> _pad;
}

@controller_header("packet_out")
header packet_out_header_t {
    bit<9> egress_port;
    bit<7> _pad;
}

Whenever you create a packet_in packet then you will first have ingress_port and _pad as the first 16 bits (2 bytes). After those bits, whatever your deparser “deparsed” will be available to the controller. To parse packets as they come, you need something like this (this piece was taken from this repository):

# imports and more

def main(p4info_file_path, bmv2_file_path):

    p4info_helper = p4runtime_lib.helper.P4InfoHelper(p4info_file_path)
    switches_conf = load_switches_conf()

    try:

        switches = connect_to_switches(switches_conf["switches"])
        send_master_arbitration_updates(switches)
        set_pipelines(switches, p4info_helper, bmv2_file_path)

        switch_2 = switches[1]

        while True:
            packet_in = switch_2.PacketIn()
            print("Packet received: "+str(packet_in))
           
            if packet_in.WhichOneof('update') == 'packet':
                pkt = Ether(_pkt=packet_in.packet.payload)
                src_ip = pkt.getlayer(IP).src
                dst_ip = pkt.getlayer(IP).dst
                tcp_dPort = pkt.getlayer(TCP).dport

                print("SRC IP: " + str(src_ip))
                print("DST IP: " + str(dst_ip))
                print("TCP DPORT: " + str(tcp_dPort))

To answer your second question, you might want to read the Telemetry Report format specification. In the end, it is all about encapsulating a packet in another packet, a telemetry report. This “new” packet is an Ethernet/IP/UDP bundle that holds all the information of the original packet that traversed the switch. Of course, you can always drop some ybtes from it. The idea is that the last switch in the network will forward the INT information using this methodology. So you might want to do something similar. Remember that you will need a UDP server to receive all the information (a collector), so that you can deparse packets and invesitgate the information as packets arrive to the collector.

Cheers,

Hello @ederollora

Your answer was really helpful, thank you. I decided to follow your recommendations and implemented a collector to deparse metrics packets and analyze their content. However, I am facing a problem that I am not sure is related to P4 or not.

My P4 program clones packets to include metrics in them and sends them to the collector through a port connected to the collector. So this is more or less how I build the cloned packets (for the moment I am sending per-hop reports):

    action send_metrics(macAddr_t srcAddr, macAddr_t dstAddr, egressSpec_t port, bit<32> ipSrcAddr, bit<32> ipDstAddr) {
        // Set port to metrics collector
        standard_metadata.egress_spec = port;
        // Set Ethernet addresses
        hdr.ethernet.srcAddr = srcAddr;
        hdr.ethernet.dstAddr = dstAddr;
        // Set IP addresses and protocol
        hdr.ipv4.srcAddr = ipSrcAddr;
        hdr.ipv4.dstAddr = ipDstAddr;
        hdr.ipv4.protocol = 0x11;
        // Set UDP header
        hdr.udp.setValid();
        random(hdr.udp.srcPort,49152,65535);
        hdr.udp.dstPort = UDP_METRICS_PORT;
        hdr.udp.length = 35; //8 bytes UDP header + 27 bytes Metrics Payload
        // Set IP length
        hdr.ipv4.totalLen = hdr.udp.length + 20;
        //Add metrics to the packet
        hdr.metrics.setValid();
        hdr.metrics.servicePathIdentifier = meta.servicePathIdentifier;
        hdr.metrics.serviceIndex = meta.serviceIndex;
        hdr.metrics.contextHeader = meta.contextHeader;
        hdr.metrics.dropped = meta.dropped;
        hdr.metrics.timestamp = meta.timestamp;
        truncate(69);

On the other hand, I have set up a simple UDP server like this:

import socket

UDP_PORT = 8192  

sock = socket.socket(socket.AF_INET,  socket.SOCK_DGRAM) 
sock.bind(("0.0.0.0", UDP_PORT)) 

while True:
    data, addr = sock.recvfrom(65565) 
    print("Received Packet from {}:{}:".format(addr[0], addr[1]))
    print(data.decode())

So my expectations were receiving packets in the UDP server, but I am not getting anything as an output. What really annoys me is that if I capture with tcpdump I can actually see the packets being received (switches are sending these packets correctly), but not in the UDP server. I have checked the Ethernet and IP addresses, and the UDP port, and everything seems ok. Is there something wrong I am doing?

Cheers

The UDP checksum is probably (or certainly) wrong. Try to activate UDP checksum checking in wireshark and you will probably see that the checksum is wrong, and therefore the kernel probably discards the packet.

1 Like

Yes, it was that actually. Thank you so much for your help

1 Like