Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
/*
 *	Ethertap: A network device for bouncing packets via user space
 *
 *	This is a very simple ethernet driver. It bounces ethernet frames
 *	to user space on /dev/tap0->/dev/tap15 and expects ethernet frames
 *	to be written back to it. By default it does not ARP. If you turn ARP
 *	on it will attempt to ARP the user space and reply to ARPS from the
 *	user space.
 *
 *	As this is an ethernet device you can use it for appletalk, IPX etc
 *	even for building bridging tunnels.
 */
 
#include <linux/config.h>
#include <linux/module.h>

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/malloc.h>
#include <linux/string.h>
#include <linux/errno.h>

#include <linux/netdevice.h>
#include <linux/inetdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/init.h>

#include <net/sock.h>
#include <linux/netlink.h>

/*
 *	Index to functions.
 */

int	    ethertap_probe(struct net_device *dev);
static int  ethertap_open(struct net_device *dev);
static int  ethertap_start_xmit(struct sk_buff *skb, struct net_device *dev);
static int  ethertap_close(struct net_device *dev);
static struct net_device_stats *ethertap_get_stats(struct net_device *dev);
static void ethertap_rx(struct sock *sk, int len);
#ifdef CONFIG_ETHERTAP_MC
static void set_multicast_list(struct net_device *dev);
#endif

static int ethertap_debug = 0;

static struct net_device *tap_map[32];	/* Returns the tap device for a given netlink */

/*
 *	Board-specific info in dev->priv.
 */

struct net_local
{
	struct sock	*nl;
#ifdef CONFIG_ETHERTAP_MC
	__u32		groups;
#endif
	struct net_device_stats stats;
};

/*
 *	To call this a probe is a bit misleading, however for real
 *	hardware it would have to check what was present.
 */
 
int __init ethertap_probe(struct net_device *dev)
{
	memcpy(dev->dev_addr, "\xFE\xFD\x00\x00\x00\x00", 6);
	if (dev->mem_start & 0xf)
		ethertap_debug = dev->mem_start & 0x7;

	/*
	 *	Initialize the device structure.
	 */

	dev->priv = kmalloc(sizeof(struct net_local), GFP_KERNEL);
	if (dev->priv == NULL)
		return -ENOMEM;
	memset(dev->priv, 0, sizeof(struct net_local));

	/*
	 *	The tap specific entries in the device structure.
	 */

	dev->open = ethertap_open;
	dev->hard_start_xmit = ethertap_start_xmit;
	dev->stop = ethertap_close;
	dev->get_stats = ethertap_get_stats;
#ifdef CONFIG_ETHERTAP_MC
	dev->set_multicast_list = set_multicast_list;
#endif

	/*
	 *	Setup the generic properties
	 */

	ether_setup(dev);

	dev->tx_queue_len = 0;
	dev->flags|=IFF_NOARP;
	tap_map[dev->base_addr]=dev;

	return 0;
}

/*
 *	Open/initialize the board.
 */

static int ethertap_open(struct net_device *dev)
{
	struct net_local *lp = (struct net_local*)dev->priv;

	if (ethertap_debug > 2)
		printk("%s: Doing ethertap_open()...", dev->name);

	MOD_INC_USE_COUNT;

	lp->nl = netlink_kernel_create(dev->base_addr, ethertap_rx);
	if (lp->nl == NULL) {
		MOD_DEC_USE_COUNT;
		return -ENOBUFS;
	}
	netif_start_queue(dev);
	return 0;
}

#ifdef CONFIG_ETHERTAP_MC
static unsigned ethertap_mc_hash(__u8 *dest)
{
	unsigned idx = 0;
	idx ^= dest[0];
	idx ^= dest[1];
	idx ^= dest[2];
	idx ^= dest[3];
	idx ^= dest[4];
	idx ^= dest[5];
	return 1U << (idx&0x1F);
}

static void set_multicast_list(struct net_device *dev)
{
	unsigned groups = ~0;
	struct net_local *lp = (struct net_local *)dev->priv;

	if (!(dev->flags&(IFF_NOARP|IFF_PROMISC|IFF_ALLMULTI))) {
		struct dev_mc_list *dmi;

		groups = ethertap_mc_hash(dev->broadcast);

		for (dmi=dev->mc_list; dmi; dmi=dmi->next) {
			if (dmi->dmi_addrlen != 6)
				continue;
			groups |= ethertap_mc_hash(dmi->dmi_addr);
		}
	}
	lp->groups = groups;
	if (lp->nl)
		lp->nl->protinfo.af_netlink.groups = groups;
}
#endif

/*
 *	We transmit by throwing the packet at netlink. We have to clone
 *	it for 2.0 so that we dev_kfree_skb() the locked original.
 */
 
static int ethertap_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
	struct net_local *lp = (struct net_local *)dev->priv;
#ifdef CONFIG_ETHERTAP_MC
	struct ethhdr *eth = (struct ethhdr*)skb->data;
#endif

	if (skb_headroom(skb) < 2) {
		static int once;
	  	struct sk_buff *skb2;

		if (!once) {
			once = 1;
			printk(KERN_DEBUG "%s: not aligned xmit by protocol %04x\n", dev->name, skb->protocol);
		}

		skb2 = skb_realloc_headroom(skb, 2);
		dev_kfree_skb(skb);
		if (skb2 == NULL)
			return 0;
		skb = skb2;
	}
	__skb_push(skb, 2);

	/* Make the same thing, which loopback does. */
	if (skb_shared(skb)) {
	  	struct sk_buff *skb2 = skb;
	  	skb = skb_clone(skb, GFP_ATOMIC);	/* Clone the buffer */
	  	if (skb==NULL) {
			dev_kfree_skb(skb2);
			return 0;
		}
	  	dev_kfree_skb(skb2);
	}
	/* ... but do not orphan it here, netlink does it in any case. */

	lp->stats.tx_bytes+=skb->len;
	lp->stats.tx_packets++;

#ifndef CONFIG_ETHERTAP_MC
	netlink_broadcast(lp->nl, skb, 0, ~0, GFP_ATOMIC);
#else
	if (dev->flags&IFF_NOARP) {
		netlink_broadcast(lp->nl, skb, 0, ~0, GFP_ATOMIC);
		return 0;
	}

	if (!(eth->h_dest[0]&1)) {
		/* Unicast packet */
		__u32 pid;
		memcpy(&pid, eth->h_dest+2, 4);
		netlink_unicast(lp->nl, skb, ntohl(pid), MSG_DONTWAIT);
	} else
		netlink_broadcast(lp->nl, skb, 0, ethertap_mc_hash(eth->h_dest), GFP_ATOMIC);
#endif
	return 0;
}

static __inline__ int ethertap_rx_skb(struct sk_buff *skb, struct net_device *dev)
{
	struct net_local *lp = (struct net_local *)dev->priv;
#ifdef CONFIG_ETHERTAP_MC
	struct ethhdr *eth = (struct ethhdr*)(skb->data + 2);
#endif
	int len = skb->len;

	if (len < 16) {
		printk(KERN_DEBUG "%s : rx len = %d\n", dev->name, len);
		kfree_skb(skb);
		return -EINVAL;
	}
	if (NETLINK_CREDS(skb)->uid) {
		printk(KERN_INFO "%s : user %d\n", dev->name, NETLINK_CREDS(skb)->uid);
		kfree_skb(skb);
		return -EPERM;
	}

#ifdef CONFIG_ETHERTAP_MC
	if (!(dev->flags&(IFF_NOARP|IFF_PROMISC))) {
		int drop = 0;

		if (eth->h_dest[0]&1) {
			if (!(ethertap_mc_hash(eth->h_dest)&lp->groups))
				drop = 1;
		} else if (memcmp(eth->h_dest, dev->dev_addr, 6) != 0)
			drop = 1;

		if (drop) {
			if (ethertap_debug > 3)
				printk(KERN_DEBUG "%s : not for us\n", dev->name);
			kfree_skb(skb);
			return -EINVAL;
		}
	}
#endif

	if (skb_shared(skb)) {
	  	struct sk_buff *skb2 = skb;
	  	skb = skb_clone(skb, GFP_KERNEL);	/* Clone the buffer */
	  	if (skb==NULL) {
			kfree_skb(skb2);
			return -ENOBUFS;
		}
	  	kfree_skb(skb2);
	} else
		skb_orphan(skb);

	skb_pull(skb, 2);
	skb->dev = dev;
	skb->protocol=eth_type_trans(skb,dev);
	memset(skb->cb, 0, sizeof(skb->cb));
	lp->stats.rx_packets++;
	lp->stats.rx_bytes+=len;
	netif_rx(skb);
	return len;
}

/*
 *	The typical workload of the driver:
 *	Handle the ether interface interrupts.
 *
 *	(In this case handle the packets posted from user space..)
 */

static void ethertap_rx(struct sock *sk, int len)
{
	struct net_device *dev = tap_map[sk->protocol];
	struct sk_buff *skb;

	if (dev==NULL) {
		printk(KERN_CRIT "ethertap: bad unit!\n");
		skb_queue_purge(&sk->receive_queue);
		return;
	}

	if (ethertap_debug > 3)
		printk("%s: ethertap_rx()\n", dev->name);

	while ((skb = skb_dequeue(&sk->receive_queue)) != NULL)
		ethertap_rx_skb(skb, dev);
}

static int ethertap_close(struct net_device *dev)
{
	struct net_local *lp = (struct net_local *)dev->priv;
	struct sock *sk = lp->nl;

	if (ethertap_debug > 2)
		printk("%s: Shutting down.\n", dev->name);

	netif_stop_queue(dev);

	if (sk) {
		lp->nl = NULL;
		sock_release(sk->socket);
	}

	MOD_DEC_USE_COUNT;
	return 0;
}

static struct net_device_stats *ethertap_get_stats(struct net_device *dev)
{
	struct net_local *lp = (struct net_local *)dev->priv;
	return &lp->stats;
}

#ifdef MODULE

static int unit;
MODULE_PARM(unit,"i");

static char devicename[9] = { 0, };

static struct net_device dev_ethertap =
{
	devicename,
	0, 0, 0, 0,
	1, 5,
	0, 0, 0, NULL, ethertap_probe
};

int init_module(void)
{
	dev_ethertap.base_addr=unit+NETLINK_TAPBASE;
	sprintf(devicename,"tap%d",unit);
	if (dev_get(devicename))
	{
		printk(KERN_INFO "%s already loaded.\n", devicename);
		return -EBUSY;
	}
	if (register_netdev(&dev_ethertap) != 0)
		return -EIO;
	return 0;
}

void cleanup_module(void)
{
	tap_map[dev_ethertap.base_addr]=NULL;
	unregister_netdev(&dev_ethertap);

	/*
	 *	Free up the private structure.
	 */

	kfree(dev_ethertap.priv);
	dev_ethertap.priv = NULL;	/* gets re-allocated by ethertap_probe */
}

#endif /* MODULE */