From: Alejandro Pacheco <[email protected]>
An output action with max_len trims the packet with dp_packet_set_size()
alone, keeping the layer offsets, L2 padding, checksum offload flags and
TSO segment size of the full packet. Completing a partial L4 checksum
on send then works on an L4 header that was cut short or lies past the
new end, and reads and writes past the packet data.
Add dp_packet_truncate() and use it for both truncation sites. It
shrinks the L2 padding, clears layer offsets past the new end and drops
the L4 checksum and segmentation offloads. The IPv4 header checksum
offload is kept only while the whole header remains.
Fixes: 3337e6d91c5b ("userspace: Enable L4 checksum offloading by default.")
Assisted-by: claude-opus-5-5, Claude Code
Signed-off-by: Alejandro Pacheco <[email protected]>
---
lib/dp-packet.c | 56 ++++++++++++++++++++++++++++++++++++++++++++
lib/dp-packet.h | 3 ++-
lib/dpif.c | 2 +-
tests/dpif-netdev.at | 45 +++++++++++++++++++++++++++++++++++
4 files changed, 104 insertions(+), 2 deletions(-)
diff --git a/lib/dp-packet.c b/lib/dp-packet.c
index fb52fec68..8febe8425 100644
--- a/lib/dp-packet.c
+++ b/lib/dp-packet.c
@@ -529,6 +529,62 @@ dp_packet_resize_l2_5(struct dp_packet *b, int increment)
return dp_packet_data(b);
}
+static void
+dp_packet_truncate_layer_offset(uint16_t *offset, uint32_t size)
+{
+ if (*offset != UINT16_MAX && *offset > size) {
+ *offset = UINT16_MAX;
+ }
+}
+
+/* Returns true if 'size' bytes at 'ip' hold an IPv4 header with all of its
+ * options. */
+static bool
+dp_packet_ip_header_fits(const struct ip_header *ip, size_t size)
+{
+ return ip && size >= IP_HEADER_LEN
+ && IP_IHL(ip->ip_ihl_ver) * 4 <= size;
+}
+
+/* Removes data from the tail of 'b', leaving it 'size' bytes long. The L2
+ * padding shrinks with the data, layer offsets past the new end are cleared,
+ * and checksum and segmentation offloads that depend on the removed data are
+ * dropped. */
+void
+dp_packet_truncate(struct dp_packet *b, uint32_t size)
+{
+ uint32_t removed;
+
+ ovs_assert(size <= dp_packet_size(b));
+ removed = dp_packet_size(b) - size;
+ dp_packet_set_size(b, size);
+
+ if (removed <= b->l2_pad_size) {
+ b->l2_pad_size -= removed;
+ return;
+ }
+ b->l2_pad_size = 0;
+
+ dp_packet_truncate_layer_offset(&b->l2_5_ofs, size);
+ dp_packet_truncate_layer_offset(&b->l3_ofs, size);
+ dp_packet_truncate_layer_offset(&b->l4_ofs, size);
+ dp_packet_truncate_layer_offset(&b->inner_l3_ofs, size);
+ dp_packet_truncate_layer_offset(&b->inner_l4_ofs, size);
+
+ /* An L4 checksum covers the removed data, so it can no longer be
+ * completed. An IPv4 header checksum only needs the whole header. */
+ b->offloads &= ~(DP_PACKET_OL_L4_CKSUM_MASK
+ | DP_PACKET_OL_INNER_L4_CKSUM_MASK);
+ if (!dp_packet_ip_header_fits(dp_packet_l3(b), dp_packet_l3_size(b))) {
+ b->offloads &= ~DP_PACKET_OL_IP_CKSUM_MASK;
+ }
+ if (!dp_packet_ip_header_fits(dp_packet_inner_l3(b),
+ dp_packet_inner_l3_size(b))) {
+ b->offloads &= ~DP_PACKET_OL_INNER_IP_CKSUM_MASK;
+ }
+ dp_packet_set_tso_segsz(b, 0);
+}
+
/* Adjust the size of the l2 portion of the dp_packet, updating the l2
* pointer and the layer offsets. The caller is responsible for
* modifying the contents. */
diff --git a/lib/dp-packet.h b/lib/dp-packet.h
index 6296cfdc7..e4dff1436 100644
--- a/lib/dp-packet.h
+++ b/lib/dp-packet.h
@@ -183,6 +183,7 @@ static inline void dp_packet_set_tso_segsz(struct dp_packet
*, uint16_t);
void *dp_packet_resize_l2(struct dp_packet *, int increment);
void *dp_packet_resize_l2_5(struct dp_packet *, int increment);
+void dp_packet_truncate(struct dp_packet *, uint32_t size);
static inline void *dp_packet_eth(const struct dp_packet *);
static inline void dp_packet_reset_outer_offsets(struct dp_packet *);
static inline void dp_packet_reset_offsets(struct dp_packet *);
@@ -1023,7 +1024,7 @@ dp_packet_batch_apply_cutlen(struct dp_packet_batch
*batch)
struct dp_packet *packet;
DP_PACKET_BATCH_FOR_EACH (i, packet, batch) {
- dp_packet_set_size(packet, dp_packet_get_send_len(packet));
+ dp_packet_truncate(packet, dp_packet_get_send_len(packet));
dp_packet_reset_cutlen(packet);
}
batch->trunc = false;
diff --git a/lib/dpif.c b/lib/dpif.c
index a62da6483..fe11d39f9 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -1245,7 +1245,7 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet_batch
*packets_,
if (!should_steal) {
packet = clone = dp_packet_clone(packet);
}
- dp_packet_set_size(packet, dp_packet_size(packet) - cutlen);
+ dp_packet_truncate(packet, dp_packet_size(packet) - cutlen);
}
execute.packet = packet;
diff --git a/tests/dpif-netdev.at b/tests/dpif-netdev.at
index 14f238e62..6ca411460 100644
--- a/tests/dpif-netdev.at
+++ b/tests/dpif-netdev.at
@@ -1234,6 +1234,51 @@ AT_CHECK([ovs-appctl dpif-netdev/pmd-perf-show | grep
'Simple Match hits'], [0],
OVS_VSWITCHD_STOP
AT_CLEANUP
+AT_SETUP([dpif-netdev - tcp csum offload with truncated output])
+AT_KEYWORDS([userspace offload truncate])
+OVS_VSWITCHD_START
+add_of_ports br0 1 2
+AT_CHECK([ovs-vsctl set Interface p2 options:tx_pcap=p2.pcap])
+
+AT_DATA([good_frame], m4_join([],
+dnl eth(src=8a:bf:7e:2f:05:84,dst=0a:8f:39:4f:e0:73,type=0x0800)
+[0a8f394fe0738abf7e2f05840800],
+dnl ipv4(src=192.168.123.2,dst=192.168.123.1,proto=6,tot_len=104)
+[45000068000000000006433cc0a87b02c0a87b01],
+dnl tcp(src=54392,dst=5201,flags=ack)
+[d47814510000000000000000501000006b720000],
+dnl 64 bytes of payload.
+[000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f],
+[202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f]
+))
+
+dnl Output cut to 40 bytes, inside the TCP header.
+AT_DATA([trunc_l4], m4_join([],
+[0a8f394fe0738abf7e2f05840800],
+[45000068000000000006433cc0a87b02c0a87b01],
+[d47814510000]
+))
+
+dnl Output cut to 30 bytes, inside the IPv4 header.
+AT_DATA([trunc_l3], m4_join([],
+[0a8f394fe0738abf7e2f05840800],
+[45000068000000000006433cc0a87b02]
+))
+
+AT_CHECK([ovs-ofctl add-flow br0 \
+ in_port=p1,tcp,actions='output(port=p2,max_len=40)'])
+CHECK_FWD_PACKET(p1, p2, ol_l4_rx_csum_set_partial, [good_frame], [trunc_l4])
+
+AT_CHECK([ovs-ofctl del-flows br0])
+AT_CHECK([ovs-ofctl add-flow br0 \
+ in_port=p1,tcp,actions='output(port=p2,max_len=30)'])
+AT_CHECK([ovs-vsctl set Interface p1 options:ol_ip_rx_csum_set_partial=true])
+CHECK_FWD_PACKET(p1, p2, ol_l4_rx_csum_set_partial, [good_frame], [trunc_l3])
+AT_CHECK([ovs-vsctl remove Interface p1 options ol_ip_rx_csum_set_partial])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
AT_SETUP([dpif-netdev - udp csum offload])
AT_KEYWORDS([userspace offload])
OVS_VSWITCHD_START(
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev