sveatlo
sveatlo

Reputation: 593

Access UDP from ICMP message

I'm trying to catch all ICMP "destination unreachable - port unreachable" messages using RAW sockets in C++. And then process the underlying UDP protocol to find out for which port the destination was not reachable. So far I managed to receive the ICMP messages and filter them based on their type and code. However, I'm having hard time accessing the udp header. So far I tried these:

*(struct udphdr*)(icmp + sizeof(struct icmphdr) + sizeof(struct iphdr))

and

struct iphdr *ip = (struct iphdr*)ether_frame;
struct icmphdr *icmp = (struct icmphdr*)(ether_frame + ip->ihl*4);
struct iphdr *ip2 = (struct iphdr*)(icmp + sizeof(struct icmphdr))
struct udphdr *udphdr = (struct udphdr*)(ip2 + ip2->ihl*4)

But neither one worked. I'm sure it's just some simple pointers arithmetic mistake, but I cannot figure it out. So my questions are: How exactly are the protocols organized in ICMP response? How can I access the port number which was unreachable from a raw icmp response?

EDIT: The socket used for receiving was created using this->rcv_sd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP and the response is received using recv(this->rcv_sd, ether_frame, IP_MAXPACKET, 0), where ether_frame is a uint8_t array of size IP_MAXPACKET.

Upvotes: 1

Views: 591

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 595827

You are using pointer arithmetic, adding byte counts to struct pointers as-is, so statements like (icmp + sizeof(struct icmphdr)) and (ip2 + ip2->ihl*4) are not doing what you think they are doing, causing you to end up with wrong addresses.

When you add a number to a typed pointer, the pointer is adjusted by the number of bytes of the underlying type multiplied by the specified number.

So, this:

struct iphdr *ip2 = (struct iphdr*)(icmp + sizeof(struct icmphdr))

Is equivalent to this:

struct iphdr *ip2 = (struct iphdr*)(((uint8_t*)icmp) + (sizeof(*icmp) * sizeof(struct icmphdr)))

And this:

struct udphdr *udphdr = (struct udphdr*)(ip2 + ip2->ihl*4)

Is equivalent to this:

struct udphdr *udphdr = (struct udphdr*)(((uint8_t*)ip2) + (sizeof(*ip2) * (ip2->ihl*4)))

Since you want to offset your struct pointers by bytes, not elements, you need to type-cast the struct pointers to byte pointers before adding the byte counts to them:

struct iphdr *ip2 = (struct iphdr*)(((uint8_t*)icmp) + sizeof(struct icmphdr))
// or simpler, using correct pointer arithmetic:
// struct iphdr *ip2 = (struct iphdr*)(icmp + 1)

struct udphdr *udphdr = (struct udphdr*)(((uint8_t*)ip2) + (ip2->ihl*4))

You don't have this issue when assigning your ip and icmp pointers from your ether_frame buffer because ether_frame is an array of uint8_t, which decays as-is to a uint8_t* pointer.

Upvotes: 3

Related Questions