Retrieve TCP MSS value from Socket Buffer - c

I am filtering packets on kernel level through LKM/netfilter using attributes from headers (TCP/IP) [linux tcp.h and ip.h].
Follows an example of the code:
static unsigned int Filter(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) {
struct ethhdr *ethh;
struct iphdr *iph; // ip header struct
struct tcphdr *tcph; // tcp header struct
ethh = eth_hdr(skb);
iph = ip_hdr(skb);
if (!(iph)){
return NF_ACCEPT;
}
if (iph->protocol == IPPROTO_TCP){ // TCP Protocol
tcph = tcp_hdr(skb);
if (iph->tot_len > htons(64)) return NF_ACCEPT;
if ((iph->ttl > htons(254))
|| (iph->ttl == htons(64) && tcph->window > htons(1024))
|| (iph->ttl <= htons(65) && tcph->window <= htons(1024) && ((iph->frag_off & IP_DF)==0)))
return NF_DROP;
return NF_ACCEPT;
}
return NF_ACCEPT;
}
But now I have to include a condition based on MSS Value, this criteria was raised through Wireshark analysis and according to Wireshark Reference:
Field Name: tcp.options.mss_val
Description: MSS Value
Type: Unsigned integer, 2 bytes
I looked for it here on Stack Overflow and through Google but did not find how to retrieve this tcp.options.mss_val from socket buffer (skb).
Please, any idea on how to retrieve it ?

Related

Recalculating TCP Checksum in Linux Kernel Module

I am building a Netfilter module which modifies the TCP payload for packets destined to a specific port. I do not modify neither the IP header nor the TCP header. The module is called at the first point in the Netfilter directly after a packet is received (NF_INET_PRE_ROUTING). Therefore I have to recalculate the TCP checksum field in each modified packet. I already saw some posts here and used there methods to recalculate TCP checksum, but non of these methods worked. Below is the two methods I used:
Method 1:
tcplen = (skb->len - (iph->ihl << 2)); /* tcplen is the length of the
* skb - the ip-header length */
tcph->check = 0;
tcph->check = csum_tcpudp_magic(iph->saddr, iph->daddr,
tcplen, IPPROTO_TCP,
csum_partial(tcph, tcplen, 0));
skb->ip_summed = CHECKSUM_UNNECESSARY;
Method 2:
tcph->check = ~(~tcph->check + ~new_field + new_field);
skb->ip_summed = CHECKSUM_UNNECESSARY;
In the two cases, I get the following error:
verif_dss_csum csum is wrong: 0x874a data_seq 2760801057
dss_csum_added 1 overflowed 0 iterations 1
Any solution for this problem? I am developing my module for Linux Kernel 4.4.83
**The Above code only helps with calculating checksum of pseudo header **
here is the code for calculating IP header Checksum and TCP/UDP Checksum
void UpdateChecksum(char *prefix,struct sk_buff *skb){
struct iphdr *ip_header;
ip_header = ip_hdr(skb);
skb->ip_summed = CHECKSUM_NONE; //stop offloading
skb->csum_valid = 0;
ip_header->check = 0;
ip_header->check = ip_fast_csum((u8 *)ip_header, ip_header->ihl);
if ( (ip_header->protocol == IPPROTO_TCP) || (ip_header->protocol == IPPROTO_UDP) ) {
if(skb_is_nonlinear(skb))
skb_linearize(skb); // very important.. You need this.
if (ip_header->protocol == IPPROTO_TCP) {
struct tcphdr *tcpHdr;
unsigned int tcplen;
tcpHdr = tcp_hdr(skb);
skb->csum =0;
tcplen = ntohs(ip_header->tot_len) - ip_header->ihl*4;
tcpHdr->check = 0;
tcpHdr->check = tcp_v4_check(tcplen, ip_header->saddr, ip_header->daddr, csum_partial((char *)tcpHdr, tcplen, 0));
//printk(KERN_INFO "%s: TCP Len :%d, Computed TCP Checksum :%x : Network : %x\n",prefix,tcplen,tcpHdr->check,htons(tcpHdr->check));
} else if (ip_header->protocol == IPPROTO_UDP) {
struct udphdr *udpHdr;
unsigned int udplen;
udpHdr = udp_hdr(skb);
skb->csum =0;
udplen = ntohs(ip_header->tot_len) - ip_header->ihl*4;
udpHdr->check = 0;
udpHdr->check = udp_v4_check(udplen,ip_header->saddr, ip_header->daddr,csum_partial((char *)udpHdr, udplen, 0));;
//printk(KERN_INFO "%s: UDP Len :%d, Computed UDP Checksum :%x : Network : %x\n",prefix,udplen,udpHdr->check,htons(udpHdr->check));
}
}
}
There is no need to recalculate the ipv4 header checksum, if you change only the tcp payload.
So if I understand you correctly, it should be just:
/* ... */
struct iphdr *ip_header = ip_hdr(skb);
if (ip_header->protocol == IPPROTO_TCP) {
struct tcphdr *tcp_header = tcp_hdr(skb);
if ((unsigned int)ntohs(tcp_header->dest) == some_port) {
unsigned char *data = (char *)tcp_header + tcp_hdrlen(skb);
/* altering TCP-payload */
tcp_header->check = 0;
tcp_header->check = tcp_v4_check(ntohs(ip_header->tot_len) - (ip_header->ihl << 2),
ip_header->saddr, ip_header->daddr,
csum_partial(tcp_header, tcp_header->doff << 2, 0));
/* ... */
/* e.g return NF_ACCEPT */

Linux Kernel Module - nfilter hook incorrect data on Slackware

I have a linux kernel module that needs to process the data received via netfilter hook as a socket buffer.
The data received on Debian 8 (kernel: 3.16.0) is good but with SlackWare 14.0 (kernel: 3.2.29) the data is incorrect. I do not understand what's wrong. I searched everywhere on the forum and on Google but I never found a solution.
This is my nfilter hook function:
static unsigned int magic_packet_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 tcphdr *tcph;
//Return if empty
if(!skb)
return NF_ACCEPT;
//Get ip header
iph = ip_hdr(skb);
//Check protocol
if(iph->protocol != IPPROTO_TCP)
return NF_ACCEPT;
//Get tcp header
tcph = tcp_hdr(skb);
//Get & check data
char *data;
data = (char *)((unsigned char *)tcph+(tcph->doff*4));
if(!data)
return NF_ACCEPT;
//Print in dmesg
#ifdef DEBUG
printk("anhackit data: %s\n", data);
#endif
//Convert source ip to string
char ip[16];
snprintf(ip, 16, "%pI4", &iph->saddr);
//Convert destination port to string
char port[6];
sprintf(port, "%u", ntohs(tcph->dest));
#ifdef DEBUG
printk("anhackit - magic packet received from %s on port %s !\n", ip, port);
#endif
return NF_ACCEPT;}
I hope someone can help me. Thanks in advance.

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.

how to move packet from NF_INET_PRE_ROUTING to NF_INET_POST_ROUTING?

I have a kernel module that utilizes netfilter hooks. The goal is to forward packets to another destination. As I can see by design packets coming from outside with daddr set to my servers IP pass through NF_INET_PRE_ROUTING and then suppose to be queued for local application. On NF_INET_PRE_ROUTING I alter specific packets (detect my own protocol) and replace daddr with remote servers IP and saddr with my servers IP. I would like to do it from within kernel module itself but cannot find a way to either move existing packet to another routing point (either NF_INET_FORWARD or NF_INET_LOCAL_OUT or even NF_INET_POST_ROUTING) or to create new packet and insert it into TCP/IP stack as if it is sent from server itself. Currently the packet simply goes to the blackhole after first hook. I do not see it going to any other hooks somehow. How could I do that?
My current code (testing code where remote server is same as client):
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/skbuff.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <net/ip.h>
#include <net/tcp.h>
#include <net/route.h>
#define DEBUG 1
static struct nf_hook_ops nfho;
static __be32 srv_addr = 0x620aa8c0;
static __be32 cli_addr = 0x630aa8c0;
static __be32 rem_addr = 0x630aa8c0;
static unsigned int hook_func(unsigned int hooknum, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)){
struct iphdr *ip_header;
struct tcphdr *tcp_header;
ip_header = (struct iphdr *)skb_network_header(skb);
skb_set_transport_header(skb, ip_header->ihl * 4);
tcp_header = (struct tcphdr *)skb_transport_header(skb);
#if DEBUG > 0
if(tcp_header->dest == ntohs(80) || tcp_header->source == ntohs(80))//(ip_header->saddr == cli_addr || ip_header->saddr == srv_addr || ip_header->saddr == rem_addr) &&
printk(KERN_INFO "[HTTP] Got a packet to %d.%d.%d.%d:%d from %d.%d.%d.%d:%d in hooknum=%d\n",
ip_header->daddr & 0x000000FF,
(ip_header->daddr & 0x0000FF00) >> 8,
(ip_header->daddr & 0x00FF0000) >> 16,
(ip_header->daddr & 0xFF000000) >> 24,
ntohs(tcp_header->dest),
ip_header->saddr & 0x000000FF,
(ip_header->saddr & 0x0000FF00) >> 8,
(ip_header->saddr & 0x00FF0000) >> 16,
(ip_header->saddr & 0xFF000000) >> 24,
ntohs(tcp_header->source),
hooknum);
#endif
if(ip_header->saddr == cli_addr && tcp_header->dest == ntohs(80)){
ip_header->daddr = rem_addr;
ip_header->saddr = srv_addr;
ip_header->check = 0;
ip_send_check(ip_header);
tcp_header->check = 0;
tcp_header->check = tcp_v4_check(skb->len - 4*ip_header->ihl, ip_header->saddr, ip_header->daddr, csum_partial((char *)tcp_header, skb->len - 4*ip_header->ihl,0));
okfn(skb);
return NF_STOP;
}
if(ip_header->saddr == rem_addr && tcp_header->source == ntohs(80)){
ip_header->daddr = cli_addr;
ip_header->saddr = srv_addr;
ip_header->check = 0;
ip_send_check(ip_header);
tcp_header->check = 0;
tcp_header->check = tcp_v4_check(skb->len - 4*ip_header->ihl, ip_header->saddr, ip_header->daddr, csum_partial((char *)tcp_header, skb->len - 4*ip_header->ihl,0));
okfn(skb);
return NF_STOP;
}
return NF_ACCEPT;
}
static int __init init_main(void) {
nfho.hook = hook_func;
nfho.hooknum = 0;
nfho.pf = PF_INET;
nfho.priority = NF_IP_PRI_FIRST;
nf_register_hook(&nfho);
#if DEBUG > 0
printk(KERN_INFO "[HTTP] Successfully inserted protocol module into kernel.\n");
#endif
return 0;
}
static void __exit cleanup_main(void) {
nf_unregister_hook(&nfho);
#if DEBUG > 0
printk(KERN_INFO "[HTTP] Successfully unloaded protocol module.\n");
#endif
}
module_init(init_main);
module_exit(cleanup_main);
MODULE_LICENSE("GPL v3");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
I couldn't find any way to programmatically forward packets in a more or less proper way.
The only way I found (seems to be very popular solution) is to manually modify all related fields in skb_buff and transmit altered packet through dev_queue_xmit. This way isn't good, because it does not implement finding a good route for packet. F.e. if neighbouring network includes many nodes that actually could be used for packet routing it doesn't seem to be possible to find a proper route from kernel module (or I am not aware of such way). Also source code for kernels TCP/IP stack shows presence of ip_forward function, which is not available from any part of kernel module and my try to reproduce that function ended up in dragging half of TCP/IP stack into module. This function could be ideal option for programmatical packet forwarding since it only takes a few parameters and all will alter all needed parts of packet by itself.
Anyways. My own fixed code now looks like this:
#include <linux/types.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include "my_mod.h"
#define DRIVER_AUTHOR "AlexKey"
#define DRIVER_DESC "HTTP packets manipulations"
#define DEBUG 1
static struct nf_hook_ops nfho;
static unsigned int hook_func(unsigned int hooknum, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)){
struct iphdr *ip_header;
struct tcphdr *tcp_header;
struct ethhdr *eth_header;
u32 saddr, daddr;
u16 source, dest;
/* Get all the headers */
eth_header = (struct ethhdr *)skb_mac_header(skb);
ip_header = (struct iphdr *)skb_network_header(skb);
skb_set_transport_header(skb, ip_header->ihl * 4);
tcp_header = (struct tcphdr *)skb_transport_header(skb);
/* If the packet source or dest are not 80 then the packet is not for us :) */
if(tcp_header->source != ntohs(80) && tcp_header->dest != ntohs(80))
return NF_ACCEPT;
#if DEBUG > 0
printk(KERN_INFO "[HTTP] Got packet on %d from %d\n", htons(tcp_header->dest), htons(tcp_header->source));
#endif
saddr = ip_header->saddr;
daddr = ip_header->daddr;
source = tcp_header->source;
dest = tcp_header->dest;
/* In link layer header change sender mac to our ethernet mac
and destination mac to sender mac :) ping-pong */
memcpy(eth_header->h_dest,eth_header->h_source,ETH_ALEN);
memcpy(eth_header->h_source,skb->dev->dev_addr,ETH_ALEN);
/* Set new link layer headers to socket buffer */
skb->data = (unsigned char *)eth_header;
skb->len += ETH_HLEN;
/* Setting it as outgoing packet */
skb->pkt_type = PACKET_OUTGOING;
/* Swap the IP headers sender and destination addresses */
memcpy(&ip_header->saddr, &daddr, sizeof(u32));
memcpy(&ip_header->daddr, &saddr, sizeof(u32));
/* If transmission suceeds then report it stolen
if it fails then drop it */
if(dev_queue_xmit(skb)==NET_XMIT_SUCCESS){
#if DEBUG > 0
printk(KERN_INFO "[HTTP] Successfully sent packet\n");
#endif
return NF_STOLEN;
} else {
#if DEBUG > 0
printk(KERN_INFO "[HTTP] Sending failed\n");
#endif
return NF_DROP;
}
}
static int __init init_main(void) {
nfho.hook = hook_func;
nfho.hooknum = 0;
nfho.pf = PF_INET;
nfho.priority = NF_IP_PRI_FIRST;
nf_register_hook(&nfho);
#if DEBUG > 0
printk(KERN_INFO "[HTTP] Successfully inserted protocol module into kernel.\n");
#endif
return 0;
}
static void __exit cleanup_main(void) {
nf_unregister_hook(&nfho);
#if DEBUG > 0
printk(KERN_INFO "[HTTP] Successfully unloaded protocol module.\n");
#endif
}
module_init(init_main);
module_exit(cleanup_main);
MODULE_LICENSE("GPL v3");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
I would love to hear any modifications to this code.
Alex, I had the exact same problem you had trying to send the mangled skb from kernel. I went through the same thought process, but couldn't find an elegant solution that will properly handle the routing of the outgoing packet. Until I found that I can also use sockets in the kernel.
Create a raw socket in your kernel module using sock_create in socket.h like so:
struct socket *mySock;
if ( sock_create(PF_INET, SOCK_RAW, IPPROTO_RAW, &mySock) != 0 )
{
/* Error creating socket */
}
Once you modified the IP header, you can then use a function to send your skb using sock_sendmsg:
int sock_send(struct socket *sock, struct sockaddr_in *addr, struct iovec *iov, int iovlen, int totalLen)
{
struct msghdr msg;
mm_segment_t oldfs;
int size = 0;
if (sock == NULL || sock->sk == NULL)
{
return 0;
}
msg.msg_flags = 0;
msg.msg_name = addr;
msg.msg_namelen = sizeof(struct sockaddr_in);
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = iovlen;
/* Set to kernel data segment since sock_sendmsg expects user space pointers */
oldfs = get_fs();
set_fs(KERNEL_DS);
size = sock_sendmsg(sock, &msg, totalLen);
set_fs(oldfs);
return size;
}
Remember with IPPROTO_RAW socket, you must make the IP header yourself, but you already have one in the skb. Now you just have to create and populate the struct iovec array and pass it to sock_send.
For struct sockaddr_in *addr, use the same destination address as the IP header:
struct sockaddr_in addr = { .sin_family = PF_INET,
.sin_port = 0, /* 0 for RAW socket */
.sin_addr = { .s_addr = dstAddr } };
Remember to return NF_DROP or free the skb and return NF_STOLEN to clean it up once you are done with the skb.
The way to do this using kernel hooks is to manually modify all related fields in skb_buff and transmit altered packet through dev_queue_xmit. As you are trying to create a packet "out of thin air" to a destination, you need to be careful with the routing. Assuming that the routing is properly set up from the userspace point of view, all you need to do to enable the packet to fly, is to use ip_route_output() before dev_queue_xmit(). For example:
struct rtable *rt;
struct net *nt;
// do the packet mangling, headers copying here
skb->dev = new_dev; // new_dev is the iface through which to reach the dest
nt = dev_net(skb->dev);
rt = ip_route_output(nt, ip_header->daddr, ip_header->saddr,
RT_TOS(ip_header->tos), skb->dev_ifindex);
skb_dst_set(skb, &(rt->dst));
return NF_ACCEPT; // pass the mangled packet on, business as usual

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