2024-09-17 10:53:09 +00:00

611 lines
21 KiB
C++
Executable File

#include "packet.h"
#include "debug.h"
#include "byteorder.h"
#include "c_common/network_defs.h"
#include "config.h"
#include <cstddef>
#include <limits>
#include <vector>
#include <stdlib.h>
#include <string.h>
#include <iostream>
using namespace std;
USE_DEBUG_FLAG(D_PACKET);
ostream &
operator<<(ostream &os, const PktErr &err)
{
switch (err) {
case PktErr::UNINITIALIZED: {
return os << "Uninitialized packet";
}
case PktErr::NON_ETHERNET_FRAME: {
return os << "Layer 2 frame length does not match the Ethernet frame length";
}
case PktErr::MAC_LEN_TOO_BIG: {
return os << "Layer 2 frame length is greater than the packet length";
}
case PktErr::NON_IP_PACKET: {
return os << "Ethernet frame contains a non-IP packet";
}
case PktErr::UNKNOWN_L3_PROTOCOL: {
return os << "Unknown Layer 3 protocol type";
}
case PktErr::IP_SIZE_MISMATCH: {
return os << "Wrong IP header size";
}
case PktErr::IP_VERSION_MISMATCH: {
return os << "IP header version differs from the IP version defined by the Ethernet frame";
}
case PktErr::IP_HEADER_TOO_SMALL: {
return os << "Reported IP header length is shorter than the allowed minimum";
}
case PktErr::PKT_TOO_SHORT_FOR_IP_HEADER: {
return os << "Packet is too short for the IP header";
}
case PktErr::PKT_TOO_SHORT_FOR_IP_EXTENSION_HEADER: {
return os << "Packet is too short for the IP extension header";
}
case PktErr::PKT_TOO_SHORT_FOR_IP_EXTENSION_HEADER_BODY: {
return os << "Packet is too short for the IP extension body";
}
case PktErr::UNKNOWN_IPV6_EXTENSION_HEADER: {
return os << "Unknown IPv6 extension";
}
case PktErr::PKT_TOO_SHORT_FOR_L4_HEADER: {
return os << "IP content is too short to hold a Layer 4 header";
}
case PktErr::PKT_TOO_SHORT_FOR_TCP_OPTIONS: {
return os << "IP content is too short to hold all the TCP Options";
}
case PktErr::TCP_HEADER_TOO_SMALL: {
return os << "Reported TCP header length is shorter than the allowed minimum";
}
case PktErr::PKT_TOO_SHORT_FOR_ICMP_ERROR_DATA: {
return os << "ICMP data is too short to hold all ICMP error information";
}
case PktErr::ICMP_VERSION_MISMATCH: {
return os << "ICMP version does not match the IP version";
}
};
return os << "Unknown error: " << static_cast<uint>(err);
}
// This is common for (almost) all extension headers.
// All headers that ipv6_is_proto_extension returns true for them must have this layout
struct IPv6ExtBasic
{
u_char next_type;
};
// This is common for some extension headers
struct IPv6ExtGeneric
{
u_char next_type;
u_char ext_hdr_len; // Not in bytes! Sometimes *4, sometimes *8...
};
static const uint basic_ext_len = 8;
static const uint format_multiplier_four = 4;
static const uint format_multiplier_eight = 8;
static const char ipv4_chr = 0x40;
static const char ipv6_chr = 0x60;
static const char ipversion_mask = 0x60;
static bool
isIPv6ProtoExtension(u_char proto)
{
// ESP and None are not considered as ext headers, as their first 4 bytes are not of type IPv6ExtBasic
switch (proto) {
case IPPROTO_HOPOPTS: // 0 IPv6 hop-by-hop options - RFC2460
case IPPROTO_ROUTING: // 43 IPv6 routing header - RFC2460
case IPPROTO_FRAGMENT: // 44 IPv6 fragmentation header - RFC2460
// case IPPROTO_ESP: // 50 IPv6 encapsulation security payload header - RFC4303
case IPPROTO_AH: // 51 IPv6 authentication header - RFC4302
// case IPPROTO_NONE: // 59 IPv6 no next header - RFC2460
case IPPROTO_DSTOPTS: // 60 IPv6 destination options - RFC2460
case IPPROTO_MH: { // 135 IPv6 mobility header - RFC3775
return true;
}
}
return false;
}
Maybe<ConnKey, PktErr>
Packet::parseFromL4(const IPAddr &src, const IPAddr &dst, IPProto proto)
{
// Here so we got the l3 headers on both IPv4 and IPv6.
if (is_fragment) return ConnKey(src, 0, dst, 0, proto);
// Add ports
PortNumber sport, dport;
switch (proto) {
case IPPROTO_TCP: {
auto maybe_tcp = l3_payload.getTypePtr<struct TcpHdr>(0);
if (!maybe_tcp.ok()) {
dbgTrace(D_PACKET)
<< "TCP packet is too short ("
<< l3_payload.size()
<< ") to contain a basic TCP header";
return genError(PktErr::PKT_TOO_SHORT_FOR_L4_HEADER);
}
auto tcp = maybe_tcp.unpack();
auto l4_hdr_len = tcp->doff * sizeof(int32_t);
if (l4_hdr_len < sizeof(struct TcpHdr)) {
dbgTrace(D_PACKET) <<
"TCP header length is smaller than the minimum: " << l4_hdr_len << " < " << sizeof(struct tcphdr);
return genError(PktErr::TCP_HEADER_TOO_SMALL);
}
if (l4_hdr_len > l3_payload.size()) {
dbgTrace(D_PACKET) <<
"TCP packet is too short (" << l3_payload.size() << ") for a TCP header (" << l4_hdr_len << ")";
return genError(PktErr::PKT_TOO_SHORT_FOR_TCP_OPTIONS);
}
l4_header = l3_payload.getSubBuffer(0, l4_hdr_len);
l4_payload = l3_payload.getSubBuffer(l4_hdr_len, l3_payload.size());
sport = ntohs(tcp->source);
dport = ntohs(tcp->dest);
break;
}
case IPPROTO_UDP: {
auto maybe_udp = l3_payload.getTypePtr<struct UdpHdr>(0);
if (!maybe_udp.ok()) {
dbgTrace(D_PACKET)
<< "UDP packet is too short ("
<< l3_payload.size()
<< ") to contain a basic UDP header";
return genError(PktErr::PKT_TOO_SHORT_FOR_L4_HEADER);
}
auto udp = maybe_udp.unpack();
auto l4_hdr_len = sizeof(struct UdpHdr);
l4_header = l3_payload.getSubBuffer(0, l4_hdr_len);
l4_payload = l3_payload.getSubBuffer(l4_hdr_len, l3_payload.size());
sport = ntohs(udp->source);
dport = ntohs(udp->dest);
break;
}
case IPPROTO_ICMP:
case IPPROTO_ICMPV6: {
auto icmp_hdr_len = getIcmpHdrLen(proto, src.getType());
if (!icmp_hdr_len.ok()) return icmp_hdr_len.passErr();
auto l4_hdr_len = icmp_hdr_len.unpack();
if (l4_hdr_len > l3_payload.size()) {
dbgTrace(D_PACKET)
<< "ICMPv6 packet is too short ("
<< l3_payload.size()
<< ") to contain an ICMP header ("
<< l4_hdr_len
<< ")";
return genError(PktErr::PKT_TOO_SHORT_FOR_L4_HEADER);
}
l4_header = l3_payload.getSubBuffer(0, l4_hdr_len);
l4_payload = l3_payload.getSubBuffer(l4_hdr_len, l3_payload.size());
tie(sport, dport) = getICMPPorts(proto);
break;
}
case IPPROTO_GRE: {
auto maybe_gre = l3_payload.getTypePtr<struct GreHdr>(0);
if (!maybe_gre.ok()) {
dbgTrace(D_PACKET)
<< "GRE packet is too short ("
<< l3_payload.size()
<< ") to contain a basic GRE header";
return genError(PktErr::PKT_TOO_SHORT_FOR_L4_HEADER);
}
auto l4_hdr_len = sizeof(struct GreHdr);
l4_header = l3_payload.getSubBuffer(0, l4_hdr_len);
l4_payload = l3_payload.getSubBuffer(l4_hdr_len, l3_payload.size());
sport = 0;
dport = 0;
break;
}
case IPPROTO_SCTP: {
auto maybe_sctp = l3_payload.getTypePtr<struct SctpHdr>(0);
if (!maybe_sctp.ok()) {
dbgTrace(D_PACKET)
<< "SCTP packet is too short ("
<< l3_payload.size()
<< ") to contain a basic SCTP header";
return genError(PktErr::PKT_TOO_SHORT_FOR_L4_HEADER);
}
auto sctp = maybe_sctp.unpack();
auto l4_hdr_len = sizeof(struct SctpHdr);
l4_header = l3_payload.getSubBuffer(0, l4_hdr_len);
l4_payload = l3_payload.getSubBuffer(l4_hdr_len, l3_payload.size());
sport = ntohs(sctp->sport);
dport = ntohs(sctp->dport);
break;
}
case IPPROTO_DCCP: {
auto maybe_dccp = l3_payload.getTypePtr<struct DccpHdr>(0);
if (!maybe_dccp.ok()) {
dbgTrace(D_PACKET)
<< "DCCP packet is too short ("
<< l3_payload.size()
<< ") to contain a basic DCCP header";
return genError(PktErr::PKT_TOO_SHORT_FOR_L4_HEADER);
}
auto dccp = maybe_dccp.unpack();
auto l4_hdr_len = sizeof(struct DccpHdr);
l4_header = l3_payload.getSubBuffer(0, l4_hdr_len);
l4_payload = l3_payload.getSubBuffer(l4_hdr_len, l3_payload.size());
sport = ntohs(dccp->dccph_sport);
dport = ntohs(dccp->dccph_dport);
break;
}
// other protocols
default: {
l4_payload = l3_payload;
sport = 0;
dport = 0;
break;
}
}
return ConnKey(src, sport, dst, dport, proto);
}
tuple<PortNumber, PortNumber>
Packet::getICMPPortsV6()
{
auto icmp_hdr = l4_header.getTypePtr<struct icmp6_hdr>(0).unpack();
PortNumber sport = 0;
PortNumber dport = 0;
switch(icmp_hdr->icmp6_type) {
case ICMP6_ECHO_REQUEST:
sport = ntohs(icmp_hdr->icmp6_id);
if (!getConfigurationWithDefault<bool>(false, "Allow simultaneous ping")) {
dport = ntohs(icmp_hdr->icmp6_seq);
}
break;
case ICMP6_ECHO_REPLY:
if (!getConfigurationWithDefault<bool>(false, "Allow simultaneous ping")) {
sport = ntohs(icmp_hdr->icmp6_seq);
}
dport = ntohs(icmp_hdr->icmp6_id);
break;
case ICMP6_DST_UNREACH:
case ICMP6_PACKET_TOO_BIG:
case ICMP6_TIME_EXCEEDED:
case ICMP6_PARAM_PROB:
case ND_REDIRECT:
sport = icmp_hdr->icmp6_code;
dport = icmp_hdr->icmp6_type;
break;
}
return make_tuple(sport, dport);
}
tuple<PortNumber, PortNumber>
Packet::getICMPPortsV4()
{
auto icmp_hdr = l4_header.getTypePtr<struct icmphdr>(0).unpack();
PortNumber sport = 0;
PortNumber dport = 0;
switch(icmp_hdr->type) {
case ICMP_ECHO:
case ICMP_TSTAMP:
case ICMP_IREQ:
case ICMP_MASKREQ:
sport = ntohs(icmp_hdr->un.echo.id);
if (!getConfigurationWithDefault<bool>(false, "Allow simultaneous ping")) {
dport = ntohs(icmp_hdr->un.echo.sequence);
}
break;
case ICMP_ECHOREPLY:
case ICMP_TSTAMPREPLY:
case ICMP_IREQREPLY:
case ICMP_MASKREPLY:
if (!getConfigurationWithDefault<bool>(false, "Allow simultaneous ping")) {
sport = ntohs(icmp_hdr->un.echo.sequence);
}
dport = ntohs(icmp_hdr->un.echo.id);
break;
case ICMP_UNREACH:
case ICMP_SOURCEQUENCH:
case ICMP_TIMXCEED:
case ICMP_PARAMPROB:
case ICMP_REDIRECT:
sport = icmp_hdr->code;
dport = icmp_hdr->type;
break;
}
return make_tuple(sport, dport);
}
tuple<PortNumber, PortNumber>
Packet::getICMPPorts(IPProto proto)
{
return proto == IPPROTO_ICMP ? getICMPPortsV4() : getICMPPortsV6();
}
Maybe<uint, PktErr>
Packet::getIcmpHdrLen(IPProto proto, IPType ip_type)
{
if (proto == IPPROTO_ICMP && ip_type == IPType::V4) {
return sizeof(struct icmphdr);
} else if (proto == IPPROTO_ICMPV6 && ip_type == IPType::V6) {
return sizeof(struct icmp6_hdr);
}
return genError(PktErr::ICMP_VERSION_MISMATCH);
}
Maybe<int, PktErr>
Packet::getIPv6GenericExtLen(uint offset_to_ext_hdr, uint length_multiplier)
{
auto maybe_header = l3.getTypePtr<IPv6ExtGeneric>(offset_to_ext_hdr);
if (!maybe_header.ok()) {
dbgTrace(D_PACKET) <<
"Not enough room for an IPv6 Extension header basic data (" << offset_to_ext_hdr << " + " <<
sizeof(IPv6ExtGeneric) << " > " << l3.size() << ")";
return genError(PktErr::PKT_TOO_SHORT_FOR_IP_EXTENSION_HEADER);
}
auto header = maybe_header.unpack();
return basic_ext_len + (header->ext_hdr_len * length_multiplier);
}
Maybe<int, PktErr>
Packet::getIPv6ExtLen(uint offset_to_ext_hdr, IPProto ext_hdr_type)
{
switch (ext_hdr_type) {
case IPPROTO_FRAGMENT: {
// The length of Fragmentation and ESP headers is always 8 bytes. They don't have a length field.
return basic_ext_len;
}
case IPPROTO_AH: {
// In AH header the length field specifies the header's length in units of 4 bytes
return getIPv6GenericExtLen(offset_to_ext_hdr, format_multiplier_four);
}
case IPPROTO_HOPOPTS:
case IPPROTO_ROUTING:
case IPPROTO_DSTOPTS:
case IPPROTO_MH: {
// For these headers, the length field specifies the header's length in units of 8 bytes
return getIPv6GenericExtLen(offset_to_ext_hdr, format_multiplier_eight);
}
}
dbgWarning(D_PACKET) << "Unknown IPv6 Extension header type" << static_cast<uint>(ext_hdr_type);
return genError(PktErr::UNKNOWN_IPV6_EXTENSION_HEADER);
}
Maybe<IPProto, PktErr>
Packet::getIPv6Proto(IPProto proto)
{
uint offset_to_ext_hdr = sizeof(struct ip6_hdr);
while (isIPv6ProtoExtension(proto)) {
auto res = getIPv6ExtLen(offset_to_ext_hdr, proto);
if (!res.ok()) return res.passErr();
auto ext_len = *res;
if (offset_to_ext_hdr + ext_len > l3.size()) {
dbgTrace(D_PACKET) <<
"IPv6 Extension header " << static_cast<uint>(proto) << " body is too long" <<
" - Body length=" << ext_len << ", offset=" << offset_to_ext_hdr << ", L3 data length=" << l3.size();
return genError(PktErr::PKT_TOO_SHORT_FOR_IP_EXTENSION_HEADER_BODY);
}
if (proto == IPPROTO_FRAGMENT) {
dbgTrace(D_PACKET) << "Fragmented IPv6 packet";
is_fragment = true;
}
auto header = l3.getTypePtr<IPv6ExtBasic>(offset_to_ext_hdr).unpack();
proto = header->next_type;
offset_to_ext_hdr += ext_len;
}
l3_header = l3.getSubBuffer(0, offset_to_ext_hdr);
l3_payload = l3.getSubBuffer(offset_to_ext_hdr, l3.size());
return proto;
}
Maybe<ConnKey, PktErr>
Packet::parseFromL3v6()
{
auto maybe_ip6 = l2_payload.getTypePtr<struct ip6_hdr>(0);
if (!maybe_ip6.ok()) {
dbgTrace(D_PACKET)
<< "IPv6 packet is too short for an IPv6 header: "
<< l2_payload.size()
<< " < "
<< sizeof(struct ip);
return genError(PktErr::PKT_TOO_SHORT_FOR_IP_HEADER);
}
auto ip6 = maybe_ip6.unpack();
uint ip_version = (ip6->ip6_vfc) >> 4;
if (ip_version != 6) {
dbgTrace(D_PACKET) << "Bad IPv6 version " << ip_version;
return genError(PktErr::IP_VERSION_MISMATCH);
}
auto l3_len_reported_by_header = sizeof(struct ip6_hdr) + ntohs(ip6->ip6_plen);
if (l3_len_reported_by_header > l2_payload.size()) {
dbgTrace(D_PACKET) <<
"IP header reports a total of " << l3_len_reported_by_header <<
" bytes, but the packet length is only " << l2_payload.size() << " bytes";
return genError(PktErr::IP_SIZE_MISMATCH);
}
l3 = l2_payload.getSubBuffer(0, l3_len_reported_by_header); // Remove padding
auto proto = getIPv6Proto(ip6->ip6_nxt);
if (!proto.ok()) return genError(proto.getErr());
return parseFromL4(IPAddr(ip6->ip6_src), IPAddr(ip6->ip6_dst), proto.unpack());
}
Maybe<ConnKey, PktErr>
Packet::parseFromL3v4()
{
auto maybe_ip4 = l2_payload.getTypePtr<struct ip>(0);
if (!maybe_ip4.ok()) {
dbgTrace(D_PACKET)
<< "IPv4 packet is too short for an IPv4 header: "
<< l2_payload.size()
<< "<"
<< sizeof(struct ip);
return genError(PktErr::PKT_TOO_SHORT_FOR_IP_HEADER);
}
auto ip4 = maybe_ip4.unpack();
uint ip_version = ip4->ip_v;
if (ip_version != 4) {
dbgTrace(D_PACKET) << "Bad IPv4 version " << ip_version << " length: " << ntohs(ip4->ip_len);
return genError(PktErr::IP_VERSION_MISMATCH);
}
auto l3_len_reported_by_header = ntohs(ip4->ip_len);
if (l3_len_reported_by_header < sizeof(struct ip)) {
dbgTrace(D_PACKET) <<
"IPv4 payload length is smaller than the IPv4 header: " <<
l3_len_reported_by_header << " < " << sizeof(struct ip);
return genError(PktErr::IP_SIZE_MISMATCH);
}
if (l3_len_reported_by_header > l2_payload.size()) {
dbgTrace(D_PACKET) <<
"IP header reports a total of " << l3_len_reported_by_header <<
" bytes, but the packet length is only " << l2_payload.size() << " bytes";
return genError(PktErr::IP_SIZE_MISMATCH);
}
auto l3_hdr_len = ip4->ip_hl * sizeof(int);
if (l3_hdr_len < sizeof(struct ip)) {
dbgTrace(D_PACKET)
<< "The reported IPv4 header length is smaller than the allowed minimum: "
<< l3_hdr_len
<< " < "
<< sizeof(struct ip);
return genError(PktErr::IP_HEADER_TOO_SMALL);
}
if (l3_hdr_len > l2_payload.size()) {
dbgTrace(D_PACKET)
<< "IPv4 header is too big for the IPv4 packet: "
<< l3_hdr_len
<< " > "
<< l2_payload.size();
return genError(PktErr::PKT_TOO_SHORT_FOR_IP_HEADER);
}
auto frag_offset = ntohs(ip4->ip_off);
if ((frag_offset & IP_OFFMASK) || (frag_offset & IP_MF)) {
dbgTrace(D_PACKET) << "Fragmented IPv4 packet";
is_fragment = true;
}
l3 = l2_payload.getSubBuffer(0, l3_len_reported_by_header); // Remove padding
l3_header = l3.getSubBuffer(0, l3_hdr_len);
l3_payload = l3.getSubBuffer(l3_hdr_len, l3.size());
return parseFromL4(IPAddr(ip4->ip_src), IPAddr(ip4->ip_dst), ip4->ip_p);
}
Maybe<ConnKey, PktErr>
Packet::parseFromL2()
{
// In case of VLAN we want to remove the additional information and pass the packet as normal.
uint _maclen = sizeof(struct ether_header) - 4; // -4 for the first do loop.
uint16_t ether_type;
do
{
_maclen += 4; // 4 is the size of vlan tag.
auto maybe_ether_type = pkt_data.getTypePtr<uint16_t>(_maclen - 2); // Last 2 Bytes contain the ether type.
if (!maybe_ether_type.ok()) {
dbgTrace(D_PACKET)
<< "VLAN tag length is greater than the packet length: "
<< _maclen
<< " > "
<< pkt_data.size();
return genError(PktErr::MAC_LEN_TOO_BIG);
}
ether_type = *(maybe_ether_type.unpack());
} while (ether_type == constHTONS(ETHERTYPE_VLAN));
l2_header = pkt_data.getSubBuffer(0, _maclen);
l2_payload = pkt_data.getSubBuffer(_maclen, pkt_data.size());
switch (ether_type) {
case constHTONS(ETHERTYPE_IP): {
return parseFromL3v4();
}
case constHTONS(ETHERTYPE_IPV6): {
return parseFromL3v6();
}
default: {
dbgTrace(D_PACKET) << "Unsupported Ethernet type: " << ether_type;
return genError(PktErr::NON_IP_PACKET);
}
}
}
Maybe<ConnKey, PktErr>
Packet::parsePacket(PktType type, IPType proto)
{
if (type == PktType::PKT_L2) return parseFromL2();
l2_payload = pkt_data;
switch (proto) {
case IPType::V4: {
return parseFromL3v4();
}
case IPType::V6: {
return parseFromL3v6();
}
default: {
dbgAssert(false)
<< AlertInfo(AlertTeam::CORE, "packet")
<< "Unknown (neither IPv4, nor IPv6), or uninitialized packet type: "
<< proto;
}
}
return genError(PktErr::UNKNOWN_L3_PROTOCOL);
}
std::vector<u_char>
Packet::getL2DataVec() const
{
auto p = pkt_data.data();
std::vector<u_char> buf(p, p+pkt_data.size());
return buf;
}
void
Packet::setInterface(NetworkIfNum value)
{
interface = value;
is_interface = true;
}
void
Packet::setZecoOpaque(u_int64_t value)
{
zeco_opaque = value;
has_zeco_opaque = true;
}
Maybe<NetworkIfNum>
Packet::getInterface() const
{
if (!is_interface) return genError("Could not set an interface to send the packet");
return interface;
}
Maybe<u_int64_t>
Packet::getZecoOpaque() const
{
if (!has_zeco_opaque) return genError("Could not get the zeco opaque");
return zeco_opaque;
}