TCP packets are not being processed correctly by bmv2

Hello,

I am new to P4, and I have implemented several small projects using VMware, Mininet, bmv2 switch, and P4, all the previous “tools”(except VMware) are running on Ubuntu 22.04.3 LTS.
During my tests, I saw that the bmv2 switch processes ethernet packets without problems up to Layer 3. For example, I can create tables for ARP, IP, I have tried ICMP as well, and it works without problems. However, when I am creating tables for TCP traffic, for some reason my virtual host does not respond to the message sent by my client. It seems like the bmv2 is malforming the packet or something. I have sniffed the packet and I can’t see any issue, but every time I make a TCP consult via a bmv2 switch the target virtual host created by mininet does not respond to the consult.

E.g.
This is the topology I create with Mininet:
h1-------bmv2 switch-----h2

Then I am uploading the compiled json file that contains my tables to the bmv2 switch, my code before compiling it into a json file is below(If my code is the problem I would appreciate help to correct it).
I dont see anything wrong with the code, and actually after populating my table and testing TCP connection over telnet or with wget(from h1), I can see that the SYN packet reaches h2, but despite having a HTTP service running on h2, this host does not respond at all to the SYN packet, I would expect a SYN-ACK response, but I see nothing, hence, every connection fails.

I have seen some tutorials here and there, and they used a python program to send data over TCP on port 1234, I replicated the exercise and effectively I can send data from client to server over TCP, BUT, the target host running a tcp service does never respond with a SYN ACK or ACK(the exercize are more like using udp concepts but over tcp), hence, I am not sure whether the BMV2 support TCP, or maybe the bmv2-mininet integration needs additional configuration.
BTW, I tried the same exercise with OVS and mininet on the same ubuntu machine and it ran without problems.

BTW, I thought my setup was wrong, then I tested it in the lab that the University of South Carolina offers online and the issue is the same.

/* -- P4_16 -- */
#include <core.p4>
#include <v1model.p4>

const bit<16> TYPE_IPV4 = 0x800;

/*************************************************************************
*********************** H E A D E R S ***********************************
*************************************************************************/

typedef bit<9> egressSpec_t;
typedef bit<48> macAddr_t;
typedef bit<32> ip4Addr_t;

header ethernet_t {
macAddr_t dstAddr;
macAddr_t srcAddr;
bit<16> etherType;
}

header ipv4_t {
bit<4> version;
bit<4> ihl;
bit<8> diffserv;
bit<16> totalLen;
bit<16> identification;
bit<3> flags;
bit<13> fragOffset;
bit<8> ttl;
bit<8> protocol;
bit<16> hdrChecksum;
ip4Addr_t srcAddr;
ip4Addr_t dstAddr;
}

struct metadata {
/* empty */
}

struct headers {
ethernet_t ethernet;
ipv4_t ipv4;
}

/*************************************************************************
*********************** P A R S E R ***********************************
*************************************************************************/

parser MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {

state start {
    transition parse_ethernet;
}

state parse_ethernet {
    packet.extract(hdr.ethernet);
    transition select(hdr.ethernet.etherType) {
        TYPE_IPV4: parse_ipv4;
        default: accept;
    }
}

state parse_ipv4 {
    packet.extract(hdr.ipv4);
    transition accept;
}

}

/*************************************************************************
************ C H E C K S U M V E R I F I C A T I O N *************
*************************************************************************/

control MyVerifyChecksum(inout headers hdr, inout metadata meta) {
apply { }
}

/*************************************************************************
************** I N G R E S S P R O C E S S I N G *******************
*************************************************************************/

control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() {
mark_to_drop(standard_metadata);
}

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

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

apply {
    forwarding.apply();
}

}

/*************************************************************************
**************** E G R E S S P R O C E S S I N G *******************
*************************************************************************/

control MyEgress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
apply { }
}

/*************************************************************************
************* C H E C K S U M C O M P U T A T I O N **************
*************************************************************************/

control MyComputeChecksum(inout headers hdr, inout metadata meta) {
apply { }
}

/*************************************************************************
*********************** D E P A R S E R *******************************
*************************************************************************/

control MyDeparser(packet_out packet, in headers hdr) {
apply {
packet.emit(hdr.ethernet);
packet.emit(hdr.ipv4);
}
}

/*************************************************************************
*********************** S W I T C H *******************************
*************************************************************************/

V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main;

I hope you can help me, my email is absuarez@uw.edu just in case.

inside your action, I think you need to update the ethernet.dstAddr to point to the MAC address of the host h2.
to do so, you need to add another parameter to your action, and set its value from the control plane in a similar way to how you are setting the egress spec.
probably the ethernet interface is discarding the packet as it doesn’t match its HW address.

Thanks for responding and providing your inside.

So, I am uploading 2 figures that I hope u can see(if not, they are in Drive: P4 and BMV2 errors - Google Drive)

  1. My code defines a table to pass the packets from one interface to another, it does not filter anything, in theory, any packet carrying any protocol would make it through the bmv2 switch.

  2. Because I dont have restrictions, each time one host wants to reach another one, this would use ARP to get the destination MAC address, hence, I dont need to change the MAC address in the switch because the source host already is sending the packet to the right MAC, also, both hosts are in the same network and the bmv2 is just allowing the packets to go from one interface to the other.

  3. If u can see the images, you can see that the SYN packets reach from h1 to h2, BUT, the h2 is not responding at all, hence, my conclusion is that either to use bmv2 with mininet you need to maybe set up something else to support TCP, or perhaps the bmv2 is deparsing or doing something to the packet for the h2 drop it and not respond at all.

I hope u can see the images and have a better overview of my problem.
Have you had success communicating hosts over TCP with bmv2 switch before?

Best,
Anthony

A switch, whether P4-programmable or not, typically does not get involved in the TCP protocol except by forwarding packets to the proper destination host. Your P4 program does not even mention TCP, which should mean that as long as the logic of your program is not dropping packets, or sending them to the wrong destination host, nor modifying them in some way such that the destination host discards them (e.g. that could happen if your P4 code was modifying the IPv4 header in some way that left the header checksum incorrect), then all should be well.

I would recommend doing as simple of a test as you can, e.g. trying to initiate a single TCP connection from one host to another, and looking at one or both of the following:

(a) the logs of the simple_switch_grpc program, to see if it is receiving a TCP SYN packet from the client host, and forwarding it out the port you expect to the server host. If that is happening, is the switch also receiving a SYN+ACK packet from the server host and forwarding it to the client host?

(b) Using a program like tcpdump or wireshark to record and examine packets sent and received by the hosts on their network interfaces, to see if packets are reaching there.

Hello Andy,

I am so Glad you commented.
Yes, my P4 program is doing nothing to the TCP header, I have another program that does it, but because it was not working I decided to test it in the simplest way possible with the program I provided before. The program provided just passes the traffic from one interface to another, thats it, it does not modify the header or anything.

As u said when we initiate a TCP connection the handshake is in this way:
h1: client, h2:server
h1—SYN–>h2
h2—SYN ACK -->h1
h1—ACK—>h1

So, I ve been sniffing interfaces and debug the bmv2 switch, and the bmv2 is passing the SYN packet without issues, however, when h2 receives the packet it seems like it is dropping it, because h2 is not sending an SYN ACK back, the bmv2 switch never receives the response and of course neither does h1.
In the image I am attaching I am providing the evidence.
BTW, when I test the topology with OVS the tcp connection(over port 80) works without problems.

Now, it seems that either my program is wrong, or maybe the bmv2 is malforming or doing something with the packet that produces h2 to drop the packet.

I thought it was my LAB that I set up, but, the University of South Carolina has an introductory course about P4 and data-plane programmability and they provide an online Lab, and the behaviour is the same in their lab, and I tested it with their code.
Beside I checked this set of execizes that you contributed with: GitHub - p4lang/tutorials: P4 language tutorials

and usually the tests are done with a python script over TCP on port 1234, however, it never goes trhough the process of openning the TCP port in the conventional way(syn,syn ack,ack), but it just sends a message over SYN to the IP destination and broasscast MAC ff…ff. So, that test is more like using UDP(without ackledgment), which in a real scenario it would have no use.

Right now I testing how granular P4 can be and I havent had problems up to layer3, however, now that I am testing layer 4 protocols I am seeing problems.
by chance DO YOU HAVE CODE TO CREATE TCP TABLES? that for instance allow us to define which tcp ports allow or block.

Right nw I am debugging the HTTP server to see why is dropping the SYN packet.

Hope to hear from u soon.

Sorry for the late reply, a wireshark capture is very helpful in diagnosing this kind of problems.
my second assumption is that you have hardware offload enabled which leaves the checksum calculations to the network card.
However, as your packets are going through virtual interfaces this is never performed.
try disabling hardware offload on both hosts.
run:

ethtool --offload {interface_name} rx off tx off 
ethtool --offload {interface_name} sg off

If your P4 program is ignoring the value of the IP protocol field, then it should be processing packets identically regardless of whether they are ICMP, TCP, UDP, etc. That is a good thing when you are trying to simplify your debugging life, as it should not matter (at least not to the P4 switch) which protocol of packets it is processing.

As mentioned in another reply here, and to a comment from Antonin Bas on a similar Github issue (perhaps yours?) here TCP packets seem not to be processed correctly by bmv2 · Issue #1228 · p4lang/behavioral-model · GitHub there are known issues when checksum offloading is enabled in NIC devices on the path.

Searching for “checksum offload” in this web forum I found this post, which might be helpful: Connect mininet hosts to root namespace - #10 by Sally

It WORKED!!!

Thank u Faris, I had to deactivate the “checksum offloading”, and that solved the problem!!!
More information how to do it can be found here:

Best regards!

Yeah, it was also my post… I did not see it until now…

Thank u, the problem was solved turnning off “checksum offloading”.
I will continue testing the granilarity that P4 offers in next months and If I find any challenge on my way I will be back here.

This the TCP program BTW, now working with no issues:
/* -- P4_16 -- */
#include <core.p4>
#include <v1model.p4>

const bit<16> TYPE_ARP = 0x0806;
const bit<16> TYPE_IPV4 = 0x800;

/*************************************************************************
*********************** H E A D E R S ***********************************
*************************************************************************/

typedef bit<9> egressSpec_t;
typedef bit<48> macAddr_t;
typedef bit<32> ip4Addr_t;

header ethernet_t {
macAddr_t dstAddr;
macAddr_t srcAddr;
bit<16> etherType;
}

header ipv4_t {
bit<4> version;
bit<4> ihl;
bit<8> diffserv;
bit<16> totalLen;
bit<16> identification;
bit<3> flags;
bit<13> fragOffset;
bit<8> ttl;
bit<8> protocol;
bit<16> hdrChecksum;
ip4Addr_t srcAddr;
ip4Addr_t dstAddr;
}

header tcp_t {
bit<16> srcPort;
bit<16> dstPort;
}

header arp_t {
bit<16> hwtype;
bit<16> protoType;
bit<8> hwlen;
bit<8> protolen;
bit<16> opcode;
bit<48> hwsrc;
bit<32> protosrc;
bit<48> hwdst;
bit<32> protodst;
}

struct metadata {
/* empty */
}

struct headers {
ethernet_t ethernet;
ipv4_t ipv4;
tcp_t tcp;
arp_t arp;
}

/*************************************************************************
*********************** P A R S E R ***********************************
*************************************************************************/

parser MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {

state start {
    transition parse_ethernet;
}

state parse_ethernet {
    packet.extract(hdr.ethernet);
    transition select(hdr.ethernet.etherType) {
        TYPE_IPV4: parse_ipv4;
        TYPE_ARP: parse_arp;
        default: accept;
    }
}

state parse_ipv4 {
packet.extract(hdr.ipv4);
transition select(hdr.ipv4.protocol) {
    6: parse_tcp; // TCP protocol number
    default: accept;
}
}

state parse_arp {
    packet.extract(hdr.arp);
    transition accept; 
}

state parse_tcp {
    packet.extract(hdr.tcp);
    transition accept;
}

}

/*************************************************************************
************ C H E C K S U M V E R I F I C A T I O N *************
*************************************************************************/

control MyVerifyChecksum(inout headers hdr, inout metadata meta) {
apply { }

}

/*************************************************************************
************** I N G R E S S P R O C E S S I N G *******************
*************************************************************************/

control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() {
mark_to_drop(standard_metadata);
}

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

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

table src_tcp_port_80 {
    key = {
        standard_metadata.ingress_port: exact;
        hdr.tcp.srcPort: exact;           // Source TCP port
    }
    actions = {
        forward;  // Action to forward packet
        drop;     // Action to drop packet
    }
    size = 256;
    default_action = drop();
}

table dst_tcp_port_80 {
    key = {
        standard_metadata.ingress_port: exact;
        hdr.tcp.dstPort: exact;           // Source TCP port
    }
    actions = {
        forward;  // Action to forward packet
        drop;     // Action to drop packet
    }
    size = 256;
    default_action = drop();
}

table arp_table {
    key = {
        hdr.arp.opcode: exact; // Match on the ARP opcode
        standard_metadata.ingress_port: exact;
    }
    actions = {
        arp_forward; // Action to forward ARP packets
        drop;
    }
    size = 256;
    default_action = drop();
}

apply {
if (hdr.arp.isValid()) {
    arp_table.apply();
} else if (hdr.ipv4.isValid() && hdr.tcp.isValid()) {
    // Check for TCP source port 80
    if (hdr.tcp.srcPort == 80) {
        src_tcp_port_80.apply();
    } else if (hdr.tcp.dstPort == 80) {
        // Check for TCP destination port 80
        dst_tcp_port_80.apply();
    } else {
        // Drop packets with neither source nor destination port 80
        drop();
    }
}

}
}

/*************************************************************************
**************** E G R E S S P R O C E S S I N G *******************
*************************************************************************/

control MyEgress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
apply { }
}

/*************************************************************************
************* C H E C K S U M C O M P U T A T I O N **************
*************************************************************************/

control MyComputeChecksum(inout headers hdr, inout metadata meta) {
apply { }
}

/*************************************************************************
*********************** D E P A R S E R *******************************
*************************************************************************/

control MyDeparser(packet_out packet, in headers hdr) {
apply {
packet.emit(hdr.ethernet);
packet.emit(hdr.arp);
packet.emit(hdr.ipv4);
packet.emit(hdr.tcp);
}
}

/*************************************************************************
*********************** S W I T C H *******************************
*************************************************************************/

V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main;