diff --git a/src/core/drivers/dpdk/pmd.cc b/src/core/drivers/dpdk/pmd.cc index 216f646a..0960fc5f 100644 --- a/src/core/drivers/dpdk/pmd.cc +++ b/src/core/drivers/dpdk/pmd.cc @@ -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) diff --git a/src/core/tcp_flow_test.cc b/src/core/tcp_flow_test.cc index aa031c22..a2baaada 100644 --- a/src/core/tcp_flow_test.cc +++ b/src/core/tcp_flow_test.cc @@ -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 // ═══════════════════════════════════════════════════════════════ diff --git a/src/include/tcp_flow.h b/src/include/tcp_flow.h index d3914f62..da4a8f25 100644 --- a/src/include/tcp_flow.h +++ b/src/include/tcp_flow.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -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(sizeof(Ethernet)); + auto* tcph = packet->head_data(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) { @@ -532,6 +547,7 @@ class TcpFlow { PrepareL3Header(packet); PrepareL4Header(packet, seq, ack, flags); packet->offload_tcpv4_csum(); + FinalizeTcpChecksum(packet); txring_->SendPackets(&packet, 1); } @@ -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(kDefaultMSS)); - snd_nxt_++; // SYN consumes one sequence number. + snd_nxt_ = snd_isn_ + 1; // SYN consumes one sequence number. } void SendSynAck() { @@ -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; } @@ -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; }