Project Title: Pseudo Network Device Driver:
Work Description-
Application Part:
We have written Socket program for Server and Client. In Server Socket program Code we have assigned the IP address of the LOOPBACK INTERFACE (lo) to the Server and the same address is assigned to the Client ,so that client can send request message or data to the server through that IP address and because of the design of loopback interface we were able to receive the request message from Client at the Server terminal and we had also send the message from the Server to the Client . And we got the results as expected.
Driver Part:
We have written the code for the Pseudo Network Device Driver with reference to the code in Text Book in Linux Device Driver (snull.c) i.e , here we have created two interfaces Pseudo0 and Pseudo1 which will act as a “hidden loopback” device .We are receiving data from userspace test code(client.c) in Pseudo0 ,which will transfer the data to pseudo1 and back to userspace (server.c ).
We have made entry in /etc/networks as Pseudo0 192.168.0.0 Pseudo1 192.168.1.0
And we also made an entry in /etc/hosts as, 192.168.0.1 local0 192.168.0.2 remote0
192.168.1.2 Local1 192.168.1.1 remote1
And we have set up the interface using the command ifconfig pseudo0 local 0 ifconfig pseudo1 local 1
We have also used ifconfig pseudo0 up local0 ping -I pseudo0 local host
and also tried to test with socket program for Server and Client .We have assigned IP address of Pseudo0 to the Client and IP address of Pseudo1 to the Server.

Problems:
1.When we are trying to ping between local0 and remote1 (host for local0), we are unable to connect to the remote1,and we are not receiving packets(Destination host unreachable) from remote1
2.after we enter the command “ifconfig pseudo0 up” the IP address is assigned to interface and the interface start transmitting and receiving packets, while testing with socket program, server & client are not responding to each other.
.Please suggest us few testing techniques.

Client Code:-
#include<stdio.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<string.h>
#include<stdlib.h>
int main() {
/* Host and port number of the echo server */
char* echo_host = "192.168.3.197";
int echo_port = 7;
int sockfd;
struct sockaddr_in *server= (struct sockaddr_in*)malloc(sizeof(struct sockaddr_in));

/* Set address of server to be connected */
server->sin_family = AF_INET;
server->sin_port = htons(echo_port); // Note network byte order!
server->sin_addr.s_addr = inet_addr(echo_host);

/* Create a socket (Internet address family, stream socket and
default protocol) */
sockfd = socket(AF_INET, SOCK_STREAM, 0);

/* Connect to server */
printf("Connecting to %s \n", echo_host);
printf("Numeric: %u\n", server->sin_addr);
connect(sockfd, (struct sockaddr*)server, sizeof(*server));

/* Send message */
char* msg = "Hello World";
printf("\nSend: ’%s’\n", msg);
write(sockfd, msg, strlen(msg));
/* ... and receive result */
char* buf = (char*)malloc(1000); // Receive buffer for max. 1000 chars
int bytes = read(sockfd, (void*)buf, 1000);
printf("\nBytes received: %u\n", bytes);
printf("Text: ’%s’\n", buf);

/* End communication (i.e. close socket) */
close(sockfd);
}


Server Code:
#include<stdio.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<string.h>
#include<stdlib.h>
int main()
{
char* echo_host = "192.168.3.197";
int echo_port = 7;
int sockfd;
struct sockaddr_in *server=
(struct sockaddr_in*)malloc(sizeof(struct sockaddr_in));

/* Set own address */
server->sin_family = AF_INET;
server->sin_port = htons(echo_port); // Note network byte order!
server->sin_addr.s_addr = inet_addr(echo_host);

/* Create a socket */
sockfd = socket(AF_INET, SOCK_STREAM, 0);

/* Bind to an address */
if (bind(sockfd, (struct sockaddr*)server, sizeof(*server)))
{
printf("bind failed\n");
}

/* Enable server mode of socket */
listen(sockfd, SOMAXCONN);
/* ...and wait for incoming data */
int clientfd;
struct sockaddr_in* client =
(struct sockaddr_in*)malloc(sizeof(struct sockaddr_in));

int client_size = sizeof(*client);
char* buf = (char*)malloc(1000);
int bytes;
printf("Wait for connection to port %u\n", echo_port);

/* Accept a connection request */
clientfd = accept(sockfd, (struct sockaddr*)client, &client_size);
printf("Connected to %s:%u\n\n", inet_ntoa(client->sin_addr),
ntohs(client->sin_port));
printf("Numeric: %u\n", ntohl(client->sin_addr.s_addr));
while(1) { /* Endless loop */
/* Receive transmitted data */
bytes = read(clientfd, (void*)buf, 1000);
if (bytes <= 0) {
close(clientfd);
printf("Connection closed.\n");
exit(0);
}
printf("Bytes received: %u\n", bytes);
printf("Text: ’%s’\n", buf);
/* Send response */
write(clientfd, buf, bytes);
}
}


Driver Code:-
#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>

#include <linux/sched.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/interrupt.h> /* mark_bh */

#include <linux/in.h>
#include <linux/netdevice.h> /* struct device, and other headers */
#include <linux/etherdevice.h> /* eth_type_trans */
#include <linux/ip.h> /* struct iphdr */
#include <linux/tcp.h> /* struct tcphdr */
#include <linux/skbuff.h>

#include "snull.h"

#include <linux/in6.h>
#include <asm/checksum.h>

MODULE_AUTHOR("Pseudo Network device driver");
MODULE_LICENSE("Dual BSD/GPL");

static int lockup = 0;
module_param(lockup, int, 0);

static int timeout = SNULL_TIMEOUT;
module_param(timeout, int, 0);




struct snull_packet {
struct snull_packet *next;
struct net_device *dev;
int datalen;
u8 data[ETH_DATA_LEN];
};


int pool_size = 8;
module_param(pool_size, int, 0);

struct snull_priv {
struct net_device_stats stats;
int status;
struct snull_packet *ppool;
struct snull_packet *rx_queue; /* List of incoming packets */
int rx_int_enabled;
int tx_packetlen;
u8 *tx_packetdata;
struct sk_buff *skb;
spinlock_t lock;
};

void snull_tx_timeout(struct net_device *dev);
static void (*snull_interrupt)(int, void *, struct pt_regs *);

//class :here we are allocating a pool of tx/rx packets needed for
// the transmission/reception of packets - used to store packets
// on the receiving interface
//
void snull_setup_pool(struct net_device *dev)
{
struct snull_priv *priv = netdev_priv(dev);
int i;
struct snull_packet *pkt;

printk("IN SETUP POOL\n");

priv->ppool = NULL;
for (i = 0; i < pool_size; i++) {
pkt = kmalloc (sizeof (struct snull_packet), GFP_KERNEL);
if (pkt == NULL) {
printk (KERN_NOTICE "Ran out of memory allocating packet pool\n");
return;
}
pkt->dev = dev;
pkt->next = priv->ppool;
priv->ppool = pkt;
}
printk("EXIT SETUP POOL\n");
}

void snull_teardown_pool(struct net_device *dev)
{
struct snull_priv *priv = netdev_priv(dev);
struct snull_packet *pkt;
printk("IN teardown POOL\n");

while ((pkt = priv->ppool)) {
priv->ppool = pkt->next;
kfree (pkt);
}
}


//when we are getting a new packet from the network layer, allocating
//a new packet and copying it - may be sent out to someone else
/*
* Buffer/pool management.
*/
struct snull_packet *snull_get_tx_buffer(struct net_device *dev)
{
struct snull_priv *priv = netdev_priv(dev);
unsigned long flags;
struct snull_packet *pkt;


printk("In get tx buffer\n");
spin_lock_irqsave(&priv->lock, flags);
pkt = priv->ppool;
priv->ppool = pkt->next;
if (priv->ppool == NULL) {
printk (KERN_INFO "Pool empty\n");
netif_stop_queue(dev);
}
spin_unlock_irqrestore(&priv->lock, flags);
printk("exit get tx buffer\n");

return pkt;
}


void snull_release_buffer(struct snull_packet *pkt)
{
unsigned long flags;
struct snull_priv *priv = netdev_priv(pkt->dev);

printk("in release buffer\n");

spin_lock_irqsave(&priv->lock, flags);
pkt->next = priv->ppool;
priv->ppool = pkt;
spin_unlock_irqrestore(&priv->lock, flags);
if (netif_queue_stopped(pkt->dev) && pkt->next == NULL)
netif_wake_queue(pkt->dev);
}

//
//class - low-level packet handling in the pseudo-device
//
void snull_enqueue_buf(struct net_device *dev, struct snull_packet *pkt)
{
unsigned long flags;
struct snull_priv *priv = netdev_priv(dev);
printk("in enqueue buffer\n");

spin_lock_irqsave(&priv->lock, flags);
pkt->next = priv->rx_queue; /* FIXME - misorders packets */
priv->rx_queue = pkt;
spin_unlock_irqrestore(&priv->lock, flags);
}

struct snull_packet *snull_dequeue_buf(struct net_device *dev)
{
struct snull_priv *priv = netdev_priv(dev);
struct snull_packet *pkt;
unsigned long flags;
printk("in dequeue buffer\n");

spin_lock_irqsave(&priv->lock, flags);
pkt = priv->rx_queue;
if (pkt != NULL)
priv->rx_queue = pkt->next;
spin_unlock_irqrestore(&priv->lock, flags);
return pkt;
}

//
//
//
//class : simulating interrupt enabling / disabling in the driver-layer,
// not device controller
//
//
/*
* Enable and disable receive interrupts.
*/
static void snull_rx_ints(struct net_device *dev, int enable)
{
struct snull_priv *priv = netdev_priv(dev);
priv->rx_int_enabled = enable;
}


/*
* Open and close
*/
//
//
//class - typically, invoked using ifconfig command(up) - in turn,
// ifconfig command uses ioctl() system calls to invoke
// network device open() - explore more, if needed
//
int snull_open(struct net_device *dev)
{
/* request_region(), request_irq(), .... (like fops->open) */

/*
* Assign the hardware address of the board: use "\0SNULx", where
* x is 0 or 1. The first byte is '\0' to avoid being a multicast
* address (the first byte of multicast addrs is odd).
*/
printk("In open\n");
memcpy(dev->dev_addr, "\0SNUL0", ETH_ALEN);
if (dev == snull_devs[1])
dev->dev_addr[ETH_ALEN-1]++; /* \0SNUL1 */
netif_start_queue(dev);
return 0;
}

//
//class - typically, invoked using ifconfig command(down) - in turn,
// ifconfig command uses ioctl() system calls to invoke
// network device release() - explore mode, if needed
//
//
int snull_release(struct net_device *dev)
{
/* release ports, irq and such -- like fops->close */
//
//
//class - tell the kernel that we are unable to
// transmit any longer - we are shutting down
//
printk("In release\n");
netif_stop_queue(dev);
return 0;
}

int snull_config(struct net_device *dev, struct ifmap *map)
{
printk("IN SNULL CONFIG\n");

if (dev->flags & IFF_UP) /* can't act on a running interface */
return -EBUSY;

/* Don't allow changing the I/O address */
if (map->base_addr != dev->base_addr) {
printk(KERN_WARNING "snull: Can't change I/O address\n");
return -EOPNOTSUPP;
}

/* Allow changing the IRQ */
if (map->irq != dev->irq) {
dev->irq = map->irq;
/* request_irq() is delayed to open-time */
}

/* ignore other fields */
return 0;
}

/*
* Receive a packet: retrieve, encapsulate and pass over to upper levels
*/
void snull_rx(struct net_device *dev, struct snull_packet *pkt)
{
struct sk_buff *skb;
char *dat;
struct snull_priv *priv = netdev_priv(dev);

/*
* The packet has been retrieved from the transmission
* medium. Build an skb around it, so upper layers can handle it
*/

//
//class - allocate a socket buffer for reception of new packet
printk("destname=%s\n",dev->name);
printk("In rx\n");
dat = pkt->data;
printk("%s\n",dat);



skb = dev_alloc_skb(pkt->datalen + 2);
if (!skb) {
if (printk_ratelimit())
printk(KERN_NOTICE "snull rx: low on mem - packet dropped\n");
priv->stats.rx_dropped++;
goto out;
}
//
//class - 16 byte alignment is required by the system
// you may explore and find what is the benifit !!
//
// no where explicitly mentioned or visible
// still, follow this rule to avoid breaking the code
//
skb_reserve(skb, 2); /* align IP on 16B boundary */
memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);


//
//class - set the protocol
// actually, eth_type_trans() does more - explore
/* Write metadata, and then pass to the receive level */
skb->dev = dev;
skb->protocol = eth_type_trans(skb, dev);
//
//class - tell the kernel layer to ignore the checksum
//
skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
priv->stats.rx_packets++;
priv->stats.rx_bytes += pkt->datalen;


//
//class - passing data to the higher layers using skb
//
netif_rx(skb);
out:
return;
}


/*
* The typical interrupt entry point
*/

//
//class - regular interrupt handler - regular API mode - does not support
// NAPI (new API) mode
// check your h/w driver or similar h/w driver for a
// practical approach
//
//
//
static void snull_regular_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
int statusword;
struct snull_priv *priv;
struct snull_packet *pkt = NULL;
/*
* As usual, check the "device" pointer to be sure it is
* really interrupting.
* Then assign "struct device *dev"
*/
struct net_device *dev = (struct net_device *)dev_id;
/* ... and check with hw if it's really ours */
printk("In interrupt\n");

/* paranoid */
if (!dev)
return;

/* Lock the device */
priv = netdev_priv(dev);
spin_lock(&priv->lock);

/* retrieve statusword: real netdevices use I/O instructions */
statusword = priv->status;
priv->status = 0;
//
//class - verify it we really have an interrupt - if so,
// process it - pass the packet to higher layer in kernel
//
if (statusword & SNULL_RX_INTR)
{
printk("In RX interrupt\n");
/* send it to snull_rx for handling */
pkt = priv->rx_queue;
if (pkt) {
priv->rx_queue = pkt->next;
snull_rx(dev, pkt);
}
}
if (statusword & SNULL_TX_INTR) {
printk("In TX interrupt\n");
/* a transmission is over: free the skb */
priv->stats.tx_packets++;
priv->stats.tx_bytes += priv->tx_packetlen;
//
//class - kernel interface to free a socket buffer
//
dev_kfree_skb(priv->skb);
}

/* Unlock the device and we are done */
spin_unlock(&priv->lock);
//
//class - free the packet that we received from another device
// this may not be true for regular network device
//
if (pkt) snull_release_buffer(pkt); /* Do this outside the lock! */
return;
}

/*
* Transmit a packet (low level interface)
*/
//
//class - hw dependent tx function
//
static void snull_hw_tx(char *buf, int len, struct net_device *dev)
{
/*
* This function deals with hw details. This interface loops
* back the packet to the other snull interface (if any).
* In other words, this function implements the snull behaviour,
* while all other procedures are rather device-independent
*/
struct iphdr *ih;
struct snull_priv *priv;
u32 *saddr, *daddr;
struct snull_packet *tx_buffer;

printk("IN hw_tx buffer\n");

if (len < sizeof(struct ethhdr) + sizeof(struct iphdr)) {
printk("snull: Hmm... packet too short (%i octets)\n",
len);
return;
}
/*
* Ethhdr is 14 bytes, but the kernel arranges for iphdr
* to be aligned (i.e., ethhdr is unaligned)
*/
//
//class - extract the ip information
//
ih = (struct iphdr *)(buf+sizeof(struct ethhdr));
saddr = &ih->saddr;
daddr = &ih->daddr;

((u8 *)saddr)[2] ^= 1; /* change the third octet (class C) */
((u8 *)daddr)[2] ^= 1;

ih->check = 0; /* and rebuild the checksum (ip needs it) */
ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);


/*
* Ok, now the packet is ready for transmission: first simulate a
* receive interrupt on the twin device, then a
* transmission-done on the transmitting device
*/
//
//
//class - get the device structure of the target device,
// copy to a tx buffer, send the packet , simulate
// rx interrupt and may be simulate a tx interrupt
// on this device
//
//
priv = netdev_priv(dev);
tx_buffer = snull_get_tx_buffer(dev);
tx_buffer->datalen = len;
memcpy(tx_buffer->data, buf, len);
snull_enqueue_buf(dev, tx_buffer);
//
//class - simulate rx interrupt at the receiving device
//
if (priv->rx_int_enabled) {
priv->status |= SNULL_RX_INTR;
snull_interrupt(0, dev, NULL);
}

priv = netdev_priv(dev);
priv->tx_packetlen = len;
priv->tx_packetdata = buf;
priv->status |= SNULL_TX_INTR;
if (lockup && ((priv->stats.tx_packets + 1) % lockup) == 0) {
/* Simulate a dropped transmit interrupt */
netif_stop_queue(dev);
PDEBUG("Simulate lockup at %ld, txp %ld\n", jiffies,
(unsigned long) priv->stats.tx_packets);
}
else
//simulate tx interrupt in the local device
snull_interrupt(0, dev, NULL);
}

/*
* Transmit a packet (called by the kernel)
*/

//
//
//
//class - this function is called by the kernel when there is
// a packet to be transmitted via this interface
//
// may be called directly by dev_queue_xmit()
// or may be queued and called up by net_tx_action(NET_TX_ACTION)
// read ch11 of ULNI/Oreilly for further confusion
//
//
// struct sk_buff * - pointer to the socket buffer that
// contains the packet from the higher layer
// struct net_device * - this identifies our interface - kernel
// uses this to identify us
//
int snull_tx(struct sk_buff *skb, struct net_device *dev)
{
int len;
//
//
//class - look into include/linux/if_ether.h for some of
// the macros - other header files may contain
// similar information
//

char *data, shortpkt[ETH_ZLEN];
struct snull_priv *priv = netdev_priv(dev);
data = skb->data;
printk("In tx\n");
printk("sourcename=%s\n",dev->name);
printk("%s\n",data);
len = skb->len;
printk("%d\n",len);
if (len < ETH_ZLEN) {
memset(shortpkt, 0, ETH_ZLEN);
memcpy(shortpkt, skb->data, skb->len);
len = ETH_ZLEN;
data = shortpkt;
}
dev->trans_start = jiffies;

/* Remember the skb, so we can free it at interrupt time */
priv->skb = skb;

//
//class - calling the Tx work-horse
//

snull_hw_tx(data, len, dev);

return 0;
}

/*
* Deal with a transmit timeout.
*/
//
// class - this method is invoked when there is problem in
// the driver - it has stopped the kernel tx request
// to the driver and has not re-enabled it for a long-time
// refer to ch11 of ULNI for further confusion
// default is 5 seconds - look into other drivers for
// for practical values used
//
//
void snull_tx_timeout (struct net_device *dev)
{
struct snull_priv *priv = netdev_priv(dev);

printk("in timeout\n");
PDEBUG("Transmit timeout at %ld, latency %ld\n", jiffies,
jiffies - dev->trans_start);
/* Simulate a transmission interrupt to get things moving */
priv->status = SNULL_TX_INTR;
snull_interrupt(0, dev, NULL);
priv->stats.tx_errors++;
netif_wake_queue(dev);
return;
}

/*
* Return statistics to the caller
*
*/
struct net_device_stats *snull_stats(struct net_device *dev)
{
struct snull_priv *priv = netdev_priv(dev);
return &priv->stats;
}

/*
* This function is called to fill up an eth header, since arp is not
* available on the interface
*/
int snull_rebuild_header(struct sk_buff *skb)
{
struct ethhdr *eth = (struct ethhdr *) skb->data;
struct net_device *dev = skb->dev;
printk("in rebuild header\n");

memcpy(eth->h_source, dev->dev_addr, dev->addr_len);
memcpy(eth->h_dest, dev->dev_addr, dev->addr_len);
eth->h_dest[ETH_ALEN-1] ^= 0x01; /* dest is us xor 1 */
return 0;
}


int snull_header(struct sk_buff *skb, struct net_device *dev,
unsigned short type, void *daddr, void *saddr,
unsigned int len)
{
struct ethhdr *eth = (struct ethhdr *)skb_push(skb,ETH_HLEN);
printk("in hard header\n");

eth->h_proto = htons(type);
memcpy(eth->h_source, saddr ? saddr : dev->dev_addr, dev->addr_len);
memcpy(eth->h_dest, daddr ? daddr : dev->dev_addr, dev->addr_len);
eth->h_dest[ETH_ALEN-1] ^= 0x01; /* dest is us xor 1 */
return (dev->hard_header_len);
}





/*
* The "change_mtu" method is usually not needed.
* ideally, you should not change this - this will affect the kernel
* and introduce too much fragmentation of data at the network layer
*
* refer to your h/w data-sheet and do the needful accordingly
* If you need it, it must be like this.
*
* Important point : if you do not know, let the system decide
* else, explore thoroughly about your data-link layer
* standard and do the rest - look into another driver
* in your kernel source-tree
*
*/
int snull_change_mtu(struct net_device *dev, int new_mtu)
{
unsigned long flags;
struct snull_priv *priv = netdev_priv(dev);
spinlock_t *lock = &priv->lock;

/* check ranges */
if ((new_mtu < 6 || (new_mtu > 1500))
return -EINVAL;
/*
* Do anything you need, and the accept the value
*/
spin_lock_irqsave(lock, flags);
dev->mtu = new_mtu;
spin_unlock_irqrestore(lock, flags);
return 0; /* success */
}

static int snull_close(struct net_device *dev)
{
printk("IN CLOSE\n");

//shutdown the transmission queue
netif_stop_queue(dev);

return 0;

}

#ifdef HAVE_NET_DEVICE_OPS
static struct net_device_ ndo = {
.ndo_open = snull_open,
.ndo_stop = snull_close,
.ndo_start_xmit = snull_tx,
.ndo_get_stats = snull_stats,
.ndo_set_config = snull_config,
.ndo_change_mtu = snull_change_mtu,
};
#endif

/*
* The init function that we supplied to alloc_netdev() and invoked
* internally by alloc_netdev() internally
*/
void snull_init(struct net_device *dev)
{
struct snull_priv *priv;

/*
* Then, assign other fields in dev, using ether_setup() and some
* hand assignments
*/
//
//
//class - must look into net/ethernet/eth.c
// analyse it as needed - data-link layer specific function
// initializes certain ethernet relevant fields
//

printk("IN SETUP,ETHER_SETUP\n");
ether_setup(dev);
//
//
//class - call open to initialize the device and
// allocate resources to the device
//

#ifdef HAVE_NET_DEVICE_OPS
dev->netdev_ops =(struct net_device_ops*)&ndo;
#else
dev->open = snull_open;
dev->stop = snull_release;
dev->set_config = snull_config;
dev->hard_start_xmit = snull_tx;
dev->do_ioctl = snull_ioctl;
dev->get_stats = snull_stats;
dev->change_mtu = snull_change_mtu;
dev->rebuild_header = snull_rebuild_header;
dev->hard_header = snull_header;
dev->tx_timeout = snull_tx_timeout;
dev->watchdog_timeo = timeout;
dev->hard_header_cache = NULL; /* Disable caching */

#endif

dev->flags |= IFF_NOARP;
dev->features |= NETIF_F_NO_CSUM;

priv = netdev_priv(dev);
memset(priv, 0, sizeof(struct snull_priv));
spin_lock_init(&priv->lock);
snull_rx_ints(dev, 1); /* enable receive interrupts */
snull_setup_pool(dev);

printk(" SETUP COMPLETE\n");

}

/*
* The devices
*/
//
//class - we are expected to store the struct net_devices and use them
// during the load time of the driver/devices
struct net_device *snull_devs[1];



/*
* Finally, the module stuff
*/

void snull_cleanup(void)
{
int i;

printk("IN CLEANUP\n");

for (i = 0; i < 1; i++) {
if (snull_devs[i]) {
//
//class: unregister the struct net_device
//
unregister_netdev(snull_devs[i]);
//
//class: tear down our device packet pool
// memory
//
snull_teardown_pool(snull_devs[i]);
//
//class: free the struct net_device we had allocated
//
free_netdev(snull_devs[i]);
}
}
return;
}


//class - here we will be registering our network devices(corresponding
// structures - struct net_device structures)

int snull_init_module(void)
{
int result, i, ret = -ENOMEM;

printk("In Init Module\n");

//
//
//class - set the normal API support or NewAPI support
// normal API uses - interrupts only rx delivery
// newAPI uses - interrupts + polling to reduce
// the load on the system
//
snull_interrupt = snull_regular_interrupt;

/* Allocate the devices */
//class - here we are using alloc_netdev() for the
// naming convenience
// the init function(snull_init()) is invoked
// within the function alloc_netdev() - as the name
// suggests, it does the major initialization
// find where this function is located ??
// net/core/dev.c
//
//class - we may use alloc_etherdev() for this purpose
//
// many drivers use this approach for this purpose
// you may try using alloc_etherdev()
//
// find where this function is located ??
snull_devs[0] = alloc_netdev(sizeof(struct snull_priv), "sn%d",
snull_init);
//class - we must store these objects in our driver and use them
// till our driver is unloaded
//
if (snull_devs[0] == NULL )
goto out;

ret = -ENODEV;

//class - register_netdev() - register the network
// device/driver - before calling this
// function, we must initialize the struct net_device
// thoroughly
//
//class - this adds our device to the global list of
// network devices
//
for (i = 0; i < 1; i++)
if ((result = register_netdev(snull_devs[i])))
printk("snull: error %i registering device \"%s\"\n",
result, snull_devs[i]->name);
else
ret = 0;
out:
if (ret)
snull_cleanup();
return ret;
}


module_init(snull_init_module);
module_exit(snull_cleanup);