bbaaef
From 4fb306df98419d45889dff6a5b4fb0c21f237609 Mon Sep 17 00:00:00 2001
bbaaef
From: Numan Siddique <numans@ovn.org>
bbaaef
Date: Fri, 29 May 2020 23:29:37 +0530
bbaaef
Subject: [PATCH] pinctrl: Support DHCPRELEASE and DHCPINFORM in native OVN
bbaaef
 dhcp responder.
bbaaef
bbaaef
Right now we ignore these dhcp packets. This patch adds the support
bbaaef
as per RFC 2131.
bbaaef
bbaaef
Change-Id: I57091f18212b93e3a366a97120f2a54009fde1d4
bbaaef
Acked-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
bbaaef
Acked-by: Mark Michelson <mmichels@redhat.com>
bbaaef
Signed-off-by: Numan Siddique <numans@ovn.org>
bbaaef
bbaaef
(cherry-picked from upstream ovn master commit e008a4d46020a778b8f1f85b9dfd7c9e9b6fde21)
bbaaef
---
bbaaef
 ovn/controller/pinctrl.c | 125 +++++++++++++++++++++++++++++++--------
bbaaef
 ovn/lib/ovn-l7.h         |  12 ++++
bbaaef
 tests/ovn.at             | 117 +++++++++++++++++++++++++++++++++++-
bbaaef
 3 files changed, 227 insertions(+), 27 deletions(-)
bbaaef
bbaaef
diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
bbaaef
index 51a0f0224..b9e115a39 100644
bbaaef
--- a/ovn/controller/pinctrl.c
bbaaef
+++ b/ovn/controller/pinctrl.c
bbaaef
@@ -979,11 +979,13 @@ static void
bbaaef
 pinctrl_handle_put_dhcp_opts(
bbaaef
     struct rconn *swconn,
bbaaef
     struct dp_packet *pkt_in, struct ofputil_packet_in *pin,
bbaaef
-    struct ofpbuf *userdata, struct ofpbuf *continuation)
bbaaef
+    struct flow *in_flow, struct ofpbuf *userdata,
bbaaef
+    struct ofpbuf *continuation)
bbaaef
 {
bbaaef
     enum ofp_version version = rconn_get_version(swconn);
bbaaef
     enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
bbaaef
     struct dp_packet *pkt_out_ptr = NULL;
bbaaef
+    struct ofpbuf *dhcp_inform_reply_buf = NULL;
bbaaef
     uint32_t success = 0;
bbaaef
 
bbaaef
     /* Parse result field. */
bbaaef
@@ -1107,22 +1109,15 @@ pinctrl_handle_put_dhcp_opts(
bbaaef
         VLOG_WARN_RL(&rl, "Missing DHCP message type");
bbaaef
         goto exit;
bbaaef
     }
bbaaef
-    if (*in_dhcp_msg_type != DHCP_MSG_DISCOVER &&
bbaaef
-        *in_dhcp_msg_type != DHCP_MSG_REQUEST) {
bbaaef
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
bbaaef
-        VLOG_WARN_RL(&rl, "Invalid DHCP message type: %d", *in_dhcp_msg_type);
bbaaef
-        goto exit;
bbaaef
-    }
bbaaef
 
bbaaef
-    uint8_t msg_type;
bbaaef
-    if (*in_dhcp_msg_type == DHCP_MSG_DISCOVER) {
bbaaef
+    struct ofpbuf *reply_dhcp_opts_ptr = userdata;
bbaaef
+    uint8_t msg_type = 0;
bbaaef
+
bbaaef
+    switch (*in_dhcp_msg_type) {
bbaaef
+    case DHCP_MSG_DISCOVER:
bbaaef
         msg_type = DHCP_MSG_OFFER;
bbaaef
-    } else {
bbaaef
-        /* This is a DHCPREQUEST. If the client has requested an IP that
bbaaef
-         * does not match the offered IP address, reply with a NAK. The
bbaaef
-         * requested IP address may be supplied either via Requested IP Address
bbaaef
-         * (opt 50) or via ciaddr, depending on the client's state.
bbaaef
-         */
bbaaef
+        break;
bbaaef
+    case DHCP_MSG_REQUEST: {
bbaaef
         msg_type = DHCP_MSG_ACK;
bbaaef
         if (request_ip != *offer_ip) {
bbaaef
             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
bbaaef
@@ -1131,12 +1126,81 @@ pinctrl_handle_put_dhcp_opts(
bbaaef
                          IP_ARGS(*offer_ip));
bbaaef
             msg_type = DHCP_MSG_NAK;
bbaaef
         }
bbaaef
+        break;
bbaaef
+    }
bbaaef
+    case OVN_DHCP_MSG_RELEASE: {
bbaaef
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 40);
bbaaef
+        const struct eth_header *l2 = dp_packet_eth(pkt_in);
bbaaef
+        VLOG_INFO_RL(&rl, "DHCPRELEASE "ETH_ADDR_FMT " "IP_FMT"",
bbaaef
+                     ETH_ADDR_ARGS(l2->eth_src),
bbaaef
+                     IP_ARGS(in_dhcp_data->ciaddr));
bbaaef
+        break;
bbaaef
+    }
bbaaef
+    case OVN_DHCP_MSG_INFORM: {
bbaaef
+        /* RFC 2131 section 3.4.
bbaaef
+         * Remove all the offer ip related dhcp options and
bbaaef
+         * all the time related dhcp options.
bbaaef
+         * Loop through the dhcp option defined in the userdata buffer
bbaaef
+         * and copy all the options into dhcp_inform_reply_buf skipping
bbaaef
+         * the not required ones.
bbaaef
+         * */
bbaaef
+        msg_type = DHCP_MSG_ACK;
bbaaef
+        in_dhcp_ptr = userdata->data;
bbaaef
+        end = (const char *)userdata->data + userdata->size;
bbaaef
+
bbaaef
+        /* The buf size cannot be greater > userdata->size. */
bbaaef
+        dhcp_inform_reply_buf = ofpbuf_new(userdata->size);
bbaaef
+
bbaaef
+        reply_dhcp_opts_ptr = dhcp_inform_reply_buf;
bbaaef
+        while (in_dhcp_ptr < end) {
bbaaef
+            const struct dhcp_opt_header *in_dhcp_opt =
bbaaef
+                (const struct dhcp_opt_header *)in_dhcp_ptr;
bbaaef
+
bbaaef
+            switch (in_dhcp_opt->code) {
bbaaef
+            case OVN_DHCP_OPT_CODE_NETMASK:
bbaaef
+            case OVN_DHCP_OPT_CODE_LEASE_TIME:
bbaaef
+            case OVN_DHCP_OPT_CODE_T1:
bbaaef
+            case OVN_DHCP_OPT_CODE_T2:
bbaaef
+                break;
bbaaef
+            default:
bbaaef
+                /* Copy the dhcp option to reply_dhcp_opts_ptr. */
bbaaef
+                ofpbuf_put(reply_dhcp_opts_ptr, in_dhcp_opt,
bbaaef
+                           in_dhcp_opt->len + sizeof *in_dhcp_opt);
bbaaef
+                break;
bbaaef
+            }
bbaaef
+
bbaaef
+            in_dhcp_ptr += sizeof *in_dhcp_opt;
bbaaef
+            if (in_dhcp_ptr > end) {
bbaaef
+                break;
bbaaef
+            }
bbaaef
+            in_dhcp_ptr += in_dhcp_opt->len;
bbaaef
+            if (in_dhcp_ptr > end) {
bbaaef
+                break;
bbaaef
+            }
bbaaef
+        }
bbaaef
+
bbaaef
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 40);
bbaaef
+        VLOG_INFO_RL(&rl, "DHCPINFORM from "ETH_ADDR_FMT " "IP_FMT"",
bbaaef
+                     ETH_ADDR_ARGS(in_flow->dl_src),
bbaaef
+                     IP_ARGS(in_flow->nw_src));
bbaaef
+
bbaaef
+        break;
bbaaef
+    }
bbaaef
+    default: {
bbaaef
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
bbaaef
+        VLOG_WARN_RL(&rl, "Invalid DHCP message type: %d", *in_dhcp_msg_type);
bbaaef
+        goto exit;
bbaaef
+    }
bbaaef
+    }
bbaaef
+
bbaaef
+    if (!msg_type) {
bbaaef
+        goto exit;
bbaaef
     }
bbaaef
 
bbaaef
     /* Frame the DHCP reply packet
bbaaef
-     * Total DHCP options length will be options stored in the userdata +
bbaaef
-     * 16 bytes. Note that the DHCP options stored in userdata are not included
bbaaef
-     * in DHCPNAK messages.
bbaaef
+     * Total DHCP options length will be options stored in the
bbaaef
+     * reply_dhcp_opts_ptr + 16 bytes. Note that the DHCP options stored in
bbaaef
+     * reply_dhcp_opts_ptr are not included in DHCPNAK messages.
bbaaef
      *
bbaaef
      * --------------------------------------------------------------
bbaaef
      *| 4 Bytes (dhcp cookie) | 3 Bytes (option type) | DHCP options |
bbaaef
@@ -1146,7 +1210,7 @@ pinctrl_handle_put_dhcp_opts(
bbaaef
      */
bbaaef
     uint16_t new_l4_size = UDP_HEADER_LEN + DHCP_HEADER_LEN + 16;
bbaaef
     if (msg_type != DHCP_MSG_NAK) {
bbaaef
-        new_l4_size += userdata->size;
bbaaef
+        new_l4_size += reply_dhcp_opts_ptr->size;
bbaaef
     }
bbaaef
     size_t new_packet_size = pkt_in->l4_ofs + new_l4_size;
bbaaef
 
bbaaef
@@ -1171,12 +1235,18 @@ pinctrl_handle_put_dhcp_opts(
bbaaef
     struct dhcp_header *dhcp_data = dp_packet_put(
bbaaef
         &pkt_out, dp_packet_pull(pkt_in, DHCP_HEADER_LEN), DHCP_HEADER_LEN);
bbaaef
     dhcp_data->op = DHCP_OP_REPLY;
bbaaef
-    dhcp_data->yiaddr = (msg_type == DHCP_MSG_NAK) ? 0 : *offer_ip;
bbaaef
+
bbaaef
+    if (*in_dhcp_msg_type != OVN_DHCP_MSG_INFORM) {
bbaaef
+        dhcp_data->yiaddr = (msg_type == DHCP_MSG_NAK) ? 0 : *offer_ip;
bbaaef
+    } else {
bbaaef
+        dhcp_data->yiaddr = 0;
bbaaef
+    }
bbaaef
+
bbaaef
     dp_packet_put(&pkt_out, &magic_cookie, sizeof(ovs_be32));
bbaaef
 
bbaaef
     uint16_t out_dhcp_opts_size = 12;
bbaaef
     if (msg_type != DHCP_MSG_NAK) {
bbaaef
-      out_dhcp_opts_size += userdata->size;
bbaaef
+      out_dhcp_opts_size += reply_dhcp_opts_ptr->size;
bbaaef
     }
bbaaef
     uint8_t *out_dhcp_opts = dp_packet_put_zeros(&pkt_out,
bbaaef
                                                  out_dhcp_opts_size);
bbaaef
@@ -1187,8 +1257,9 @@ pinctrl_handle_put_dhcp_opts(
bbaaef
     out_dhcp_opts += 3;
bbaaef
 
bbaaef
     if (msg_type != DHCP_MSG_NAK) {
bbaaef
-      memcpy(out_dhcp_opts, userdata->data, userdata->size);
bbaaef
-      out_dhcp_opts += userdata->size;
bbaaef
+        memcpy(out_dhcp_opts, reply_dhcp_opts_ptr->data,
bbaaef
+               reply_dhcp_opts_ptr->size);
bbaaef
+        out_dhcp_opts += reply_dhcp_opts_ptr->size;
bbaaef
     }
bbaaef
 
bbaaef
     /* Padding */
bbaaef
@@ -1236,6 +1307,10 @@ exit:
bbaaef
     if (pkt_out_ptr) {
bbaaef
         dp_packet_uninit(pkt_out_ptr);
bbaaef
     }
bbaaef
+
bbaaef
+    if (dhcp_inform_reply_buf) {
bbaaef
+        ofpbuf_delete(dhcp_inform_reply_buf);
bbaaef
+    }
bbaaef
 }
bbaaef
 
bbaaef
 static bool
bbaaef
@@ -1936,8 +2011,8 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg)
bbaaef
         break;
bbaaef
 
bbaaef
     case ACTION_OPCODE_PUT_DHCP_OPTS:
bbaaef
-        pinctrl_handle_put_dhcp_opts(swconn, &packet, &pin, &userdata,
bbaaef
-                                     &continuation);
bbaaef
+        pinctrl_handle_put_dhcp_opts(swconn, &packet, &pin, &headers,
bbaaef
+                                     &userdata, &continuation);
bbaaef
         break;
bbaaef
 
bbaaef
     case ACTION_OPCODE_ND_NA:
bbaaef
diff --git a/ovn/lib/ovn-l7.h b/ovn/lib/ovn-l7.h
bbaaef
index c43218224..f81acb0f4 100644
bbaaef
--- a/ovn/lib/ovn-l7.h
bbaaef
+++ b/ovn/lib/ovn-l7.h
bbaaef
@@ -34,6 +34,14 @@ struct gen_opts_map {
bbaaef
 
bbaaef
 #define DHCP_BROADCAST_FLAG 0x8000
bbaaef
 
bbaaef
+/* These are not defined in ovs/lib/dhcp.h and hence defined here with
bbaaef
+ * OVN_DHCP_OPT_CODE_<opt_name>.
bbaaef
+ */
bbaaef
+#define OVN_DHCP_OPT_CODE_NETMASK      1
bbaaef
+#define OVN_DHCP_OPT_CODE_LEASE_TIME   51
bbaaef
+#define OVN_DHCP_OPT_CODE_T1           58
bbaaef
+#define OVN_DHCP_OPT_CODE_T2           59
bbaaef
+
bbaaef
 #define DHCP_OPTION(NAME, CODE, TYPE) \
bbaaef
     {.name = NAME, .code = CODE, .type = TYPE}
bbaaef
 
bbaaef
@@ -161,6 +169,10 @@ struct dhcp_opt6_header {
bbaaef
     ovs_be16 size;
bbaaef
 };
bbaaef
 
bbaaef
+/* These are not defined in ovs/lib/dhcp.h, hence defining here. */
bbaaef
+#define OVN_DHCP_MSG_RELEASE        7
bbaaef
+#define OVN_DHCP_MSG_INFORM         8
bbaaef
+
bbaaef
 /* Supported DHCPv6 Message Types */
bbaaef
 #define DHCPV6_MSG_TYPE_SOLICIT     1
bbaaef
 #define DHCPV6_MSG_TYPE_ADVT        2
bbaaef
diff --git a/tests/ovn.at b/tests/ovn.at
bbaaef
index 0c545b0b2..203c1d80f 100644
bbaaef
--- a/tests/ovn.at
bbaaef
+++ b/tests/ovn.at
bbaaef
@@ -4639,6 +4639,12 @@ test_dhcp() {
bbaaef
     done
bbaaef
     if test $offer_ip != 0; then
bbaaef
         local srv_mac=$1 srv_ip=$2 dhcp_reply_type=$3 expected_dhcp_opts=$4
bbaaef
+        local offered_ip=$offer_ip
bbaaef
+        if [[ "$dhcp_type" == "08" ]]; then
bbaaef
+            # DHCP ACK for DHCP INFORM should not have any offer ip.
bbaaef
+            offered_ip=00000000
bbaaef
+        fi
bbaaef
+
bbaaef
         # total IP length will be the IP length of the request packet
bbaaef
         # (which is 272 in our case) + 8 (padding bytes) + (expected_dhcp_opts / 2)
bbaaef
         ip_len=`expr 280 + ${#expected_dhcp_opts} / 2`
bbaaef
@@ -4654,7 +4660,7 @@ test_dhcp() {
bbaaef
         if test $dhcp_reply_type = 06; then
bbaaef
             reply=${reply}00000000
bbaaef
         else
bbaaef
-            reply=${reply}${offer_ip}
bbaaef
+            reply=${reply}${offered_ip}
bbaaef
         fi
bbaaef
         # next server ip address, relay agent ip address, client mac address
bbaaef
         reply=${reply}0000000000000000${src_mac}
bbaaef
@@ -4794,7 +4800,7 @@ rm -f 2.expected
bbaaef
 ciaddr=`ip_to_hex 0 0 0 0`
bbaaef
 offer_ip=0
bbaaef
 request_ip=0
bbaaef
-test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 0 1 1
bbaaef
+test_dhcp 2 f00000000002 09 0 $ciaddr $offer_ip $request_ip 0 1 1
bbaaef
 
bbaaef
 # NXT_RESUMEs should be 4.
bbaaef
 OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
bbaaef
@@ -4964,6 +4970,113 @@ AT_CHECK([cat 1.packets | cut -c -48], [0], [expout])
bbaaef
 cat 1.expected | cut -c 53- > expout
bbaaef
 AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout])
bbaaef
 
bbaaef
+reset_pcap_file hv1-vif1 hv1/vif1
bbaaef
+reset_pcap_file hv1-vif2 hv1/vif2
bbaaef
+rm -f 1.expected
bbaaef
+rm -f 2.expected
bbaaef
+
bbaaef
+# Send DHCPRELEASE.
bbaaef
+offer_ip=0
bbaaef
+server_ip=`ip_to_hex 10 0 0 1`
bbaaef
+ciaddr=`ip_to_hex 10 0 0 6`
bbaaef
+request_ip=0
bbaaef
+expected_dhcp_opts=0
bbaaef
+test_dhcp 2 f00000000002 07 0 $ciaddr $offer_ip $request_ip 0 ff1000000001
bbaaef
+
bbaaef
+# NXT_RESUMEs should be 10.
bbaaef
+OVS_WAIT_UNTIL([test 10 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
bbaaef
+
bbaaef
+# There is no reply for this. Check for the INFO log in ovn-controller.log
bbaaef
+AT_CHECK([test 1 = $(cat hv1/ovn-controller.log | \
bbaaef
+grep "DHCPRELEASE f0:00:00:00:00:02 10.0.0.6" -c)])
bbaaef
+
bbaaef
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
bbaaef
+AT_CHECK([cat 2.packets], [0], [])
bbaaef
+
bbaaef
+reset_pcap_file hv1-vif1 hv1/vif1
bbaaef
+reset_pcap_file hv1-vif2 hv1/vif2
bbaaef
+rm -f 1.expected
bbaaef
+rm -f 2.expected
bbaaef
+
bbaaef
+# Send DHCPINFORM
bbaaef
+offer_ip=`ip_to_hex 10 0 0 6`
bbaaef
+server_ip=`ip_to_hex 10 0 0 1`
bbaaef
+ciaddr=$offer_ip
bbaaef
+request_ip=0
bbaaef
+src_ip=$offer_ip
bbaaef
+dst_ip=$server_ip
bbaaef
+# In the expected_dhcp_opts we should not see 330400000e10 which is
bbaaef
+# dhcp lease time option and 0104ffffff00 which is subnet mask option.
bbaaef
+expected_dhcp_opts=03040a00000136040a000001
bbaaef
+test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
bbaaef
+
bbaaef
+# NXT_RESUMEs should be 11.
bbaaef
+OVS_WAIT_UNTIL([test 11 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
bbaaef
+
bbaaef
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
bbaaef
+cat 2.expected | cut -c -48 > expout
bbaaef
+AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
bbaaef
+# Skipping the IPv4 checksum.
bbaaef
+cat 2.expected | cut -c 53- > expout
bbaaef
+AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
bbaaef
+
bbaaef
+# Now add the dhcp option T1 to the dhcp options.
bbaaef
+ovn-nbctl set dhcp_options ${d1} options:T1=4000
bbaaef
+
bbaaef
+reset_pcap_file hv1-vif1 hv1/vif1
bbaaef
+reset_pcap_file hv1-vif2 hv1/vif2
bbaaef
+rm -f 1.expected
bbaaef
+rm -f 2.expected
bbaaef
+
bbaaef
+# Send DHCPREQUEST to make sure that T1 is in the reply dhcp options.
bbaaef
+offer_ip=`ip_to_hex 10 0 0 6`
bbaaef
+server_ip=`ip_to_hex 10 0 0 1`
bbaaef
+ciaddr=$offer_ip
bbaaef
+request_ip=0
bbaaef
+src_ip=$offer_ip
bbaaef
+dst_ip=$server_ip
bbaaef
+# In the expected_dhcp_opts we should not see 330400000e10 which is
bbaaef
+# dhcp lease time option.
bbaaef
+expected_dhcp_opts=3a0400000fa0330400000e100104ffffff0003040a00000136040a000001
bbaaef
+test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
bbaaef
+
bbaaef
+# NXT_RESUMEs should be 12.
bbaaef
+OVS_WAIT_UNTIL([test 12 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
bbaaef
+
bbaaef
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
bbaaef
+cat 2.expected | cut -c -48 > expout
bbaaef
+AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
bbaaef
+# Skipping the IPv4 checksum.
bbaaef
+cat 2.expected | cut -c 53- > expout
bbaaef
+AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
bbaaef
+
bbaaef
+reset_pcap_file hv1-vif1 hv1/vif1
bbaaef
+reset_pcap_file hv1-vif2 hv1/vif2
bbaaef
+rm -f 1.expected
bbaaef
+rm -f 2.expected
bbaaef
+
bbaaef
+# Now send DHCPINFORM again.
bbaaef
+offer_ip=`ip_to_hex 10 0 0 6`
bbaaef
+server_ip=`ip_to_hex 10 0 0 1`
bbaaef
+ciaddr=00000000
bbaaef
+request_ip=0
bbaaef
+src_ip=$offer_ip
bbaaef
+dst_ip=$server_ip
bbaaef
+# In the expected_dhcp_opts we should not see 330400000e10 which is
bbaaef
+# dhcp lease time option and 0104ffffff00 which is subnet mask option.
bbaaef
+expected_dhcp_opts=03040a00000136040a000001
bbaaef
+test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
bbaaef
+
bbaaef
+# NXT_RESUMEs should be 13.
bbaaef
+OVS_WAIT_UNTIL([test 13 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
bbaaef
+
bbaaef
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
bbaaef
+cat 2.expected | cut -c -48 > expout
bbaaef
+AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
bbaaef
+# Skipping the IPv4 checksum.
bbaaef
+cat 2.expected | cut -c 53- > expout
bbaaef
+AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
bbaaef
+
bbaaef
 OVN_CLEANUP([hv1])
bbaaef
 
bbaaef
 AT_CLEANUP
bbaaef
-- 
bbaaef
2.26.2
bbaaef