How to Capture Network Frames in a Kernel Module

How to capture network frames in a kernel module

Actually Netfilter should work fine because it receives the entire packet (internally stored as an sk_buff which includes the Link layer information). Here's some sample code that should get you started. This code intercepts all incoming packets for a given device and prints the src MAC and src IP.

static struct nf_hook_ops nfin;

static unsigned int hook_func_in(unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
struct ethhdr *eth;
struct iphdr *ip_header;
/* check *in is the correct device */
if (in is not the correct device)
return NF_ACCEPT;

eth = (struct ethhdr*)skb_mac_header(skb);
ip_header = (struct iphdr *)skb_network_header(skb);
printk("src mac %pM, dst mac %pM\n", eth->h_source, eth->h_dest);
printk("src IP addr:=%d.%d.%d.%d:%d\n", NIPQUAD(ip_headr->saddr));
return NF_ACCEPT;
}

static int __init init_main(void)
{
nfin.hook = hook_func_in;
nfin.hooknum = NF_IP_LOCAL_IN;
nfin.pf = PF_INET;
nfin.priority = NF_IP_PRI_FIRST;
nf_register_hook(&nfin);

return 0;
}



static void __exit cleanup_main(void)
{
nf_unregister_hook(&nfin);
}
module_init(init_main);
module_exit(cleanup_main);

What do I need to build to directly access the Ethernet frame bits in the kernel level?

For receiving frames destined to all hosts you must set your network interface in promiscuous mode.

For getting frames you can use different alternatives:

  1. pcap API (library libpcap)
  2. packet sockets: http://man7.org/linux/man-pages/man7/packet.7.html
  3. Look at ebtables (I've never used it so I'm not sure in this point): http://linux.die.net/man/8/ebtables
  4. Here netfilter is proposed: How to capture network frames in a kernel module

If you still want to hack the kernel you don't need to create a new Ethernet device driver, just write a kernel module that registers to receive frames received from the Ethernet device driver. Look at kernel file http://lxr.free-electrons.com/source/net/core/dev.c , you can begin with function:

int netif_rx(struct sk_buff *skb)

This is the one receiving frames from the device driver.

netfilter-like kernel module to get source and destination address

This article is a little outdated now. Text that you don't understand is only applicable to kernel versions below 3.11.

For new kernels (>= 3.11)

If you are sure that your code will only be used with kernels >= 3.11, you can use next code for both input and output packets:

udp_header = (struct udphdr *)skb_transport_header(skb);  

Or more elegant:

udp_header = udp_hdr(skb);

It's because transport header is already set up for you in ip_rcv():

skb->transport_header = skb->network_header + iph->ihl*4;

This change was brought by this commit.

For old kernels (< 3.11)

Outgoing packets (NF_INET_POST_ROUTING)

In this case .transport_header field set up correctly in sk_buffer, so it points to actual transport layer header (UDP/TCP). So you can use code like this:

udp_header = (struct udphdr *)skb_transport_header(skb);  

or better looking (but actually the same):

udp_header = udp_hdr(skb);  

Incoming packets (NF_INET_PRE_ROUTING)

This is the tricky part.

In this case the .transport_header field is not set to the actual transport layer header (UDP or TCP) in sk_buffer structure (that you get in your netfilter hook function). Instead, .transport_header points to IP header (which is network layer header).

So you need to calculate address of transport header by your own. To do so you need to skip IP header (i.e. add IP header length to your .transport_header address). That's why you can see next code in the article:

udp_header = (struct udphdr *)(skb_transport_header(skb) + 20);

So 20 here is just the length of IP header.

It can be done more elegant in this way:

struct iphdr *iph;
struct udphdr *udph;

iph = ip_hdr(skb);

/* If transport header is not set for this kernel version */
if (skb_transport_header(skb) == (unsigned char *)iph)
udph = (unsigned char *)iph + (iph->ihl * 4); /* skip IP header */
else
udph = udp_hdr(skb);

In this code we use an actual IP header size (which is iph->ihl * 4, in bytes) instead of magic number 20.

Another magic number in the article is 17 in next code:

if (ip_header->protocol == 17) {

In this code you should use IPPROTO_UDP instead of 17:

#include <linux/udp.h>

if (ip_header->protocol == IPPROTO_UDP) {

Netfilter input/output packets explanation

If you need some reference about difference between incoming and outgoing packets in netfilter, see the picture below.

netfilter-hooks

Details:

[1]: Some useful code from GitHub

[2]: "Linux Kernel Networking: Implementation and Theory" by Rami Rosen

[3]: This answer may be also useful

Send a raw ethernet packet from inside a kernel module

Take a look at the function packet_sendmsg_spkt in net/packet/af_packet.c for inspiration. The hard part is getting a struct sock if you don't have a socket...

Edit: Added a basic code shell:

int sendpacket(struct socket *sock, struct net_device *dev, __be16 proto, void *data, size_t len)
{
struct sock *sk = sock->sk;
struct sk_buff *skb;

if (!(dev->flags & IFF_UP))
return -ENETDOWN;

if (len > dev->mtu + dev->hard_header_len)
return -EMSGSIZE;

skb = sock_wmalloc(sk, len + LL_RESERVED_SPACE(dev), 0, GFP_KERNEL);

if (skb == NULL)
return -ENOBUFS;

/* FIXME: Save some space for broken drivers that write a
* hard header at transmission time by themselves. PPP is the
* notable one here. This should really be fixed at the driver level.
*/
skb_reserve(skb, LL_RESERVED_SPACE(dev));
skb_reset_network_header(skb);

/* Try to align data part correctly */
if (dev->header_ops) {
skb->data -= dev->hard_header_len;
skb->tail -= dev->hard_header_len;
if (len < dev->hard_header_len)
skb_reset_network_header(skb);
}

memcpy(skb_put(skb, len), data, len);
skb->protocol = proto;
skb->dev = dev;
skb->priority = sk->sk_priority;

dev_queue_xmit(skb);

return len;
}

How to intercept packet on TCP layer in kernel to analyze data?

If you want to receive only specific stuff and drop not matching stuff, could you open a raw/tcp socket with a BFP filter attached to it ?

Ex - https://www.kernel.org/doc/Documentation/networking/filter.txt



Related Topics



Leave a reply



Submit