I've been trying to teach myself a bit about kernel internals by
writing (yet another) VPN module.
Basically, I used new_tunnel.c & dummy.c as examples. My first test
was to simply create a network device that forwarded packets; no
tunneling or anything. It just takes the skb that is passed,
determines the route via ip_rt_route and forwards it via ip_forward.
Everything has worked well except for one thing. After loading my
module, ifconfig'ing the device and setting some routes, I try to ping
a machine through this device. A few packets go out (and come back),
but then ping causes a general protect fault (which I can't trace
because klogd isn't working for me and I don't have C++ installed to
compile ksymoops) [they are on my list of things to do:)]
I would like it if someone could look over my code (it isn't big) and
tell me what I'm doing wrong. I also have a few questions if youre in
the mind to answer them:
1. What does ip_rt_put do?
2. In new_tunnel.c, ip_rt_route is called twice, with a ip_rt_put in
between. Why? No doubt it has to do with #1.
3. Whats the difference between dev_kfree_skb & kfree_skb? Is
kfree_skb for skb_buff's that you create yourself? And dev_* is for
system created ones?
Well, I guess that's about it for now. This is VERY much uncharted
territory for me, so be nice :)
[ module.c ]
#define __KERNEL__
#define MODULE
#include <linux/module.h> /* can't do without it */
#include <linux/version.h> /* and this too */
#include <linux/types.h> /* ulong and friends */
#include <linux/sched.h> /* current, task_struct, other goodies */
#include <linux/fcntl.h> /* O_NONBLOCK etc. */
#include <linux/errno.h> /* return values */
#include <linux/config.h>
#include <linux/malloc.h>
#include "module.h"
static char * version="VPN driver pre1.0, (c) Nicholas J. Leon";
/******************************************************************************/
static char * szhost(__u32 ipaddr) {
char sz[256];
unsigned char *ip;
ip=(unsigned char*)&ipaddr;
sprintf(sz,"%u.%u.%u.%u",*ip,*(ip+1),*(ip+2),*(ip+3));
return sz;
}
void print_ip(struct iphdr *ip) {
unsigned char *ipaddr;
printk(KERN_DEBUG "IP packet:\n");
printk(KERN_DEBUG "--- header len = %d\n", ip->ihl*4);
printk(KERN_DEBUG "--- ip version: %d\n", ip->version);
printk(KERN_DEBUG "--- ip protocol: %d\n", ip->protocol);
ipaddr=(unsigned char *)&ip->saddr;
printk(KERN_DEBUG "--- source address: %s\n",szhost(ip->saddr));
// printk(KERN_DEBUG "--- source address: %u.%u.%u.%u\n",
// *ipaddr, *(ipaddr+1), *(ipaddr+2), *(ipaddr+3));
ipaddr=(unsigned char *)&ip->daddr;
printk(KERN_DEBUG "--- destination address: %u.%u.%u.%u\n",
*ipaddr, *(ipaddr+1), *(ipaddr+2), *(ipaddr+3));
// printk(KERN_DEBUG "--- total packet len: %d\n", ntohs(ip->tot_len));
}
int vpn_open(struct device *dev) {
MOD_INC_USE_COUNT;
return 0;
}
int vpn_close(struct device *dev) {
MOD_DEC_USE_COUNT;
return 0;
}
static int vpn_xmit(struct sk_buff *skb,struct device *dev) {
struct enet_statistics *stats=NULL;
struct iphdr * iph;
struct rtable *rt;
__u32 target;
if (skb==NULL||dev==NULL) {
printk(KERN_DEBUG "nothing to do!\n");
return 0;
}
// make sure we're not busy
cli();
if (dev->tbusy!=0) {
sti();
stats->tx_errors++;
return 1;
}
dev->tbusy=1;
sti();
iph=(struct iphdr *)skb->data;
stats=(struct enet_statistics *)dev->priv;
print_ip(iph);
if ((rt=ip_rt_route(iph->daddr,0,skb->sk?skb->sk->bound_device:NULL))==NULL) {
// no route
stats->tx_errors++;
dev->tbusy=0;
dev_kfree_skb(skb,FREE_WRITE);
return 0;
}
if (rt->rt_flags & RTF_GATEWAY) {
printk(KERN_DEBUG "RTF_GATEWAY\n");
target=rt->rt_gateway;
}
else {
printk(KERN_DEBUG "Not gateway\n");
target=dev->pa_dstaddr;
}
printk(KERN_DEBUG "target is %s\n",szhost(target));
ip_rt_put(rt);
if (sysctl_ip_forward) {
if (ip_forward(skb,dev,IPFWD_NOTTLDEC,target)) {
dev_kfree_skb(skb,FREE_WRITE);
}
}
else {
dev_kfree_skb(skb,FREE_WRITE);
}
// dev_kfree_skb(skb,FREE_WRITE);
stats->tx_packets++;
dev->tbusy=0;
return 0;
}
struct enet_statistics * vpn_get_stats(struct device *dev) {
struct enet_statistics *stats=(struct enet_statistics*)dev->priv;
return stats;
}
int vpn_init(struct device *dev) {
int i;
dev->hard_start_xmit=vpn_xmit;
dev->open=vpn_open;
dev->stop=vpn_close;
dev->get_stats=vpn_get_stats;
dev->flags|=IFF_NOARP;
dev->type=ARPHRD_TUNNEL;
dev->hard_header_len=(sizeof(struct iphdr)+10);
dev->mtu=1500;
dev->addr_len=0;
dev->tx_queue_len=2;
memset(dev->broadcast,0xFF,ETH_ALEN);
dev->family=AF_INET;
dev->pa_alen=4;
for (i=0;i<DEV_NUMBUFFS;i++)
skb_queue_head_init(&dev->buffs[i]);
dev->priv=kmalloc(sizeof(struct enet_statistics),GFP_KERNEL);
if (dev->priv==NULL) {
printk(KERN_ERR "Couldn't allocate memory");
return -ENOMEM;
}
memset(dev->priv,0,sizeof(struct enet_statistics));
return 0;
}
/******************************************************************************/
static char *device=NULL;
static struct_vpn *dvpn=NULL;
static int numdevices=1;
//static struct device *dvpn;
int init_module(void) {
int i=1;
char name[16];
struct_vpn *tvpn=dvpn;
printk(KERN_DEBUG "%s, %d devices\n",version,numdevices);
while (numdevices--) {
// first device if tvpn==NULL
if (tvpn==NULL) {
tvpn=(struct_vpn *)kmalloc(sizeof(struct_vpn),GFP_KERNEL);
if (tvpn==NULL) {
printk(KERN_ERR "Couldn't allocate space for tvpn\n");
return -ENOMEM;
}
// make our global point to the first one in the chain
dvpn=tvpn;
}
else {
// second or greater device
tvpn->next=(struct_vpn *)kmalloc(sizeof(struct_vpn),GFP_KERNEL);
if (tvpn->next==NULL) {
printk(KERN_ERR "Couldn't allocate space for tvpn\n");
return -ENOMEM;
}
tvpn=tvpn->next;
}
// tvpn has our new, blank, record
memset(tvpn,0,sizeof(struct_vpn));
tvpn->dev=kmalloc(sizeof(struct device),GFP_KERNEL);
if (tvpn->dev==NULL) {
printk(KERN_ERR "Couldn't allocate space for dev\n");
return -ENOMEM;
}
memset(tvpn->dev,0,sizeof(struct device));
if (device)
strcpy(name,device);
else
strcpy(name,"vpn0");
while (dev_get(name)!=NULL) {
sprintf(name,"vpn%d",i);
i++;
}
tvpn->dev->name=(char *)kmalloc(sizeof(name),GFP_KERNEL);
strcpy(tvpn->dev->name,name);
tvpn->dev->init=vpn_init;
printk(KERN_DEBUG "Creating device %s\n",tvpn->dev->name);
if (register_netdev(tvpn->dev)!=0)
return -EIO;
}
return 0;
}
int cleanup_module(void) {
struct_vpn *tmp;
while (dvpn) {
printk(KERN_DEBUG "Removing device %s\n",dvpn->dev->name);
unregister_netdev(dvpn->dev);
kfree(dvpn->dev->name);
kfree(dvpn->dev);
tmp=dvpn->next;
kfree(dvpn);
dvpn=tmp;
}
return 0;
}
[ module.h ]
#ifndef __MODULE_H
#define __MODULE_H
#define __KERNEL__
#define MODULE
#include <linux/netdevice.h>
#include <net/ip.h>
#include <linux/if.h>
#include <linux/if_arp.h>
#include <linux/route.h>
typedef struct struct_vpn {
struct device *dev;
struct struct_vpn * next;
} struct_vpn;
#endif
------------------------------------------------------------------------------
Nicholas J. Leon <nicholas@binary9.net>
"Elegance through Simplicity" http://mrnick.binary9.net/
PGP:finger nicholas@neko.binary9.net Shinanyaku:[Chronx/War2/XvT]
------------------------------------------------------------------------------
[ -- Powered by a Linux/GNU System -- ]
-- Please ignore the following addresses, they are intended to determine and catch bulk emailers that scan newsgroups and mailing lists.>> linux-kernel_vger.rutgers.edu@catcher.binary9.net