0
votes

I’m writing a C program which builds an Ethernet/IPv4/TCP network packet, then writes the packet into a PCAP file for inspection. I build my code off the SO post here. The first version of my code worked perfectly, but it was one big main() function, and that is not portable into larger programs.

So I reorganized the code so I could port it into another program. I don’t want to get into the differences between Version 1 and Version 2 in this post. But needless to say, Version 2 works great, except for one annoying quirk. When Wireshark opened a Version 1 PCAP file, it saw that my Layer 2 was Ethernet II:

Frame 1: 154 bytes on wire (1232 bits), 154 bytes captured (1232 bits)
Ethernet II, Src: 64:96:c8:fa:fc:ff (64:96:c8:fa:fc:ff), Dst: Woonsang_04:05:06 (01:02:03:04:05:06)
    Destination: Woonsang_04:05:06 (01:02:03:04:05:06)
    Source: 64:96:c8:fa:fc:ff (64:96:c8:fa:fc:ff)
    Type: IPv4 (0x0800)
Internet Protocol Version 4, Src: 10.10.10.10, Dst: 20.20.20.20
Transmission Control Protocol, Src Port: 22, Dst Port: 55206, Seq: 1, Ack: 1, Len: 100
SSH Protocol

But in Version 2, the Layer 2 header became 802.3 Ethernet:

Frame 1: 154 bytes on wire (1232 bits), 134 bytes captured (1072 bits)
IEEE 802.3 Ethernet 
    Destination: Vibratio_1c:08:00 (00:09:70:1c:08:00)
    Source: 45:00:23:28:06:cf (45:00:23:28:06:cf)
    Length: 64
    Trailer: 050401040204000001020506040400070602040704060202…
Logical-Link Control
Data (61 bytes)
[Packet size limited during capture: Ethernet truncated]

I’m no expert in networking, but I’m guessing my Version 2 PCAP file is malformed somewhere. I should not have a Logical-Link Control header in there; my code thinks it is writing Ethernet II / IPv4 / TCP headers. At this point, my instinct is that either the PCAP Packet header (necessary to proceed every packet in a PCAP file) or my Ethernet header is incorrect, somehow. Which would tell Wireshark “the next X bytes are an Ethernet II header?"

Here’s my code, in excerpts:

The structs for the PCAP header and Ethernet frames were cribbed directly from the before-mentioned SO post. The solution in that post was to use the pcap_sf_pkthdr struct for the PCAP Packet header:

// struct for PCAP Packet Header - Timestamp
struct pcap_timeval {
        bpf_int32 tv_sec;       // seconds
        bpf_int32 tv_usec;      // microseconds
};
// struct for PCAP Packet Header
struct pcap_sf_pkthdr {
        struct pcap_timeval ts; // time stamp
        bpf_u_int32 caplen;     // length of portion present
        bpf_u_int32 len;        // length this packet (off wire)
};

And the Ethernet header is from the original post:

// struct for the Ethernet header
struct ethernet {
        u_char          mac1[6];
        u_char          mac2[6];
        u_short         protocol;   // will be ETHERTYPE_IP, for IPv4
};

There’s not much to either struct, right? I don’t really understand how Wireshark looks at this and knows the first 20 bytes of the packet are Ethernet.

Here’s the actual code, slightly abridged:

#include <netinet/in.h>     // for ETHERTYPE_IP

struct pcap_sf_pkthdr* allocatePCAPPacketHdr(struct pcap_sf_pkthdr* pcapPacketHdr ){
        pcapPacketHdr = malloc( sizeof(struct pcap_sf_pkthdr) );
        if( pcapPacketHdr == NULL ){
                return NULL;
        }
        uint32_t frameSize = sizeof( struct ethernet) + …correctly computed here
        bzero( pcapPacketHdr, sizeof( struct pcap_sf_pkthdr ) );
        pcapPacketHdr->ts.tv_sec = 0;           // for now
        pcapPacketHdr->ts.tv_usec = 0;          // for now
        pcapPacketHdr->caplen = frameSize;
        pcapPacketHdr->len =    frameSize;
        return pcapPacketHdr;
}
void* allocateL2Hdr( packetChecklist* pc, void* l2header ){
        l2header = malloc( sizeof( struct ethernet ) );
        if( l2header == NULL ){
                return NULL;
        }
        bzero( ((struct ethernet*)l2header)->mac1, 6 );
        bzero( ((struct ethernet*)l2header)->mac2, 6 );
         // …MAC addresses filled in later…
         ((struct ethernet*)l2header)->protocol =  ETHERTYPE_IP;   // This is correctly set
        return l2header;
}

...and the code which uses the above functions...

struct pcap_sf_pkthdr* pcapPacketHdr;
pcapPacketHdr = allocatePCAPPacketHdr( pcapPacketHdr );
struct ethernet* l2header;
l2header = allocateL2Hdr( l2header );

Later, the code populates these structs and writes them into a file, along with an IPv4 header, a TCP header, and so on.

But I think my problem is that I don’t really understand how Wireshark is supposed to know that my Ethernet header is Ethernet II and not 802.3 Ethernet with an Logical-Link Header. Is that communicated in the PCAP Packet Header? Or in the ethernet frame somewhere? I’m hoping for advice. Thank you

1

1 Answers

1
votes

Wireshark is supposed to know that my Ethernet header is Ethernet II and not 802.3 Ethernet with an Logical-Link Header. Is that communicated in the PCAP Packet Header?

No.

Or in the ethernet frame somewhere?

Yes.

If you want the details, see, for example, the "Types" section of the Wikipedia "Ethernet frame" page.

However, the problem appears to be that the packet you're writing to the file doesn't have the full 6-byte destination and source addresses in it - the last two bytes of the destination address are 0x08 0x00, which are the first two bytes of a big-endian value of ETHERTYPE_IP (0x0800), and the first byte of the source address is 0x45, which is the first byte of an IPv4 header for an IPv4 packet with no IP options.

Somehow, Version 1 of your program put the destination and source addresses into the data part of the pcap record, but Version 2 didn't.