Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to echo a packet in kernel space using netfilter hooks?

I want to echo a packet in kernel space. I run an echo server on this machine with port 6000. Now a client runs on another machine sending data to the echo server. Now, what I want to do is to echo the packet back from the kernel space. I do not want to bother the server with the packet, and it will be echoed silently from the kernel space. I am presenting my codes below:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/netfilter.h>
#include <linux/netdevice.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/mm.h>
#include <linux/err.h>
#include <linux/crypto.h>
#include <linux/init.h>
#include <linux/crypto.h>
#include <linux/scatterlist.h>
#include <net/ip.h>
#include <net/udp.h>
#include <net/route.h>
#include <net/checksum.h>
#include <linux/netfilter_ipv4.h>

#define IP_HDR_LEN 20
#define UDP_HDR_LEN 8
#define TOT_HDR_LEN 28

static unsigned int pkt_echo_begin(unsigned int hooknum,
                        struct sk_buff *skb,
                        const struct net_device *in,
                        const struct net_device *out,
                        int (*okfn)(struct sk_buff *));

static struct nf_hook_ops pkt_echo_ops __read_mostly = {
    .pf = NFPROTO_IPV4,
    .priority = 1,
    .hooknum = NF_INET_PRE_ROUTING,
    .hook = pkt_echo_begin,
};

static int __init pkt_echo_init(void)
{
    printk(KERN_ALERT "\npkt_echo module started ...");
    return nf_register_hook(&pkt_echo_ops);
}

static void __exit pkt_echo_exit(void)
{
    nf_unregister_hook(&pkt_echo_ops);
    printk(KERN_ALERT "pkt_echo module stopped ...");
}

static unsigned int pkt_echo_begin (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 udphdr *udph;
    unsigned char *data;

    unsigned char *temp;

    __u16 dst_port, src_port;
    int data_len;



    if (skb) {
        iph = (struct iphdr *) skb_header_pointer (skb, 0, 0, NULL);

        if (iph && iph->protocol &&(iph->protocol == IPPROTO_UDP)) {
            udph = (struct udphdr *) skb_header_pointer (skb, IP_HDR_LEN, 0, NULL);
            src_port = ntohs (udph->source);
            dst_port = ntohs (udph->dest);

            if (dst_port == 6000) {
                printk(KERN_ALERT "\nUDP packet goes in");

                data = (unsigned char *) skb_header_pointer (skb, TOT_HDR_LEN, 0, NULL);
                data_len = skb->len - TOT_HDR_LEN;

                struct sk_buff *newskb;
                struct iphdr *newiph;
                struct udphdr *newudph;
                unsigned char *newdata;
                unsigned int newdata_len;

                newskb = skb_copy(skb, GFP_ATOMIC);
                newiph = (struct iphdr *) skb_header_pointer (newskb, 0, 0, NULL);
                newudph = (struct udphdr *) skb_header_pointer (newskb, IP_HDR_LEN, 0, NULL);
                newdata = (unsigned char *) skb_header_pointer (newskb, TOT_HDR_LEN, 0, NULL);

                newiph->saddr = iph->daddr;
                newiph->daddr = iph->saddr;

                newudph->source = udph->dest;
                newudph->dest = udph->source;

                struct sk_buff *tempskb;

                tempskb = skb_copy(skb, GFP_ATOMIC);

                *tempskb = *skb;
                *skb = *newskb;
                *newskb = *tempskb;

                kfree_skb(newskb);

            }
        }
    }
    return NF_ACCEPT;
}


module_init(pkt_echo_init);
module_exit(pkt_echo_exit);

MODULE_AUTHOR("Rifat Rahman Ovi: <[email protected]>");
MODULE_DESCRIPTION("Echoing a packet from kernel space.");
MODULE_LICENSE("GPL");

Now I do not understand what is needed. I captured the packet in the PRE_ROUTING hook. Then I created a new skb, populated it from the old received skb, and then altered the source address (saddr), destination address (daddr), source port (source), and destination port (dest), so that the packet can be forwarded from the PRE_ROUTING hook. As routing decisions are made after the packet passes from the hook, I look for the packet to be forwarded to its originating source. But for some reason it is not doing like that. The packet is received, and everything is altered, but the packet does not seem to move backwards. I do not understand what is missing, and also what is needed to make it work. More specifically, what is needed to send a packet to the network from a PRE_ROUTING hook?

like image 445
rr_ovi Avatar asked Oct 25 '12 14:10

rr_ovi


People also ask

Can netfilter be used to modify packets?

3.1 Netfilter Base A module that registers a function must specify the priority of the function within the hook; then when that netfilter hook is called from the core networking code, each module registered at that point is called in the order of priorites, and is free to manipulate the packet.

What is netfilter in Linux kernel?

Netfilter is a framework provided by the Linux kernel that allows various networking-related operations to be implemented in the form of customized handlers.

What is hooks in netfilter?

hook — A pointer to a function that is called as soon as the hook is triggered. This function is from type nf_hookfn which has different signatures in different versions of the kernel. I recommend you to search for the right signature according to the kernel version you work on (see reference [2]).

How many hooks does netfilter have for IPV4?

There are 5 hook types defined for the IPV4 protocol. It is common for a packet to be processed by multiple hooks.


2 Answers

A lot is missing.

First of all, the netfilter hook you used is PRE-ROUTING which captures INCOMING packets and so unless you use some kernel function to transmit the packet you've built, return NF_ACCEPT will only let the packet you altered(or didn't) continue on its way (which is TO the local system, not from it).
Read about functions like dev_queue_xmit(struct sk_buff *) but notice that before using this function, your SKB has to have the Link-layer header because this function actually queues your packet in a queue to be sent right away to the NIC and it's your job to set the Link layer addresses.

Second, remember that after you alter the IP-header addresses you have to re-calculate the checksum of the packet or else your packet will be discarded at the other end.

Third, notice that doing what you're trying to do in kernel space is largely considered a VERY bad practice. Kernel modules exist for a reason and this is not one of them, netfilter is a great tool, but it's not really supposed to be used for sending normal traffic.

EDIT:

Reading your latest comment; I'd suggest you read about libPCap library, it should serve you very well and still keep your work in its right place, the user-space.

like image 111
Fingolfin Avatar answered Oct 22 '22 11:10

Fingolfin


In addition to the previous answer, another technique that can be used to echo an UDP packet from the netfilter callback is:

send the packet back, as a new UDP packet, using:

int sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)

in net/socket.c

or also using the netpoll library, as said in the answer here.

The original packet can than be dropped using NF_DROP.

In the netfilter callback, that runs in an "interrupt context" is possible to send packets, but is not possible to receive them (since every attempt to wait causes a kernel panic). For this reason the solution I proposed works with UDP, but cannot work with TCP (the TCP handshake requires that the ACK message must be received).

Anyway, as already said, doing this kind of things in kernel space is BAD and should be used only for learning purposes.

like image 37
Amedeo Avatar answered Oct 22 '22 12:10

Amedeo