kernel module for netfilter - kernel-module

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.

Related

Difference between skb_header_pointer and skb_transport_header?

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

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
}

Change destination ip

I'm trying to create a kernel module that forward packets in certain conditions. Now I'm trying to do just a hard code test to forward a packet received in an interface and forward it to another interface. In this test I'm receiving a packet from 192.168.56.101 on eth0 and I want to forward this packet on eht1 for 192.168.57.103. In eth0 my ip is 192.168.56.102 and in eth1 my ip is 192.168.57.102. The transport protocol I'm using is a experimental protocol (253). The following code is just a simplified part of my code:
#define XOR_PROTOCOL 253
static unsigned int xor_pre_routing_hook(const struct nf_hook_ops *ops, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *))
{
struct iphdr *iph;
struct xorhdr *ptr;
char sip[15];
char sip2[15];
iph = ip_hdr(skb);
sprintf(sip, "%pI4", &iph->saddr);
sprintf(sip2, "%pI4", &iph->daddr);
// Check if is XOR protocol
if (iph->protocol == XOR_PROTOCOL) {
DEBUG("(Ogirinal) From %pI4 to %pI4.\n", &iph->saddr, &iph->daddr);
if (strcmp(sip, "192.168.56.101") == 0 && strcmp(sip2, "192.168.56.255") == 0) {
//iph->saddr = inet_addr("192.168.57.102");
iph->daddr = inet_addr("192.168.57.103");
DEBUG("(Modified) From %pI4 to %pI4.\n", &iph->saddr, &iph->daddr);
iph = ip_hdr(skb);
iph->check = 0;
ip_send_check (iph);
return NF_ACCEPT;
}
}
accept:
return NF_ACCEPT;
}
This hook in NF_INET_PRE_ROUTING. I also have a hook to just print source and destination ip in NF_INET_FORWARD, but there is no packet passing through this hook.
I'm testing with 3 linux virtual machine on virtual box, and I enabled the forward option in each vm. Is possible to forward packets in this scenario? What I'm doing wrong and what can I do to solve this problem?
The problem is the broadcast ip 192.168.56.255, with the ip 192.168.56.102 the packets were forwarded.

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.

Calculating TCP Checksum in a netfilter module

I am trying to change some fields in the IP and TCP header in a netfilter postrouting hook, however I can't seem to get the kernels TCP checksum function to work properly to amend it afterwards.
The checksum is fine during the TCP handshake, but as soon as the packet has any payload the checksum is wrong.
I have pulled this checksum code together from digging around the TCP source. I am fairly sure tcplen is correct, matching the expected TCP header + payload size.
static unsigned int posthook_fn(
unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
struct iphdr *iph;
struct tcphdr *tcph;
iph = ip_hdr(skb);
tcph = (struct tcphdr *)(skb->data + iph->ihl * 4);
tcph->source = port;
iph->saddr = addr;
tcplen = (skb->len - (ip_header->ihl << 2));
tcph->check = 0;
tcph->check = tcp_v4_check(tcph, tcplen,
iph->saddr,
iph->daddr,
csum_partial((char *)tcph, tcplen, 0));
skb->ip_summed = CHECKSUM_NONE; //stop offloading
ip_header->check = ip_fast_csum((u8 *)iph, iph->ihl);
return NF_ACCEPT;
}
Am I correct in thinking that tcp_v4_check calculates the psuedo header and csum_partial calculates the unfolded checksum for the payload and tcp_header?
I really want to avoid writing the function myself as the kernel will be much faster as the underlying functions use assembly for the calculation.
Is there an alternative method that might work? Or is there something I am missing out?
There is no need for extra call to skb_is_nonlinear(), since include/linux/skbuff.h:
static inline int skb_linearize(struct sk_buff *skb)
{
return skb_is_nonlinear(skb) ? __skb_linearize(skb) : 0;
}
Also, you have to:
ip_header->check = 0
before:
ip_header->check = ip_fast_csum((u8 *)iph, iph->ihl);
It's taken a while to get here but the problem seems to be that the socket buffer isn't always linear, the following code ensures that it is before calculating the checksum.
if (skb_is_nonlinear(skb)) {
if (skb_linearize(skb) != 0) {
return NF_DROP;
}
iph = ip_hdr(skb);
tcph = (void *)iph + (iph->ihl << 2);
}

Resources