Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/core/drivers/dpdk/pmd.cc
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,19 @@ static rte_eth_conf DefaultEthConf(const rte_eth_dev_info *devinfo) {
port_conf.txmode.offloads =
(RTE_ETH_TX_OFFLOAD_IPV4_CKSUM | RTE_ETH_TX_OFFLOAD_UDP_CKSUM);

// TCP TX checksum offload for the native TCP transport. The TCP datapath
// sets RTE_MBUF_F_TX_TCP_CKSUM per mbuf and pre-loads the pseudo-header
// checksum, but the NIC only completes the checksum if the offload was
// enabled here at port-configure time; otherwise every TCP segment ships with
// an invalid checksum and the peer drops it. Enable it when supported.
if (tx_offload_capa & RTE_ETH_TX_OFFLOAD_TCP_CKSUM) {
port_conf.txmode.offloads |= RTE_ETH_TX_OFFLOAD_TCP_CKSUM;
} else {
LOG(WARNING) << "NIC does not support TCP TX checksum offload; the native "
"TCP transport will emit invalid checksums until a software "
"checksum fallback is added.";
}

if (tx_offload_capa & RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE) {
// TODO(ilias): Add option to the constructor to enable this offload.
LOG(WARNING)
Expand Down
29 changes: 29 additions & 0 deletions src/core/tcp_flow_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,35 @@ TEST_F(TcpFlowTest, ActiveOpen_SynAckWrongAck) {
dpdk::Packet::Free(bad_syn_ack);
}

// A retransmitted SYN must reuse the original sequence number, not consume a
// fresh one. Previously SendSyn incremented snd_nxt_ on every call, so after a
// single retransmit the peer's SYN-ACK (acking isn+1) no longer matched
// snd_nxt_ and the handshake could never complete.
TEST_F(TcpFlowTest, SynRetransmitIsSequenceIdempotent) {
auto flow = MakeFlow();
flow->InitiateHandshake();
const uint32_t isn = flow->snd_isn_;
ASSERT_EQ(flow->snd_nxt_, isn + 1);

// Force an RTO to retransmit the SYN.
for (uint32_t i = 0; i < TcpFlow::kInitialRTO; i++) {
EXPECT_TRUE(flow->PeriodicCheck());
}
EXPECT_TRUE(flow->PeriodicCheck()); // RTO fires → SYN retransmit.

// The retransmit must NOT have advanced the sequence number.
EXPECT_EQ(flow->snd_nxt_, isn + 1);
EXPECT_EQ(flow->state(), TcpFlow::State::kSynSent);

// A SYN-ACK acking isn+1 now completes the handshake (it would have been
// rejected as a wrong-ack before the fix).
auto* syn_ack = MakePacket(9000, isn + 1, Tcp::kSyn | Tcp::kAck);
flow->InputPacket(syn_ack);
EXPECT_EQ(flow->state(), TcpFlow::State::kEstablished);
EXPECT_EQ(flow->snd_una_, isn + 1);
dpdk::Packet::Free(syn_ack);
}

// ═══════════════════════════════════════════════════════════════
// Passive Open (Server) Handshake
// ═══════════════════════════════════════════════════════════════
Expand Down
28 changes: 26 additions & 2 deletions src/include/tcp_flow.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <packet.h>
#include <packet_pool.h>
#include <pmd.h>
#include <rte_ip.h>
#include <tcp.h>
#include <types.h>
#include <utils.h>
Expand Down Expand Up @@ -519,6 +520,20 @@ class TcpFlow {
tcph->urgent_ptr = be16_t(0);
}

/// Seed the TCP checksum field with the IPv4 pseudo-header partial checksum.
/// The DPDK TX offload contract for RTE_MBUF_F_TX_TCP_CKSUM (non-TSO) is:
/// software pre-loads the pseudo-header sum here, hardware completes it over
/// the TCP header + payload. Leaving it 0 (valid for UDP, where checksums
/// are optional) yields an invalid TCP checksum that the peer drops. Must run
/// after PrepareL3Header (IP total_length final) and offload_tcpv4_csum().
/// @note tcph->checksum is a raw uint16_t holding a network-order partial
/// checksum — do NOT wrap it in be16_t, which would byte-swap it.
void FinalizeTcpChecksum(dpdk::Packet* packet) const {
auto* ipv4h = packet->head_data<struct rte_ipv4_hdr*>(sizeof(Ethernet));
auto* tcph = packet->head_data<Tcp*>(sizeof(Ethernet) + sizeof(Ipv4));
tcph->checksum = rte_ipv4_phdr_cksum(ipv4h, /*ol_flags=*/0);
}

// ──────────────── Helpers: Send Control Packets ────────────────

void SendControlPacket(uint32_t seq, uint32_t ack, uint8_t flags) {
Expand All @@ -532,6 +547,7 @@ class TcpFlow {
PrepareL3Header(packet);
PrepareL4Header(packet, seq, ack, flags);
packet->offload_tcpv4_csum();
FinalizeTcpChecksum(packet);

txring_->SendPackets(&packet, 1);
}
Expand Down Expand Up @@ -563,13 +579,19 @@ class TcpFlow {
std::memcpy(&opts[2], &mss_net, sizeof(mss_net));

packet->offload_tcpv4_csum();
FinalizeTcpChecksum(packet);
txring_->SendPackets(&packet, 1);
}

void SendSyn() {
SendControlPacketWithMSS(snd_nxt_, 0, Tcp::kSyn,
// Always send the SYN from the fixed ISN and set snd_nxt_ absolutely, so a
// retransmitted SYN (from PeriodicCheck) reuses the same sequence number
// instead of consuming a fresh one. Incrementing on every call drifted
// snd_nxt_ past the peer's ack after the first retransmit, permanently
// breaking the handshake. Mirrors SendSynAck, which is already idempotent.
SendControlPacketWithMSS(snd_isn_, 0, Tcp::kSyn,
static_cast<uint16_t>(kDefaultMSS));
snd_nxt_++; // SYN consumes one sequence number.
snd_nxt_ = snd_isn_ + 1; // SYN consumes one sequence number.
}

void SendSynAck() {
Expand Down Expand Up @@ -632,6 +654,7 @@ class TcpFlow {
PrepareL3Header(packet);
PrepareL4Header(packet, seq, rcv_nxt_, Tcp::kAck | Tcp::kPsh);
packet->offload_tcpv4_csum();
FinalizeTcpChecksum(packet);
txring_->SendPackets(&packet, 1);
return true;
}
Expand All @@ -652,6 +675,7 @@ class TcpFlow {
PrepareL3Header(packet);
PrepareL4Header(packet, snd_nxt_, rcv_nxt_, Tcp::kFin | Tcp::kAck);
packet->offload_tcpv4_csum();
FinalizeTcpChecksum(packet);
txring_->SendPackets(&packet, 1);
return true;
}
Expand Down