0
struct msghdr msg;
struct iovec iov;
unsigned char buf[BUFSIZE] = { '\0', };
ssize n;
int fd;
...

fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

...

msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_flags = 0;

iov.iov_base = buf;
iov.iov_len = sizeof(buf);

...   
n = recvmsg(sockfd, &msg, 0);
...
parse_pkt(buf, BUFSIZE);

So far so good, the packet is received, and now I need to parse it:

static int parse_packet(unsigned char *pkt, int len)
{
    struct iphdr *ip = (struct iphdr *)(pkt + 14);
    struct udphdr *udp;

    /* ip has valid pointer and we can explore IP header fields, ip pointer is NOT modified. */
    ...

    udp = (struct udphdr *)(ip + (ip->ihl << 2));
    /* at this point I'm expecting udp to point past IP header space. */
    ...

}

The problem I'm seeing is that udp does not point where I'm expecting, I don't get why: pkt contains the whole packet (including Ethernet header, no VLANs), so ip obtains a pointer past ether_header, so udp = (struct udphdr *)(ip + (ip->ihl << 2)) should just skip over IP header size, but it does not!

What does work though is this:

struct iphdr *ip = (struct iphdr *)(pkt + 14);
...
udp = (struct udphdr *)(pkt + 14 + (ip->ihl << 2));

What is it so, what am I doing wrong?

Mark
  • 6,052
  • 8
  • 61
  • 129
  • 3
    Pointer arithmetic operates in units the size of the pointed-to type. Thus, this: `ip + (ip->ihl << 2)` does not compute a pointer offset from `ip` by `ip->ihl << 2` bytes, but rather one offset from `ip` by `sizeof(struct iphdr) * (ip->ihl << 2)` bytes. – John Bollinger Sep 12 '22 at 14:05
  • Where is `pkt` defined? Shouldn't it be `buf` instead? – Remy Lebeau Sep 12 '22 at 22:18
  • @JohnBollinger, thanks for comment. So `udp = (struct udphdr *)(ip + sizeof(struct iphdr) * (ip->ihl << 2));` did not help, it results in very large offset. – Mark Sep 13 '22 at 13:15
  • @RemyLebeau, thanks for spotting this, I updated my code. – Mark Sep 13 '22 at 13:15
  • @Mark, nowhere did I suggest that that variation to solve the problem (or at all). In fact, read my comment again: it predicts exactly the result you observe, that the variation you now describe would be even farther off than the one you started with. – John Bollinger Sep 13 '22 at 13:20
  • @JohnBollinger, yes indeed, I should've read your comment more carefully! – Mark Sep 14 '22 at 12:17

1 Answers1

2

When you do this:

udp = (struct udphdr *)(ip + (ip->ihl << 2));

You're doing pointer arithmetic in units of sizeof(*ip) instead of 1.

Your alternate works:

udp = (struct udphdr *)(pkt + 14 + (ip->ihl << 2));

Because pkt is an unsigned char * so pointer arithmetic is done in single byte units.

This would also work:

udp = (struct udphdr *)((unsigned char *)ip + (ip->ihl << 2));

As it allow you to perform pointer arithmetic in single byte units.

dbush
  • 205,898
  • 23
  • 218
  • 273