Functions for getting packet headers available in sk_buff - c

There are functions in Linux for getting Ethernet header, IP header,and UDP headers likes these
udp_hdr(skb)
ip_hdr
skb_push(skb, ETH_HLEN)
But I could not find any function for getting payload like body of a packet like i.e. which contains body so I can write HTTP or other protocol data. in Linux Device Driver book or after searching couldn't find it. so question is how to compose UDP packet with Ethernet, IP, UDP headers and payload in kernel?

any function for getting payload like body of a packet
You can access payload different ways depending on what you want to do the next time. E.g.:
struct iphdr *iph = ip_hdr(skb);
if (iph->protocol == IPPROTO_UDP) {
struct udphdr *udph = udp_hdr(skb);
// E.g. check for UDP port
struct myl7_header *l7h = (struct myl7_header *)(udph + sizeof(struct udphdr));
// ...
}
Or you can pull the network and transport headers if you want to reconstruct encapsulation further or they are no longer needed (rough example, not with all possible sanity checks):
struct iphdr *iph = ip_hdr(skb);
if (iph->protocol == IPPROTO_UDP) {
struct udphdr *udph = udp_hdr(skb);
struct myl7_header *l7h;
// E.g. check for UDP port
skb_pull(skb, sizeof(struct iphdr));
skb_pull(skb, sizeof(struct udphdr));
l7h = (struct myl7_header *)skb->data;
// tansport protocol payload's length:
// skb->len or skb_tail_pointer(skb) - skb->data
}
I don't really know what you mean by payloadhtml, L7-protocol it not so kernel specific thing, so in general we are talking about transport protocol's payload.
N.B.: ip_hdr(), udp_hdr() functions imply that non-paged (linear) skb is used.
Related: What's the correct way to process all the payload of a sk_buff packet in Linux

Related

linux packet filtering by interface

I'm trying to write my own network packet filter module to replace iptables on my custom linux system.
I want the filter to drop all packets arriving from port 80 except the ones coming from the interface wlan0.
I hoped there would be a interface member in the struct iphdr but it seems to not be there (which makes sense since I think the interface is part of the 2nd layer). does anyone know how I can acheive this?
this is my code and I'm wandering what to put in the if statement that will complete the check:
static unsigned int pkg_filter_handler(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
if (!skb) {
return NF_ACCEPT;
}
/* check if loopback */
u32 srcAddr;
struct iphdr *ipHeader = ip_hdr(skb);
/* check if TCP protocol */
else if (iphdr->protocol == IPPROTO_TCP) {
/* check interface */
if (/*TODO: accept wlan0 rcp packets ad drop the rest*/) {
return NF_ACCEPT;
}
return NF_DROP;
}
return NF_ACCEPT;
}

Add TCP Option to ACK packet during 3-way handshake

I'm writing kernel module using netfilter. I just want to handle ACK for SYN/ACK (TCP three-way handshake). I use skb_is_tcp_pure_ack function, but ACK for data is also processed.
How can I do? My kernel version is 3.10.0-514.16.1.el7.x86_64.
Current code looks like this:
struct iphdr *iph;
struct tcphdr *tcph;
struct net *net;
unsigned int hdr_len;
unsigned int tcphoff;
if (!skb_is_tcp_pure_ack(skb)) {
return NF_ACCEPT;
}
/* add tcp option */
/* A netfilter instance to use */
static struct nf_hook_ops nfho __read_mostly = {
.hook = ato_hookfn,
.pf = PF_INET,
.hooknum = NF_INET_POST_ROUTING,
.priority = NF_IP_PRI_LAST,
.owner = THIS_MODULE,
};
From the TCP state machine, you want to only match ACK packets when the state is TCP_SYN_SENT. Try adding another condition to check that, something like:
if (skb_is_tcp_pure_ack(skb)
&& skb->sk->sk_state == TCP_SYN_SENT) {
/*
* ACK(-only) packet during three-way handshake
*/
}
Also, note that skb_is_tcp_pure_ack was introduced in kernel version 4.* and not below.

Printing the Source and Destination IP address from an ARP Packet

I am able to print the destination and source address from an ARP packet. But I want to print the source ip and destination ip address from the ARP request. I have searched in the and found this structure.
`struct ether_arp {
struct arphdr ea_hdr;
u_char arp_sha[6];
u_char arp_spa[4];
u_char arp_tha[6];
u_char arp_tpa[4];
};`
But when I am trying to print arp_spa I am getting something like 0:1:8:0.
Obviously this is not the source IP address.
arp_ptr = (struct ether_arp *) packet;
I feel this is wrong as some bytes need to be skipped. But I am not sure about this
Can anybody please comment.
EDIT :
arp_ptr = (struct ether_arp *) packet;
ptr = arp_ptr->arp_sha;
i = ETHER_ADDR_LEN;
printf(" source is: ");
do{
printf("%s%x",(i == ETHER_ADDR_LEN) ? " " : ":",*ptr++);
}while(--i>0);
The above is the code I am using to print either source IP or source MAC from ARP request

libnet tcp header results to be all zero

I'm writing a few C line of code using libnet and pcap.
The purpose is to do a 3-way handshake manually sniffing filtered traffic on my NIC, looking for SYN packet and creating a SYN-ACK response using raw ipv4 socket.
I succesfully receive raw SYN packet using pcap_loop().
In my custom packet_handler() I do the following:
void packet_handler(u_char *user_args, const struct pcap_pkthdr *cap_header, const u_char *packet) {
struct libnet_ipv4_hdr *ip = (struct libnet_ipv4_hdr *)(packet + LIBNET_ETH_H);
struct libnet_tcp_hdr *tcp = (struct libnet_tcp_hdr *)(ip + (ip->ip_hl << 2));
printf(
"%s:%"PRIu16" > %s:%"PRIu16"\t[seq: %"PRIu32"\tack: %"PRIu32"]\n",
libnet_addr2name4(ip->ip_src.s_addr, LIBNET_DONT_RESOLVE),
ntohs(tcp->th_sport),
libnet_addr2name4(ip->ip_dst.s_addr, LIBNET_DONT_RESOLVE),
ntohs(tcp->th_dport),
ntohl(tcp->th_seq), ntohl(tcp->th_ack)
);
}
As results of opening a TCP connection I got the following result:
192.168.1.64:0 > 192.168.1.64:0 [seq: 0 ack: 0]
As you can see the IP header is correctly read, but the TCP header is not.
In particular, the problem is the libnet_tcp_hdr header fields which result to be all zero.
Am I doing something wrong in the pointer assignment?
Firstly, you need to verify that your packet handler only receives IP packets carrying TCP, not e.g. UDP.
However, your pointer arithmetic is wrong. Pointer aritmetic is done based on the type of the pointer, it is not based on bytes. This means this code:
struct libnet_tcp_hdr *tcp = (struct libnet_tcp_hdr *)(ip + (ip->ip_hl << 2));
adds ip->ip_hl << 2 * sizeof(struct struct libnet_ipv4_hdr bytes to your ip pointer.
Or if you look at it another way, the above code is exactly the same as:
struct libnet_tcp_hdr *tcp = (struct libnet_tcp_hdr *)(&ip[ip->ip_hl << 2]);
That might show more clearly what is going on.
You need to change that code to something like:
struct libnet_tcp_hdr *tcp = (struct libnet_tcp_hdr *)((unsigned char*)ip + (ip->ip_hl << 2));

C - Linux - kernel module - TCP header

I'm trying to create linux kernel module, that will inspect incoming packets. At the moment, I'm in process of extracting TCP header of packet and reading source and destination port -> However I'm getting incorrect values. I have hook function:
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 *ipp = (struct iphdr *)skb_network_header(skb);
struct tcphdr *hdr;
/* Using this to filter data from another machine */
unsigned long ok_ip = 2396891328;
/* Some problem, empty network packet. Stop it now. */
if (!skb)
return NF_ACCEPT;
/* Just to track only packets coming from 1 IP */
if (ipp->saddr != ok_ip)
return NF_ACCEPT;
/* Incomming packet is TCP */
if (ipp->protocol == IPPROTO_TCP) {
hdr = (struct tcphdr *) skb_transport_header(skb);
printk(" TCP ports: source: %d, dest: %d .\n", ntohs(hdr->source),
ntohs(hdr->dest));
}
}
Now, when I try to telnet port 21(not listening there I get):
[ 4252.961912] TCP ports: source: 17664, dest: 52 .
[ 4253.453978] TCP ports: source: 17664, dest: 52 .
[ 4253.953204] TCP ports: source: 17664, dest: 48 .
And when I telnet port 22 - SSH deamon listening there:
[ 4299.239940] TCP ports: source: 17664, dest: 52 .
[ 4299.240527] TCP ports: source: 17664, dest: 40 .
[ 4299.552566] TCP ports: source: 17664, dest: 40 .
As visible from output I'm getting very weird results, anyone has idea where problem is coming from? when I compile module I have no errors / warnings. Version of kernel(headers): 3.7.10 . Not using SELinux or similar.
I had the same problem writing a small firewall for a networking class I just found out the problem I was having. I was casting the tcp header wrong. Try casting to tcp then accessing the port.
Here is a code snippet of it working
struct iphdr *ip_header; // ip header struct
struct tcphdr *tcp_header; // tcp header struct
struct udphdr *udp_header; // udp header struct
struct sk_buff *sock_buff;
unsigned int sport ,
dport;
sock_buff = skb;
if (!sock_buff)
return NF_ACCEPT;
ip_header = (struct iphdr *)skb_network_header(sock_buff);
if (!ip_header)
return NF_ACCEPT;
//if TCP PACKET
if(ip_header->protocol==IPPROTO_TCP)
{
//tcp_header = (struct tcphdr *)skb_transport_header(sock_buff); //doing the cast this way gave me the same problem
tcp_header= (struct tcphdr *)((__u32 *)ip_header+ ip_header->ihl); //this fixed the problem
sport = htons((unsigned short int) tcp_header->source); //sport now has the source port
dport = htons((unsigned short int) tcp_header->dest); //dport now has the dest port
}
To get the IP header or TCP header from a socket buffer (skb), just apply the functions ip_hdr(skb) and tcp_hdr(skb).

Resources