Difference between skb_header_pointer and skb_transport_header? - c

I'm trying to implement a netfilter module, while processing sk_buff I found two possible ways to retrieve TCP header:
struct iphdr *ip_header = (struct iphdr *)skb_network_header(skb);
struct tcphdr *tcp_header = (struct tcphdr *)skb_transport_header(skb);
And
struct iphdr *ip_header = skb_header_pointer(skb, 0, sizeof(struct iphdr), &_iph)
struct tcphdr *tcp_header = skb_header_pointer(skb, ip_header->ihl * 4, sizeof(struct tcphdr), &_tcph);
Which one should I use?

You should use ip_hdr() from /include/linux/ip.h and tcp_hdr() from /include/linux/tcp.h in case you know that there cannot be paged-skb here:
struct iphdr *ip_header = ip_hdr(skb);
if (ip_header->protocol == IPPROTO_TCP) {
struct tcphdr *tcp_header = tcp_hdr(skb);
//...
skb_header_pointer() should be used in case the appearance of paged-skb is possible. Examples: IP, TCP, ICMP, etc.
So if the header is in paged data (fully or partially) - skb_header_pointer() will correctly handle it.
Also remember to check the return value of skb_header_pointer(), it can return NULL.
Useful links: 1, 2

Related

sockets: right way to access IP and UDP headers on raw socket buffer

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?
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.

How to print packet info of a specific IP address in netfilter using C?

I'm using this code to print some packet info about "all" received and sent packets from all IP addresses. How do I only print packet info of a specific IP address?
this is part of the code:
{
struct ethhdr *eth;
struct iphdr *ip_header;
eth = (struct ethhdr*)skb_mac_header(skb);
ip_header = (struct iphdr *)skb_network_header(skb);
if (HOST_IP_ADDR == ip_header->saddr)
return NF_ACCEPT;
printk("NF_IP_LOCAL_IN hook:\n");
printk("src mac %pM, dst mac %pM\n", eth->h_source, eth->h_dest);
printk("src IP addr:=%pI4\n", &ip_header->saddr);
return NF_ACCEPT;
}
If you have HOST_IP_ADDR defined as a string you can use in_aton() around the ip address you want to match against
ip_header = (struct iphdr *)skb_network_header(skb);
if (in_aton("192.168.1.1") == ip_header->saddr)
{
// matches 192.168.1.1
}

kernel module for netfilter

I have started writing a kernel module for netfilter.
I am able to hook the netfilter and able to access the sk_buff.
Now I am trying to modify the packet(sk_buff) and trying to reinject the packet in tcp stack but it is not working.
My requirement is to drop SYN-ACK packet from a server and sending back a cooked up ack packet back to server without sending the SYN-ACK to client.
I have tried dev_queue_xmit after swapping source/dest IP, source/dest port
and source/dest MAC and recalculating the checksum and then send the packet using dev_queue_xmit. though dev_queue_xmit return success, the packet is not injecting to stack. Any help will be appreciated.
My code:
code is part of NF_INET_POST_ROUTING hook.
void do_exp(struct sk_buff *skb)
{
printk(KERN_INFO "Enering do_exp\n");
struct iphdr *ip_header = (struct iphdr *)skb_network_header(skb);
struct tcphdr *tcp_header = (struct tcphdr *)skb_transport_header(skb);
struct sk_buff *newskb;
struct iphdr *newiph;
struct tcphdr *newtcph;
newskb = skb_copy(skb, GFP_ATOMIC);
//newskb->pkt_type = PACKET_OUTGOING;
// newskb->pkt_type = PACKET_HOST;
/*changing Mac address */
unsigned char srcaddr[6];
struct ethhdr *eth = eth_hdr(skb);
struct ethhdr *neweth = eth_hdr(newskb);
memcpy(srcaddr, eth->h_source, ETH_ALEN);
memcpy(neweth->h_source, eth->h_dest, ETH_ALEN);
memcpy(neweth->h_dest, srcaddr, ETH_ALEN);
newiph = (struct iphdr *) skb_network_header(newskb);
newtcph = (struct tcphdr *) skb_transport_header(newskb);
newiph->saddr = ip_header->daddr;
newiph->daddr = ip_header->saddr;
newtcph->source = tcp_header->dest;
newtcph->dest = tcp_header->source;
newtcph->syn = 0;
newtcph->ack_seq = tcp_header->seq + 1;
newtcph->seq = tcp_header->ack_seq;
newiph->check = ip_fast_csum((unsigned char *)newiph, newiph->ihl);
int ret = dev_queue_xmit(newskb);
}
TCP dump is showing correctly.

linux ethernet frame socket clarification

I am trying to understand below lines in the sample socket code in found in google.
struct ether_header *eh = (struct ether_header *) sendbuf;
struct iphdr *iph = (struct iphdr *) (sendbuf + sizeof(struct ether_header));
struct ether_header *eh -> So far in know *eh used to access the struct variable
i just want to understand these assignment
(struct ether_header *) sendbuf;
(struct iphdr *) (sendbuf + sizeof(struct ether_header));
In the first line
(struct ether_header *) sendbuf;
the variable sendbuf is cast to a pointer to the struct ether_header, you can read more about casting here
The second line
(struct iphdr *) (sendbuf + sizeof(struct ether_header));
it's adding sizeof(struct ether_header) to the pointer sendbuf, by doing that, it reaches the memory zone after the one occupied by the pointer to the struct ether_header , which seems to contain a pointer to the struct iphdr
This is the schematic representation of sendbuf
+------------------------------------------------------+
| eh |
+------------------------------------------------------+
| iph = eh + sizeof(struct ether_header) |
+------------------------------------------------------+
-- sendbuf --
first one is accessing ethernet header ptr, and next is accessing the iphdr ptr. ( ethernet packet contains IP packet)

error in ip address received

I am implementing raw sockets in C. I have two programs (say server and client). Client is sending info to server and then server is sending ACK. Client has sent info and server has successfully received it. Now server is sending back ACK. Now, Client has parsed ethernet header successfully. I have problem in receiving the IP address.
1. What should be the type of arguments of the function CreateIPHeader()?
2. How to print IP addresses in ParseIPHeader()?
3. What should be the data type of value returned by ParseIPHeader, if I want just the IP addresses?
struct iphdr *CreateIPHeader(char *src_ip,char *dst_ip)
{
struct iphdr *ip_header;
ip_header=malloc(sizeof(struct iphdr));
//OTHER FIELDS OF IP HEADER
ip_header->saddr = inet_ntoa(*((struct in_addr *)(src_ip)));
ip_header->daddr = inet_ntoa(*((struct in_addr *)(dst_ip)));
ip_header->check=ComputeChecksum((unsigned char *)ip_header,ip_header->ihl*4);
printf("\nip to be sent = %s",ip_header->saddr); //printing correct IP
printf("\nip to be rcvd = %s",ip_header->daddr);
return(ip_header);
}
char* ParseIPHeader(unsigned char *packet,int len)
{
struct iphdr *ip_header,*ret_ip;
unsigned char *out;
struct ethhdr *ethernet_header;
out=malloc(2048);
memset(out, 0, 2048);
ethernet_header=(struct ethhdr *) out;
ret_ip=(struct iphdr *) (out + sizeof(struct ethhdr));
if(ntohs(ethernet_header->h_proto)==ETH_P_IP)
{
if(len>=(sizeof(struct ethhdr)+sizeof(struct iphdr)))
{
ip_header=(struct iphdr*)(packet+sizeof(struct ethhdr));
ret_ip->saddr = ip_header->daddr;
ret_ip->daddr = ip_header->saddr;
printf("daddr SENT = %s",ret_ip->daddr); //how to print them?
printf("saddr SENT = %s",ret_ip->saddr);
}
else
printf("IP packet does not have full header\n");
}
else
{
//not an IP packet
}
return out;
}
int main()
{
unsigned char in[2048];
int len;
char *rcv_ip;
Struct iphdr *ip_header;
memset(in,0,2048);
len=recvfrom(raw,in,2048,0,(struct sockaddr *)&packet_info,&packet_info_size);
rcv_ip=ParseIPHeader(in,len); /*I want this function to return me the ip addresses which I would use in the next line.*/
ip_header =CreateIPHeader(rcv_ip+5,rcv_ip);
memset(in,0,2048);
memcpy(in+sizeof(struct ethhdr),ip_header,ip_header->ihl*4);
sendrawpacket(raw,in,pkt_len);
free(ip_header);
return 0;
}
Any help would be appreciated.
Thanks :)
Instead of:
printf("\nDest Addr %s \n",inet_ntoa(*((struct in_addr *)&((ip_header->daddr)))));
printf("\nSource Addr %s \n",inet_ntoa(*((struct in_addr *)&(ip_header->saddr))));
Perhaps:
printf("\nDest Addr %s \n",inet_ntoa(ip_header->daddr));
printf("\nSource Addr %s \n",inet_ntoa(ip_header->saddr));
If indeed the printf is causing you to segfault and not something else, then perhaps:
struct in_addr dest;
dest.s_addr = ip_header->daddr;
printf("\nDest Addr %s \n", inet_ntoa(dest));
This is because (assuming you are using struct iphdr *ip_header) ip_header->daddr has a type of __u32 and inet_ntoa takes a struct in_addr.
There are a couple things wrong.
CreateIPHeader
You are confusing inet_ntoa() with inet_aton(). When creating the IP header, you want to set ip_header->saddr using something like
inet_aton(src_ip, (struct in_addr *) &ip_header->saddr)
Remember, the s_addr and d_addr in struct iphdr are of type __u32, not char arrays. Thus, when you make this change, the printf statements in CreateIPHeader will break.
main
The reason why your printf's are working is due to Problem #1. On receive, you call CreateIPHeader to create an incorrect struct iphdr (see above), which now incorrectly has C-strings assigned to saddr and daddr. Later, abc points to this incorrect header; your printf's magically work because abc->saddr and abc->daddr actually are C strings.
You are only overwriting the iphdr portion of packet_buffer (your memcpy statement). You must also change the h_dest and h_src values in struct ethhdr.
You really don't need to convert the IP addresses into a C string and then convert them back into IP addresses.
Also, you aren't freeing ip_header.
In general, you can do something like this:
int main()
{
unsigned char in[2048];
unsigned char out[2048];
int len;
memset(in, 0, 2048);
memset(out, 0, 2048);
len = recvfrom(raw, in, 2048, 0,
(struct sockaddr *) &packet_info, &packet_info_size);
struct ethhdr *in_eth = (struct ethhdr *) in;
if (ntohs(ethernet_header->h_proto) == ETH_P_IP &&
len >= sizeof(struct ethhdr) + sizeof(struct iphdr) {
struct iphdr *in_ip = (struct iphdr*) (in + sizeof(struct ethhdr));
/* create outbound packet, starting with eth header */
struct ethhdr *out_eth = (struct ethhdr *) out;
/* ... set h_dest and h_src */
struct iphdr *out_ip = (struct iphdr *) (out + sizeof(struct ethhdr));
out_ip->saddr = in_ip->daddr;
out_ip->daddr = in_ip->saddr;
/* calculate the IPv4 checksum, packet len etc */
sendrawpacket(raw, out, pkt_len);
}
return 0;
};
No guarantee that's bug-free. Just wrote it in the browser.

Resources