diff --git a/src/iosource/Layer2.cc b/src/iosource/Layer2.cc new file mode 100644 index 0000000000..b7e8c508f3 --- /dev/null +++ b/src/iosource/Layer2.cc @@ -0,0 +1,117 @@ +// See the file "COPYING" in the main distribution directory for copyright. + +#include "IP.h" +#include "Type.h" +#include "Val.h" +#include "Var.h" +#include "NetVar.h" +#include "iosource/Layer2.h" +#include "iosource/Packet.h" + +extern "C" { +#include +#include +#include +#include +#include +#include +#ifdef HAVE_NET_ETHERNET_H +#include +#elif defined(HAVE_SYS_ETHERNET_H) +#include +#elif defined(HAVE_NETINET_IF_ETHER_H) +#include +#elif defined(HAVE_NET_ETHERTYPES_H) +#include +#endif +} + + +Val *L2_Hdr::fmt_eui48(const u_char *mac) const + { + char buf[20]; + snprintf(buf, sizeof buf, "%02x:%02x:%02x:%02x:%02x:%02x", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return new StringVal(buf); + } + +RecordVal* L2_Hdr::BuildPktHdrVal() const + { + static RecordType* l2_hdr_type = 0; + static RecordType* raw_pkt_hdr_type = 0; + + if ( ! raw_pkt_hdr_type ) + { + raw_pkt_hdr_type = internal_type("raw_pkt_hdr")->AsRecordType(); + l2_hdr_type = internal_type("l2_hdr")->AsRecordType(); + } + + RecordVal* pkt_hdr = new RecordVal(raw_pkt_hdr_type); + RecordVal* l2_hdr = new RecordVal(l2_hdr_type); + + int is_ethernet = (pkt->link_type == DLT_EN10MB) ? 1 : 0; + + int l3 = BifEnum::L3_UNKNOWN; + + if ( pkt->l3_proto == L3_IPV4 ) + l3 = BifEnum::L3_IPV4; + + else if ( pkt->l3_proto == L3_IPV6 ) + l3 = BifEnum::L3_IPV6; + + else if ( pkt->l3_proto == L3_ARP ) + l3 = BifEnum::L3_ARP; + + // l2_hdr layout: + // encap: link_encap; ##< L2 link encapsulation + // len: count; ##< Total frame length on wire + // cap_len: count; ##< Captured length + // src: string &optional; ##< L2 source (if ethernet) + // dst: string &optional; ##< L2 destination (if ethernet) + // vlan: count &optional; ##< VLAN tag if any (and ethernet) + // ethertype: count &optional; ##< If ethernet + // proto: layer3_proto; ##< L3 proto + + if ( is_ethernet ) + { + // Ethernet header layout is: + // dst[6bytes] src[6bytes] ethertype[2bytes]... + l2_hdr->Assign(0, new EnumVal(BifEnum::LINK_ETHERNET, BifType::Enum::link_encap)); + l2_hdr->Assign(3, fmt_eui48(pkt->data + 6)); // src + l2_hdr->Assign(4, fmt_eui48(pkt->data)); // dst + + if ( pkt->vlan ) + l2_hdr->Assign(5, new Val(pkt->vlan, TYPE_COUNT)); + + l2_hdr->Assign(6, new Val(pkt->eth_type, TYPE_COUNT)); + + if ( pkt->eth_type == ETHERTYPE_ARP || pkt->eth_type == ETHERTYPE_REVARP ) + // We also identify ARP for L3 over ethernet + l3 = BifEnum::L3_ARP; + } + else + l2_hdr->Assign(0, new EnumVal(BifEnum::LINK_UNKNOWN, BifType::Enum::link_encap)); + + l2_hdr->Assign(1, new Val(pkt->len, TYPE_COUNT)); + l2_hdr->Assign(2, new Val(pkt->cap_len, TYPE_COUNT)); + + l2_hdr->Assign(7, new EnumVal(l3, BifType::Enum::layer3_proto)); + + pkt_hdr->Assign(0, l2_hdr); + + if ( pkt->l3_proto == L3_IPV4 ) + { + IP_Hdr ip_hdr((const struct ip*)(pkt->data + pkt->hdr_size), false); + return ip_hdr.BuildPktHdrVal(pkt_hdr, 1); + } + + else if ( pkt->l3_proto == L3_IPV6 ) + { + IP_Hdr ip6_hdr((const struct ip6_hdr*)(pkt->data + pkt->hdr_size), false, pkt->cap_len); + return ip6_hdr.BuildPktHdrVal(pkt_hdr, 1); + } + + else + return pkt_hdr; + } + diff --git a/src/iosource/Layer2.h b/src/iosource/Layer2.h new file mode 100644 index 0000000000..7152836ffc --- /dev/null +++ b/src/iosource/Layer2.h @@ -0,0 +1,33 @@ +#ifndef l2_h +#define l2_h + +#include "config.h" +#include "net_util.h" +#include "IP.h" +#include "Reporter.h" +#include "Val.h" +#include "Type.h" +#include + +class Packet; + +/** + * A class that wraps an L2 packet. + */ +class L2_Hdr { +public: + L2_Hdr(const Packet *arg_pkt) : pkt(arg_pkt) { } + ~L2_Hdr() { } + + /** + * Returns a raw_pkt_hdr RecordVal, which includes L2 and also + * everything in IP_Hdr (i.e. IP4/6 + tcp/udp/icmp) + */ + RecordVal* BuildPktHdrVal() const; + +private: + Val *fmt_eui48(const u_char *mac) const; + const Packet *pkt; +}; + +#endif diff --git a/src/iosource/Packet.cc b/src/iosource/Packet.cc new file mode 100644 index 0000000000..41e5e31a8e --- /dev/null +++ b/src/iosource/Packet.cc @@ -0,0 +1,270 @@ + +#include "Packet.h" +#include "Sessions.h" + +void Packet::Weird(const char* name) + { + sessions->Weird(name, this); + l2_valid = false; + } + +int Packet::GetLinkHeaderSize(int link_type) + { + switch ( link_type ) { + case DLT_NULL: + return 4; + + case DLT_EN10MB: + return 14; + + case DLT_FDDI: + return 13 + 8; // fddi_header + LLC + +#ifdef DLT_LINUX_SLL + case DLT_LINUX_SLL: + return 16; +#endif + + case DLT_PPP_SERIAL: // PPP_SERIAL + return 4; + + case DLT_RAW: + return 0; + } + + return -1; + } + +void Packet::ProcessLayer2() + { + l2_valid = true; + + // Unfortunately some packets on the link might have MPLS labels + // while others don't. That means we need to ask the link-layer if + // labels are in place. + bool have_mpls = false; + + const u_char* pdata = data; + + switch ( link_type ) { + case DLT_NULL: + { + int protocol = (pdata[3] << 24) + (pdata[2] << 16) + (pdata[1] << 8) + pdata[0]; + pdata += GetLinkHeaderSize(link_type); + + // From the Wireshark Wiki: "AF_INET6, unfortunately, has + // different values in {NetBSD,OpenBSD,BSD/OS}, + // {FreeBSD,DragonFlyBSD}, and {Darwin/Mac OS X}, so an IPv6 + // packet might have a link-layer header with 24, 28, or 30 + // as the AF_ value." As we may be reading traces captured on + // platforms other than what we're running on, we accept them + // all here. + + if ( protocol == AF_INET ) + l3_proto = L3_IPV4; + else if ( protocol == 24 || protocol == 28 || protocol == 30 ) + l3_proto = L3_IPV6; + else + { + Weird("non_ip_packet_in_null_transport"); + return; + } + + break; + } + + case DLT_EN10MB: + { + // Get protocol being carried from the ethernet frame. + int protocol = (pdata[12] << 8) + pdata[13]; + pdata += GetLinkHeaderSize(link_type); + eth_type = protocol; + + switch ( protocol ) + { + // MPLS carried over the ethernet frame. + case 0x8847: + have_mpls = true; + break; + + // VLAN carried over the ethernet frame. + // 802.1q / 802.1ad + case 0x8100: + case 0x9100: + vlan = ((pdata[0] << 8) + pdata[1]) & 0xfff; + protocol = ((pdata[2] << 8) + pdata[3]); + pdata += 4; // Skip the vlan header + + // Check for MPLS in VLAN. + if ( protocol == 0x8847 ) + { + have_mpls = true; + break; + } + + // Check for double-tagged (802.1ad) + if ( protocol == 0x8100 || protocol == 0x9100 ) + { + protocol = ((pdata[2] << 8) + pdata[3]); + pdata += 4; // Skip the vlan header + } + + eth_type = protocol; + break; + + // PPPoE carried over the ethernet frame. + case 0x8864: + protocol = (pdata[6] << 8) + pdata[7]; + pdata += 8; // Skip the PPPoE session and PPP header + + if ( protocol == 0x0021 ) + l3_proto = L3_IPV4; + else if ( protocol == 0x0057 ) + l3_proto = L3_IPV6; + else + { + // Neither IPv4 nor IPv6. + Weird("non_ip_packet_in_pppoe_encapsulation"); + return; + } + + break; + } + + // Normal path to determine Layer 3 protocol. + if ( ! have_mpls && l3_proto == L3_UNKNOWN ) + { + if ( protocol == 0x800 ) + l3_proto = L3_IPV4; + else if ( protocol == 0x86dd ) + l3_proto = L3_IPV6; + else if ( protocol == 0x0806 || protocol == 0x8035 ) + l3_proto = L3_ARP; + else + { + // Neither IPv4 nor IPv6. + Weird("non_ip_packet_in_ethernet"); + return; + } + } + + break; + } + + case DLT_PPP_SERIAL: + { + // Get PPP protocol. + int protocol = (pdata[2] << 8) + pdata[3]; + pdata += GetLinkHeaderSize(link_type); + + if ( protocol == 0x0281 ) + { + // MPLS Unicast. Remove the pdata link layer and + // denote a header size of zero before the IP header. + have_mpls = true; + } + else if ( protocol == 0x0021 ) + l3_proto = L3_IPV4; + else if ( protocol == 0x0057 ) + l3_proto = L3_IPV6; + else + { + // Neither IPv4 nor IPv6. + Weird("non_ip_packet_in_ppp_encapsulation"); + return; + } + break; + } + + default: + { + // Assume we're pointing at IP. Just figure out which version. + pdata += GetLinkHeaderSize(link_type); + const struct ip* ip = (const struct ip *)pdata; + + if ( ip->ip_v == 4 ) + l3_proto = L3_IPV4; + else if ( ip->ip_v == 6 ) + l3_proto = L3_IPV6; + else + { + // Neither IPv4 nor IPv6. + Weird("non_ip_packet"); + return; + } + + break; + } + } + + if ( have_mpls ) + { + // Skip the MPLS label stack. + bool end_of_stack = false; + + while ( ! end_of_stack ) + { + end_of_stack = *(pdata + 2) & 0x01; + pdata += 4; + + if ( pdata >= pdata + cap_len ) + { + Weird("no_mpls_payload"); + return; + } + } + + // We assume that what remains is IP + if ( pdata + sizeof(struct ip) >= data + cap_len ) + { + Weird("no_ip_in_mpls_payload"); + return; + } + + const struct ip* ip = (const struct ip *)pdata; + + if ( ip->ip_v == 4 ) + l3_proto = L3_IPV4; + else if ( ip->ip_v == 6 ) + l3_proto = L3_IPV6; + else + { + // Neither IPv4 nor IPv6. + Weird("no_ip_in_mpls_payload"); + return; + } + } + + else if ( encap_hdr_size ) + { + // Blanket encapsulation. We assume that what remains is IP. + pdata += encap_hdr_size; + if ( pdata + sizeof(struct ip) >= data + cap_len ) + { + Weird("no_ip_left_after_encap"); + return; + } + + const struct ip* ip = (const struct ip *)pdata; + + if ( ip->ip_v == 4 ) + l3_proto = L3_IPV4; + else if ( ip->ip_v == 6 ) + l3_proto = L3_IPV6; + else + { + // Neither IPv4 nor IPv6. + Weird("no_ip_in_encap"); + return; + } + + } + + // We've now determined (a) L3_IPV4 vs (b) L3_IPV6 vs + // (c) L3_ARP vs (d) L3_UNKNOWN (0 == anything else) + l3_proto = l3_proto; + + // Calculate how much header we've used up. + hdr_size = (pdata - data); +} + diff --git a/src/iosource/Packet.h b/src/iosource/Packet.h new file mode 100644 index 0000000000..f4f4c01709 --- /dev/null +++ b/src/iosource/Packet.h @@ -0,0 +1,136 @@ +#ifndef packet_h +#define packet_h + +#include "Desc.h" +#include "IP.h" +#include "NetVar.h" + +enum Layer3Proto { + L3_UNKNOWN = -1, + L3_IPV4 = 1, + L3_IPV6 = 2, + L3_ARP = 3, +}; + +// A link-layer packet. +// +// Note that for serialization we don't use much of the support provided by +// the serialization framework. Serialize/Unserialize do all the work by +// themselves. In particular, Packets aren't derived from SerialObj. They are +// completely seperate and self-contained entities, and we don't need any of +// the sophisticated features like object caching. + +class Packet { +public: + Packet() + { + struct timeval ts = {0, 0}; + Init(0, &ts, 0, 0, 0); + } + // Construct and initialize from packet data. + // + // arg_free: If true makes an internal copy of the *data*. If false, + // stores just a pointer to *data*, which must remain valid. + Packet(int arg_link_type, struct timeval *arg_ts, uint32 arg_caplen, + uint32 arg_len, const u_char *arg_data, int arg_free = false, + std::string arg_tag = std::string("")) + { + Init(arg_link_type, arg_ts, arg_caplen, arg_len, arg_data, arg_free, arg_tag); + } + + ~Packet() + { + if ( free ) + delete [] data; + } + + // Initialize with data from pointer. + // + // arg_free: If true makes an internal copy of the *data*. If false, + // stores just a pointer to *data*, which must remain valid. + void Init(int arg_link_type, struct timeval *arg_ts, uint32 arg_caplen, + uint32 arg_len, const u_char *arg_data, int arg_free = false, + std::string arg_tag = std::string(""), uint32 arg_hdrsize = 0) + { + link_type = arg_link_type; + ts = *arg_ts; + cap_len = arg_caplen; + len = arg_len; + free = arg_free; + + if ( free ) + { + data = new u_char[cap_len]; + memcpy(const_cast(data), arg_data, cap_len); + } + else + data = arg_data; + + hdr_size = arg_hdrsize; + l3_proto = L3_UNKNOWN; + tag = arg_tag; + time = ts.tv_sec + double(ts.tv_usec) / 1e6; + eth_type = 0; + vlan = 0; + + l2_valid = false; + + if ( data ) + ProcessLayer2(); + } + + const IP_Hdr IP() const + { return IP_Hdr((struct ip *) (data + hdr_size), false); } + + // Returns true if parsing the Layer 2 fields failed, including when + // no data was passed into the constructor in the first place. + bool Layer2Valid() + { + return l2_valid; + } + + void Describe(ODesc* d) const; + + /** + * Helper method to return the header size for a given link tyoe. + * + * @param link_type The link tyoe. + * + * @return The header size in bytes, or -1 if not known. + */ + static int GetLinkHeaderSize(int link_type); + + bool Serialize(SerialInfo* info) const; + static Packet* Unserialize(UnserialInfo* info); + + // These are passed in through the constructor. + std::string tag; /// Used in serialization + double time; /// Timestamp reconstituted as float + struct timeval ts; /// Capture timestamp + const u_char* data; /// Packet data. + uint32 len; /// Actual length on wire + uint32 cap_len; /// Captured packet length + uint32 link_type; /// pcap link_type (DLT_EN10MB, DLT_RAW, etc) + + // These are computed from Layer 2 data. These fields are only valid if + // Layer2Valid() returns true. + uint32 hdr_size; /// Layer 2 header size + Layer3Proto l3_proto; /// Layer 3 protocol identified (if any) + uint32 eth_type; /// If L2==ethernet, innermost ethertype field + uint32 vlan; /// (Outermost) VLan tag if any, else 0 + +private: + // Calculate layer 2 attributes. Sets + void ProcessLayer2(); + + // Wrapper to generate a packet-level weird. + void Weird(const char* name); + + // should we delete associated packet memory upon destruction. + bool free; + + // True if L2 processing succeeded. + bool l2_valid; +}; + +#endif // packet_h