tags:

views:

152

answers:

4

I have defined the following struct to represent an IPv4 header (up until the options field):

struct IPv4Header
{
    // First row in diagram
    u_int32 Version:4;
    u_int32 InternetHeaderLength:4;     // Header length is expressed in units of 32 bits.
    u_int32 TypeOfService:8;
    u_int32 TotalLength:16;

    // Second row in diagram
    u_int32 Identification:16;
    u_int32 Flags:3;
    u_int32 FragmentOffset:13;

    // Third row in diagram
    u_int32 TTL:8;
    u_int32 Protocol:8;
    u_int32 HeaderChecksum:16;

    // Fourth row in diagram
    u_int32 SourceAddress:32;

    // Fifth row in diagram
    u_int32 DestinationAddress:32;
};

I now also captured an IP frame with Wireshark. As an array literal it looks like this:

// Captured with Wireshark
const u_int8 cIPHeaderSample[] = {
    0x45, 0x00, 0x05, 0x17,
    0xA7, 0xE0, 0x40, 0x00,
    0x2E, 0x06, 0x1B, 0xEA,
    0x51, 0x58, 0x25, 0x02,
    0x0A, 0x04, 0x03, 0xB9
};

My question is: How can I create a IPv4Header object using the array data?

This doesn't work because of incompatible endianness:

IPv4Header header = *((IPv4Header*)cIPHeaderSample);

I'm aware of the functions like ntohs and ntohl, but it can't figure out how to use them correctly:

u_int8 version = ntohs(cIPHeaderSample[0]);
printf("version: %x \n", version);

// Output is:
// version: 0

Can anyone help?

+4  A: 

ntohl and ntohs don't operate on 1-byte fields. They are for 32 and 16 bit fields, respectively. You probably want to start with a cast or memcpy then byte swap the 16 and 32-bit fields if you need to. If you find that version isn't coming through with that approach without any byte swapping, then you have bit field troubles.

Bit fields are a big mess in C. Most people (including me) will advise you to avoid them.

Nathon
+3  A: 

You want to take a look at an the source for ip.h, that one is from FreeBSD. There should be a pre-dedined iphdr struct on your system, use that. Don't reinvent the wheel if you don't have to.

The easiest way to make this work is to take a pointer to the byte array from wireshark and cast it into a pointer to an iphdr. That'll let you use the correct header struct.

struct iphdr* hrd;
hdr = (iphdr*) cIPHeaderSample;
unsigned int version = hdr->version;

Also, htons takes in a 16-bit and changes the byte order, calling it on a 32-bit variable is just going to make a mess of things. You want htonl for 32-bit variables. Also note that for a byte there is no such thing as an endianess, it takes multiple bytes to have different endianess.

Paul Rubel
The `:4` indicates a 4 bit long bitfield - the `uint32_t` is only the base type for the bitfield.
caf
+1 for don't reinvent the wheel. Take a look at the tcpdump source and read up on using libpcap - you can possibly automate the capture yourself, although you could also use tcpdump and redirected IO to communicate with your process.
Ninefingers
thanks caf, noted.
Paul Rubel
+3  A: 

The most portable way to do it is one field at a time, using memcpy() for types longer than a byte. You don't need to worry about endianness for byte-length fields:

uint16_t temp_u16;
uint32_t temp_u32;
struct IPv4Header header;

header.Version = cIPHeaderSample[0] >> 4;

header.InternetHeaderLength = cIPHeaderSample[0] & 0x0f;

header.TypeOfServer = cIPHeaderSample[1];

memcpy(&temp_u16, &cIPHeaderSample[2], 2);
header.TotalLength = ntohs(temp_u16);

memcpy(&temp_u16, &cIPHeaderSample[4], 2);
header.Identification = ntohs(temp_u16);

header.Flags = cIPHeaderSample[6] >> 5;

memcpy(&temp_u16, &cIPHeaderSample[6], 2);
header.FragmentOffset = ntohs(temp_u16) & 0x1fff;

header.TTL = cIPHeaderSample[8];

header.Protocol = cIPHeaderSample[9];

memcpy(&temp_u16, &cIPHeaderSample[10], 2);
header.HeaderChecksum = ntohs(temp_u16);

memcpy(&temp_u32, &cIPHeaderSample[12], 4);
header.SourceAddress = ntohl(temp_u32);

memcpy(&temp_u32, &cIPHeaderSample[16], 4);
header.DestinationAddress = ntohl(temp_u32);
caf
Impressive :) I was hoping for a more general solution, but if this works I'll be happy.
StackedCrooked
@StackedCrooked: You'll notice that the handling for whole 1, 2 or 4 byte fields is always the same pattern - it's just the fields that are a strange number of bits that need special handling. You could (should) write inline helper functions for those common cases.
caf
A: 

Updated:

I suggest you use memcpy to avoid the issues of bitfields and struct alignment, as this can get messy. The solution below works on a simple example, and can be easily extended:

struct IPv4Header
{
    uint32_t Source;
};

int main(int argc, char **argv) {
    const uint8_t cIPHeaderSample[] = {
        0x45, 0x00, 0x05, 0x17
    };

    IPv4Header header;
    memcpy(&header.Source, cIPHeaderSample, sizeof(uint8_t) * 4);
    header.Source= ntohl(header.Source);
    cout << hex << header.Source<< endl;
}

Output: 
45000517
Justin Ardini
`header.Version` is supposed to be 4 *bits*, not 4 bytes - the output should be just `4` (for the eponymous "IPv4").
caf
Thanks, I updated as you were commenting, I originally saw the `uint32_t` type and thought it was the OP's mistake. The example should be correct now.
Justin Ardini