blob: e73e284a0105c36947509f92ad04f47cbd11e3b7 [file] [log] [blame]
// Copyright (c) 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "quiche/quic/qbone/platform/tcp_packet.h"
#include <netinet/ip6.h>
#include "absl/base/optimization.h"
#include "absl/strings/string_view.h"
#include "quiche/quic/platform/api/quic_logging.h"
#include "quiche/quic/qbone/platform/internet_checksum.h"
#include "quiche/common/quiche_endian.h"
namespace quic {
namespace {
constexpr size_t kIPv6AddressSize = sizeof(in6_addr);
constexpr size_t kTcpTtl = 64;
struct TCPv6Packet {
ip6_hdr ip_header;
tcphdr tcp_header;
};
struct TCPv6PseudoHeader {
uint32_t payload_size{};
uint8_t zeros[3] = {0, 0, 0};
uint8_t next_header = IPPROTO_TCP;
};
} // namespace
void CreateTcpResetPacket(absl::string_view original_packet,
const std::function<void(absl::string_view)>& cb) {
// By the time this method is called, original_packet should be fairly
// strongly validated. However, it's better to be more paranoid than not, so
// here are a bunch of very obvious checks.
if (ABSL_PREDICT_FALSE(original_packet.size() < sizeof(ip6_hdr))) {
return;
}
auto* ip6_header = reinterpret_cast<const ip6_hdr*>(original_packet.data());
if (ABSL_PREDICT_FALSE(ip6_header->ip6_vfc >> 4 != 6)) {
return;
}
if (ABSL_PREDICT_FALSE(ip6_header->ip6_nxt != IPPROTO_TCP)) {
return;
}
if (ABSL_PREDICT_FALSE(quiche::QuicheEndian::NetToHost16(
ip6_header->ip6_plen) < sizeof(tcphdr))) {
return;
}
auto* tcp_header = reinterpret_cast<const tcphdr*>(ip6_header + 1);
// Now that the original packet has been confirmed to be well-formed, it's
// time to make the TCP RST packet.
TCPv6Packet tcp_packet{};
const size_t payload_size = sizeof(tcphdr);
// Set version to 6.
tcp_packet.ip_header.ip6_vfc = 0x6 << 4;
// Set the payload size, protocol and TTL.
tcp_packet.ip_header.ip6_plen =
quiche::QuicheEndian::HostToNet16(payload_size);
tcp_packet.ip_header.ip6_nxt = IPPROTO_TCP;
tcp_packet.ip_header.ip6_hops = kTcpTtl;
// Since the TCP RST is impersonating the endpoint, flip the source and
// destination addresses from the original packet.
tcp_packet.ip_header.ip6_src = ip6_header->ip6_dst;
tcp_packet.ip_header.ip6_dst = ip6_header->ip6_src;
// The same is true about the TCP ports
tcp_packet.tcp_header.dest = tcp_header->source;
tcp_packet.tcp_header.source = tcp_header->dest;
// There are no extensions in this header, so size is trivial
tcp_packet.tcp_header.doff = sizeof(tcphdr) >> 2;
// Checksum is 0 before it is computed
tcp_packet.tcp_header.check = 0;
// Per RFC 793, TCP RST comes in one of 3 flavors:
//
// * connection CLOSED
// * connection in non-synchronized state (LISTEN, SYN-SENT, SYN-RECEIVED)
// * connection in synchronized state (ESTABLISHED, FIN-WAIT-1, etc.)
//
// QBONE is acting like a firewall, so the RFC text of interest is the CLOSED
// state. Note, however, that it is possible for a connection to actually be
// in the FIN-WAIT-1 state on the remote end, but the processing logic does
// not change.
tcp_packet.tcp_header.rst = 1;
// If the incoming segment has an ACK field, the reset takes its sequence
// number from the ACK field of the segment,
if (tcp_header->ack) {
tcp_packet.tcp_header.seq = tcp_header->ack_seq;
} else {
// Otherwise the reset has sequence number zero and the ACK field is set to
// the sum of the sequence number and segment length of the incoming segment
tcp_packet.tcp_header.ack = 1;
tcp_packet.tcp_header.seq = 0;
tcp_packet.tcp_header.ack_seq = quiche::QuicheEndian::HostToNet32(
quiche::QuicheEndian::NetToHost32(tcp_header->seq) + 1);
}
TCPv6PseudoHeader pseudo_header{};
pseudo_header.payload_size = quiche::QuicheEndian::HostToNet32(payload_size);
InternetChecksum checksum;
// Pseudoheader.
checksum.Update(tcp_packet.ip_header.ip6_src.s6_addr, kIPv6AddressSize);
checksum.Update(tcp_packet.ip_header.ip6_dst.s6_addr, kIPv6AddressSize);
checksum.Update(reinterpret_cast<char*>(&pseudo_header),
sizeof(pseudo_header));
// TCP header.
checksum.Update(reinterpret_cast<const char*>(&tcp_packet.tcp_header),
sizeof(tcp_packet.tcp_header));
// There is no body.
tcp_packet.tcp_header.check = checksum.Value();
const char* packet = reinterpret_cast<char*>(&tcp_packet);
cb(absl::string_view(packet, sizeof(tcp_packet)));
}
} // namespace quic