1073 lines
24 KiB
C
1073 lines
24 KiB
C
/*
|
|
* INET An implementation of the TCP/IP protocol suite for the LINUX
|
|
* operating system. INET is implemented using the BSD Socket
|
|
* interface as the means of communication with the user level.
|
|
*
|
|
* Interface (streams) handling functions.
|
|
*
|
|
* Version: @(#)dev.c 1.0.19 05/31/93
|
|
*
|
|
* Authors: Ross Biro, <bir7@leland.Stanford.Edu>
|
|
* Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
|
|
* Mark Evans, <evansmp@uhura.aston.ac.uk>
|
|
*
|
|
* Fixes:
|
|
* Alan Cox: check_addr returns a value for a wrong subnet
|
|
* ie not us but don't forward this!
|
|
* Alan Cox: block timer if the inet_bh handler is running
|
|
* Alan Cox: generic queue code added. A lot neater now
|
|
* C.E.Hawkins: SIOCGIFCONF only reports 'upped' interfaces
|
|
* C.E.Hawkins: IFF_PROMISC support
|
|
* Alan Cox: Supports Donald Beckers new hardware
|
|
* multicast layer, but not yet multicast lists.
|
|
* Alan Cox: ip_addr_match problems with class A/B nets.
|
|
* C.E.Hawkins IP 0.0.0.0 and also same net route fix. [FIXME: Ought to cause ICMP_REDIRECT]
|
|
* Alan Cox: Removed bogus subnet check now the subnet code
|
|
* a) actually works for all A/B nets
|
|
* b) doesn't forward off the same interface.
|
|
* Alan Cox: Multiple extra protocols
|
|
* Alan Cox: Fixed ifconfig up of dud device setting the up flag
|
|
* Alan Cox: Fixed verify_area errors
|
|
* Alan Cox: Removed IP_SET_DEV as per Fred's comment. I hope this doesn't give
|
|
* anything away 8)
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*/
|
|
#include <asm/segment.h>
|
|
#include <asm/system.h>
|
|
#include <asm/bitops.h>
|
|
#include <linux/config.h>
|
|
#include <linux/types.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/string.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/socket.h>
|
|
#include <linux/sockios.h>
|
|
#include <linux/in.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/if_ether.h>
|
|
#include "inet.h"
|
|
#include "dev.h"
|
|
#include "eth.h"
|
|
#include "ip.h"
|
|
#include "route.h"
|
|
#include "protocol.h"
|
|
#include "tcp.h"
|
|
#include "skbuff.h"
|
|
#include "sock.h"
|
|
#include "arp.h"
|
|
#ifdef CONFIG_AX25
|
|
#include "ax25.h"
|
|
#endif
|
|
|
|
|
|
#ifdef CONFIG_IPX
|
|
|
|
static struct packet_type ipx_8023_type = {
|
|
NET16(ETH_P_802_3),
|
|
0,
|
|
ipx_rcv,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
static struct packet_type ipx_packet_type = {
|
|
NET16(ETH_P_IPX),
|
|
0,
|
|
ipx_rcv,
|
|
NULL,
|
|
&ipx_8023_type
|
|
};
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_AX25
|
|
|
|
static struct packet_type ax25_packet_type = {
|
|
NET16(ETH_P_AX25),
|
|
0,
|
|
ax25_rcv,
|
|
NULL,
|
|
#ifdef CONFIG_IPX
|
|
&ipx_packet_type
|
|
#else
|
|
NULL
|
|
#endif
|
|
};
|
|
#endif
|
|
|
|
|
|
static struct packet_type arp_packet_type = {
|
|
NET16(ETH_P_ARP),
|
|
0, /* copy */
|
|
arp_rcv,
|
|
NULL,
|
|
#ifdef CONFIG_IPX
|
|
#ifndef CONFIG_AX25
|
|
&ipx_packet_type
|
|
#else
|
|
&ax25_packet_type
|
|
#endif
|
|
#else
|
|
#ifdef CONFIG_AX25
|
|
&ax25_packet_type
|
|
#else
|
|
NULL /* next */
|
|
#endif
|
|
#endif
|
|
};
|
|
|
|
|
|
static struct packet_type ip_packet_type = {
|
|
NET16(ETH_P_IP),
|
|
0, /* copy */
|
|
ip_rcv,
|
|
NULL,
|
|
&arp_packet_type
|
|
};
|
|
|
|
|
|
struct packet_type *ptype_base = &ip_packet_type;
|
|
static struct sk_buff *volatile backlog = NULL;
|
|
static unsigned long ip_bcast = 0;
|
|
|
|
|
|
/* Return the lesser of the two values. */
|
|
static unsigned long
|
|
min(unsigned long a, unsigned long b)
|
|
{
|
|
if (a < b) return(a);
|
|
return(b);
|
|
}
|
|
|
|
|
|
/* Determine a default network mask, based on the IP address. */
|
|
static unsigned long
|
|
get_mask(unsigned long addr)
|
|
{
|
|
unsigned long dst;
|
|
|
|
if (addr == 0L)
|
|
return(0L); /* special case */
|
|
|
|
dst = ntohl(addr);
|
|
if (IN_CLASSA(dst))
|
|
return(htonl(IN_CLASSA_NET));
|
|
if (IN_CLASSB(dst))
|
|
return(htonl(IN_CLASSB_NET));
|
|
if (IN_CLASSC(dst))
|
|
return(htonl(IN_CLASSC_NET));
|
|
|
|
/* Something else, probably a subnet. */
|
|
return(0);
|
|
}
|
|
|
|
|
|
int
|
|
ip_addr_match(unsigned long me, unsigned long him)
|
|
{
|
|
int i;
|
|
unsigned long mask=0xFFFFFFFF;
|
|
DPRINTF((DBG_DEV, "ip_addr_match(%s, ", in_ntoa(me)));
|
|
DPRINTF((DBG_DEV, "%s)\n", in_ntoa(him)));
|
|
|
|
if (me == him)
|
|
return(1);
|
|
for (i = 0; i < 4; i++, me >>= 8, him >>= 8, mask >>= 8) {
|
|
if ((me & 0xFF) != (him & 0xFF)) {
|
|
/*
|
|
* The only way this could be a match is for
|
|
* the rest of addr1 to be 0 or 255.
|
|
*/
|
|
if (me != 0 && me != mask) return(0);
|
|
return(1);
|
|
}
|
|
}
|
|
return(1);
|
|
}
|
|
|
|
|
|
/* Check the address for our address, broadcasts, etc. */
|
|
int
|
|
chk_addr(unsigned long addr)
|
|
{
|
|
struct device *dev;
|
|
unsigned long dst;
|
|
|
|
DPRINTF((DBG_DEV, "chk_addr(%s) --> ", in_ntoa(addr)));
|
|
dst = ntohl(addr);
|
|
|
|
/* Accept both `all ones' and `all zeros' as BROADCAST. */
|
|
if (dst == INADDR_ANY || dst == INADDR_BROADCAST) {
|
|
DPRINTF((DBG_DEV, "BROADCAST\n"));
|
|
return(IS_BROADCAST);
|
|
}
|
|
|
|
/* Accept all of the `loopback' class A net. */
|
|
if ((dst & IN_CLASSA_NET) == 0x7F000000L) {
|
|
DPRINTF((DBG_DEV, "LOOPBACK\n"));
|
|
|
|
/*
|
|
* We force `loopback' to be equal to MY_ADDR.
|
|
*/
|
|
return(IS_MYADDR);
|
|
/* return(IS_LOOPBACK); */
|
|
}
|
|
|
|
/* OK, now check the interface addresses. */
|
|
for (dev = dev_base; dev != NULL; dev = dev->next) {
|
|
if (!(dev->flags&IFF_UP))
|
|
continue;
|
|
if ((dev->pa_addr == 0)/* || (dev->flags&IFF_PROMISC)*/)
|
|
return(IS_MYADDR);
|
|
/* Is it the exact IP address? */
|
|
if (addr == dev->pa_addr) {
|
|
DPRINTF((DBG_DEV, "MYADDR\n"));
|
|
return(IS_MYADDR);
|
|
}
|
|
|
|
/* Nope. Check for a subnetwork broadcast. */
|
|
if ((addr & dev->pa_mask) == (dev->pa_addr & dev->pa_mask)) {
|
|
if ((addr & ~dev->pa_mask) == 0) {
|
|
DPRINTF((DBG_DEV, "SUBBROADCAST-0\n"));
|
|
return(IS_BROADCAST);
|
|
}
|
|
if (((addr & ~dev->pa_mask) | dev->pa_mask)
|
|
== INADDR_BROADCAST) {
|
|
DPRINTF((DBG_DEV, "SUBBROADCAST-1\n"));
|
|
return(IS_BROADCAST);
|
|
}
|
|
}
|
|
|
|
/* Nope. Check for Network broadcast. */
|
|
if(IN_CLASSA(dst)) {
|
|
if( addr == (dev->pa_addr | 0xffffff00)) {
|
|
DPRINTF((DBG_DEV, "CLASS A BROADCAST-1\n"));
|
|
return(IS_BROADCAST);
|
|
}
|
|
}
|
|
else if(IN_CLASSB(dst)) {
|
|
if( addr == (dev->pa_addr | 0xffff0000)) {
|
|
DPRINTF((DBG_DEV, "CLASS B BROADCAST-1\n"));
|
|
return(IS_BROADCAST);
|
|
}
|
|
}
|
|
else { /* IN_CLASSC */
|
|
if( addr == (dev->pa_addr | 0xff000000)) {
|
|
DPRINTF((DBG_DEV, "CLASS C BROADCAST-1\n"));
|
|
return(IS_BROADCAST);
|
|
}
|
|
}
|
|
}
|
|
|
|
DPRINTF((DBG_DEV, "NONE\n"));
|
|
|
|
return(0); /* no match at all */
|
|
}
|
|
|
|
|
|
/*
|
|
* Retrieve our own address.
|
|
* Because the loopback address (127.0.0.1) is already recognized
|
|
* automatically, we can use the loopback interface's address as
|
|
* our "primary" interface. This is the addressed used by IP et
|
|
* al when it doesn't know which address to use (i.e. it does not
|
|
* yet know from or to which interface to go...).
|
|
*/
|
|
unsigned long
|
|
my_addr(void)
|
|
{
|
|
struct device *dev;
|
|
|
|
for (dev = dev_base; dev != NULL; dev = dev->next) {
|
|
if (dev->flags & IFF_LOOPBACK) return(dev->pa_addr);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
|
|
static int dev_nit=0; /* Number of network taps running */
|
|
|
|
/* Add a protocol ID to the list. This will change soon. */
|
|
void
|
|
dev_add_pack(struct packet_type *pt)
|
|
{
|
|
struct packet_type *p1;
|
|
pt->next = ptype_base;
|
|
|
|
/* Don't use copy counts on ETH_P_ALL. Instead keep a global
|
|
count of number of these and use it and pt->copy to decide
|
|
copies */
|
|
pt->copy=0;
|
|
if(pt->type==NET16(ETH_P_ALL))
|
|
dev_nit++; /* I'd like a /dev/nit too one day 8) */
|
|
else
|
|
{
|
|
/* See if we need to copy it. */
|
|
for (p1 = ptype_base; p1 != NULL; p1 = p1->next) {
|
|
if (p1->type == pt->type) {
|
|
pt->copy = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* NIT taps must go at the end or inet_bh will leak!
|
|
*/
|
|
|
|
if(pt->type==NET16(ETH_P_ALL))
|
|
{
|
|
pt->next=NULL;
|
|
if(ptype_base==NULL)
|
|
ptype_base=pt;
|
|
else
|
|
{
|
|
for(p1=ptype_base;p1->next!=NULL;p1=p1->next);
|
|
p1->next=pt;
|
|
}
|
|
}
|
|
else
|
|
ptype_base = pt;
|
|
}
|
|
|
|
|
|
/* Remove a protocol ID from the list. This will change soon. */
|
|
void
|
|
dev_remove_pack(struct packet_type *pt)
|
|
{
|
|
struct packet_type *lpt, *pt1;
|
|
|
|
if (pt->type == NET16(ETH_P_ALL))
|
|
dev_nit--;
|
|
if (pt == ptype_base) {
|
|
ptype_base = pt->next;
|
|
return;
|
|
}
|
|
|
|
lpt = NULL;
|
|
for (pt1 = ptype_base; pt1->next != NULL; pt1 = pt1->next) {
|
|
if (pt1->next == pt ) {
|
|
cli();
|
|
if (!pt->copy && lpt)
|
|
lpt->copy = 0;
|
|
pt1->next = pt->next;
|
|
sti();
|
|
return;
|
|
}
|
|
|
|
if (pt1->next -> type == pt ->type && pt->type != NET16(ETH_P_ALL)) {
|
|
lpt = pt1->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Find an interface in the list. This will change soon. */
|
|
struct device *
|
|
dev_get(char *name)
|
|
{
|
|
struct device *dev;
|
|
|
|
for (dev = dev_base; dev != NULL; dev = dev->next) {
|
|
if (strcmp(dev->name, name) == 0)
|
|
return(dev);
|
|
}
|
|
return(NULL);
|
|
}
|
|
|
|
|
|
/* Find an interface that can handle addresses for a certain address. */
|
|
struct device * dev_check(unsigned long addr)
|
|
{
|
|
struct device *dev;
|
|
|
|
for (dev = dev_base; dev; dev = dev->next) {
|
|
if (!(dev->flags & IFF_UP))
|
|
continue;
|
|
if (!(dev->flags & IFF_POINTOPOINT))
|
|
continue;
|
|
if (addr != dev->pa_dstaddr)
|
|
continue;
|
|
return dev;
|
|
}
|
|
for (dev = dev_base; dev; dev = dev->next) {
|
|
if (!(dev->flags & IFF_UP))
|
|
continue;
|
|
if (dev->flags & IFF_POINTOPOINT)
|
|
continue;
|
|
if (dev->pa_mask & (addr ^ dev->pa_addr))
|
|
continue;
|
|
return dev;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* Prepare an interface for use. */
|
|
int
|
|
dev_open(struct device *dev)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (dev->open)
|
|
ret = dev->open(dev);
|
|
if (ret == 0)
|
|
dev->flags |= (IFF_UP | IFF_RUNNING);
|
|
|
|
return(ret);
|
|
}
|
|
|
|
|
|
/* Completely shutdown an interface. */
|
|
int
|
|
dev_close(struct device *dev)
|
|
{
|
|
if (dev->flags != 0) {
|
|
int ct=0;
|
|
dev->flags = 0;
|
|
if (dev->stop)
|
|
dev->stop(dev);
|
|
rt_flush(dev);
|
|
dev->pa_addr = 0;
|
|
dev->pa_dstaddr = 0;
|
|
dev->pa_brdaddr = 0;
|
|
dev->pa_mask = 0;
|
|
/* Purge any queued packets when we down the link */
|
|
while(ct<DEV_NUMBUFFS)
|
|
{
|
|
struct sk_buff *skb;
|
|
while((skb=skb_dequeue(&dev->buffs[ct]))!=NULL)
|
|
if(skb->free)
|
|
kfree_skb(skb,FREE_WRITE);
|
|
ct++;
|
|
}
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
/* Send (or queue for sending) a packet. */
|
|
void
|
|
dev_queue_xmit(struct sk_buff *skb, struct device *dev, int pri)
|
|
{
|
|
int where = 0; /* used to say if the packet should go */
|
|
/* at the front or the back of the */
|
|
/* queue. */
|
|
|
|
DPRINTF((DBG_DEV, "dev_queue_xmit(skb=%X, dev=%X, pri = %d)\n",
|
|
skb, dev, pri));
|
|
|
|
if (dev == NULL) {
|
|
printk("dev.c: dev_queue_xmit: dev = NULL\n");
|
|
return;
|
|
}
|
|
|
|
IS_SKB(skb);
|
|
|
|
skb->dev = dev;
|
|
if (skb->next != NULL) {
|
|
/* Make sure we haven't missed an interrupt. */
|
|
dev->hard_start_xmit(NULL, dev);
|
|
return;
|
|
}
|
|
|
|
if (pri < 0) {
|
|
pri = -pri-1;
|
|
where = 1;
|
|
}
|
|
|
|
if (pri >= DEV_NUMBUFFS) {
|
|
printk("bad priority in dev_queue_xmit.\n");
|
|
pri = 1;
|
|
}
|
|
|
|
if (dev->hard_start_xmit(skb, dev) == 0) {
|
|
return;
|
|
}
|
|
|
|
/* Put skb into a bidirectional circular linked list. */
|
|
DPRINTF((DBG_DEV, "dev_queue_xmit dev->buffs[%d]=%X\n",
|
|
pri, dev->buffs[pri]));
|
|
|
|
/* Interrupts should already be cleared by hard_start_xmit. */
|
|
cli();
|
|
skb->magic = DEV_QUEUE_MAGIC;
|
|
if(where)
|
|
skb_queue_head(&dev->buffs[pri],skb);
|
|
else
|
|
skb_queue_tail(&dev->buffs[pri],skb);
|
|
skb->magic = DEV_QUEUE_MAGIC;
|
|
sti();
|
|
}
|
|
|
|
/*
|
|
* Receive a packet from a device driver and queue it for the upper
|
|
* (protocol) levels. It always succeeds.
|
|
*/
|
|
void
|
|
netif_rx(struct sk_buff *skb)
|
|
{
|
|
/* Set any necessary flags. */
|
|
skb->sk = NULL;
|
|
skb->free = 1;
|
|
|
|
/* and add it to the "backlog" queue. */
|
|
IS_SKB(skb);
|
|
skb_queue_tail(&backlog,skb);
|
|
|
|
/* If any packet arrived, mark it for processing. */
|
|
if (backlog != NULL) mark_bh(INET_BH);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* The old interface to fetch a packet from a device driver.
|
|
* This function is the base level entry point for all drivers that
|
|
* want to send a packet to the upper (protocol) levels. It takes
|
|
* care of de-multiplexing the packet to the various modules based
|
|
* on their protocol ID.
|
|
*
|
|
* Return values: 1 <- exit I can't do any more
|
|
* 0 <- feed me more (i.e. "done", "OK").
|
|
*/
|
|
int
|
|
dev_rint(unsigned char *buff, long len, int flags, struct device *dev)
|
|
{
|
|
static int dropping = 0;
|
|
struct sk_buff *skb = NULL;
|
|
unsigned char *to;
|
|
int amount, left;
|
|
int len2;
|
|
|
|
if (dev == NULL || buff == NULL || len <= 0) return(1);
|
|
if (flags & IN_SKBUFF) {
|
|
skb = (struct sk_buff *) buff;
|
|
} else {
|
|
if (dropping) {
|
|
if (backlog != NULL)
|
|
return(1);
|
|
printk("INET: dev_rint: no longer dropping packets.\n");
|
|
dropping = 0;
|
|
}
|
|
|
|
skb = alloc_skb(sizeof(*skb) + len, GFP_ATOMIC);
|
|
if (skb == NULL) {
|
|
printk("dev_rint: packet dropped on %s (no memory) !\n",
|
|
dev->name);
|
|
dropping = 1;
|
|
return(1);
|
|
}
|
|
skb->mem_len = sizeof(*skb) + len;
|
|
skb->mem_addr = (struct sk_buff *) skb;
|
|
|
|
/* First we copy the packet into a buffer, and save it for later. */
|
|
to = skb->data;
|
|
left = len;
|
|
len2 = len;
|
|
while (len2 > 0) {
|
|
amount = min(len2, (unsigned long) dev->rmem_end -
|
|
(unsigned long) buff);
|
|
memcpy(to, buff, amount);
|
|
len2 -= amount;
|
|
left -= amount;
|
|
buff += amount;
|
|
to += amount;
|
|
if ((unsigned long) buff == dev->rmem_end)
|
|
buff = (unsigned char *) dev->rmem_start;
|
|
}
|
|
}
|
|
skb->len = len;
|
|
skb->dev = dev;
|
|
skb->free = 1;
|
|
|
|
netif_rx(skb);
|
|
/* OK, all done. */
|
|
return(0);
|
|
}
|
|
|
|
|
|
/* This routine causes all interfaces to try to send some data. */
|
|
void
|
|
dev_transmit(void)
|
|
{
|
|
struct device *dev;
|
|
|
|
for (dev = dev_base; dev != NULL; dev = dev->next) {
|
|
if (!dev->tbusy) {
|
|
dev_tint(dev);
|
|
}
|
|
}
|
|
}
|
|
|
|
static volatile char in_bh = 0;
|
|
|
|
int in_inet_bh() /* Used by timer.c */
|
|
{
|
|
return(in_bh==0?0:1);
|
|
}
|
|
|
|
/*
|
|
* This function gets called periodically, to see if we can
|
|
* process any data that came in from some interface.
|
|
*
|
|
*/
|
|
void
|
|
inet_bh(void *tmp)
|
|
{
|
|
struct sk_buff *skb;
|
|
struct packet_type *ptype;
|
|
unsigned short type;
|
|
unsigned char flag = 0;
|
|
int nitcount;
|
|
|
|
/* Atomically check and mark our BUSY state. */
|
|
if (set_bit(1, (void*)&in_bh))
|
|
return;
|
|
|
|
/* Can we send anything now? */
|
|
dev_transmit();
|
|
|
|
/* Any data left to process? */
|
|
while((skb=skb_dequeue(&backlog))!=NULL)
|
|
{
|
|
nitcount=dev_nit;
|
|
flag=0;
|
|
sti();
|
|
/*
|
|
* Bump the pointer to the next structure.
|
|
* This assumes that the basic 'skb' pointer points to
|
|
* the MAC header, if any (as indicated by its "length"
|
|
* field). Take care now!
|
|
*/
|
|
skb->h.raw = skb->data + skb->dev->hard_header_len;
|
|
skb->len -= skb->dev->hard_header_len;
|
|
|
|
/*
|
|
* Fetch the packet protocol ID. This is also quite ugly, as
|
|
* it depends on the protocol driver (the interface itself) to
|
|
* know what the type is, or where to get it from. The Ethernet
|
|
* interfaces fetch the ID from the two bytes in the Ethernet MAC
|
|
* header (the h_proto field in struct ethhdr), but drivers like
|
|
* SLIP and PLIP have no alternative but to force the type to be
|
|
* IP or something like that. Sigh- FvK
|
|
*/
|
|
type = skb->dev->type_trans(skb, skb->dev);
|
|
|
|
/*
|
|
* We got a packet ID. Now loop over the "known protocols"
|
|
* table (which is actually a linked list, but this will
|
|
* change soon if I get my way- FvK), and forward the packet
|
|
* to anyone who wants it.
|
|
*/
|
|
for (ptype = ptype_base; ptype != NULL; ptype = ptype->next) {
|
|
if (ptype->type == type || ptype->type == NET16(ETH_P_ALL)) {
|
|
struct sk_buff *skb2;
|
|
|
|
if (ptype->type==NET16(ETH_P_ALL))
|
|
nitcount--;
|
|
if (ptype->copy || nitcount) { /* copy if we need to */
|
|
skb2 = alloc_skb(skb->mem_len, GFP_ATOMIC);
|
|
if (skb2 == NULL)
|
|
continue;
|
|
memcpy(skb2, (const void *) skb, skb->mem_len);
|
|
skb2->mem_addr = skb2;
|
|
skb2->h.raw = (unsigned char *)(
|
|
(unsigned long) skb2 +
|
|
(unsigned long) skb->h.raw -
|
|
(unsigned long) skb
|
|
);
|
|
skb2->free = 1;
|
|
} else {
|
|
skb2 = skb;
|
|
}
|
|
|
|
/* This used to be in the 'else' part, but then
|
|
* we don't have this flag set when we get a
|
|
* protocol that *does* require copying... -FvK
|
|
*/
|
|
flag = 1;
|
|
|
|
/* Kick the protocol handler. */
|
|
ptype->func(skb2, skb->dev, ptype);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* That's odd. We got an unknown packet. Who's using
|
|
* stuff like Novell or Amoeba on this network??
|
|
*/
|
|
if (!flag) {
|
|
DPRINTF((DBG_DEV,
|
|
"INET: unknown packet type 0x%04X (ignored)\n", type));
|
|
skb->sk = NULL;
|
|
kfree_skb(skb, FREE_WRITE);
|
|
}
|
|
|
|
/* Again, see if we can transmit anything now. */
|
|
dev_transmit();
|
|
cli();
|
|
}
|
|
in_bh = 0;
|
|
sti();
|
|
dev_transmit();
|
|
}
|
|
|
|
|
|
/*
|
|
* This routine is called when an device driver (i.e. an
|
|
* interface) is * ready to transmit a packet.
|
|
*/
|
|
|
|
void dev_tint(struct device *dev)
|
|
{
|
|
int i;
|
|
struct sk_buff *skb;
|
|
|
|
for(i = 0;i < DEV_NUMBUFFS; i++) {
|
|
while((skb=skb_dequeue(&dev->buffs[i]))!=NULL)
|
|
{
|
|
skb->magic = 0;
|
|
skb->next = NULL;
|
|
skb->prev = NULL;
|
|
dev->queue_xmit(skb,dev,-i - 1);
|
|
if (dev->tbusy)
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Perform a SIOCGIFCONF call. */
|
|
static int
|
|
dev_ifconf(char *arg)
|
|
{
|
|
struct ifconf ifc;
|
|
struct ifreq ifr;
|
|
struct device *dev;
|
|
char *pos;
|
|
int len;
|
|
int err;
|
|
|
|
/* Fetch the caller's info block. */
|
|
err=verify_area(VERIFY_WRITE, arg, sizeof(struct ifconf));
|
|
if(err)
|
|
return err;
|
|
memcpy_fromfs(&ifc, arg, sizeof(struct ifconf));
|
|
len = ifc.ifc_len;
|
|
pos = ifc.ifc_buf;
|
|
|
|
/* Loop over the interfaces, and write an info block for each. */
|
|
for (dev = dev_base; dev != NULL; dev = dev->next) {
|
|
if(!(dev->flags & IFF_UP))
|
|
continue;
|
|
memset(&ifr, 0, sizeof(struct ifreq));
|
|
strcpy(ifr.ifr_name, dev->name);
|
|
(*(struct sockaddr_in *) &ifr.ifr_addr).sin_family = dev->family;
|
|
(*(struct sockaddr_in *) &ifr.ifr_addr).sin_addr.s_addr = dev->pa_addr;
|
|
|
|
/* Write this block to the caller's space. */
|
|
memcpy_tofs(pos, &ifr, sizeof(struct ifreq));
|
|
pos += sizeof(struct ifreq);
|
|
len -= sizeof(struct ifreq);
|
|
if (len < sizeof(struct ifreq)) break;
|
|
}
|
|
|
|
/* All done. Write the updated control block back to the caller. */
|
|
ifc.ifc_len = (pos - ifc.ifc_buf);
|
|
ifc.ifc_req = (struct ifreq *) ifc.ifc_buf;
|
|
memcpy_tofs(arg, &ifc, sizeof(struct ifconf));
|
|
return(pos - arg);
|
|
}
|
|
|
|
/* Print device statistics. */
|
|
char *sprintf_stats(char *buffer, struct device *dev)
|
|
{
|
|
char *pos = buffer;
|
|
struct enet_statistics *stats = (dev->get_stats ? dev->get_stats(dev): NULL);
|
|
|
|
if (stats)
|
|
pos += sprintf(pos, "%6s:%7d %4d %4d %4d %4d %8d %4d %4d %4d %5d %4d\n",
|
|
dev->name,
|
|
stats->rx_packets, stats->rx_errors,
|
|
stats->rx_dropped + stats->rx_missed_errors,
|
|
stats->rx_fifo_errors,
|
|
stats->rx_length_errors + stats->rx_over_errors
|
|
+ stats->rx_crc_errors + stats->rx_frame_errors,
|
|
stats->tx_packets, stats->tx_errors, stats->tx_dropped,
|
|
stats->tx_fifo_errors, stats->collisions,
|
|
stats->tx_carrier_errors + stats->tx_aborted_errors
|
|
+ stats->tx_window_errors + stats->tx_heartbeat_errors);
|
|
else
|
|
pos += sprintf(pos, "%6s: No statistics available.\n", dev->name);
|
|
|
|
return pos;
|
|
}
|
|
|
|
/* Called from the PROCfs module. */
|
|
int
|
|
dev_get_info(char *buffer)
|
|
{
|
|
char *pos = buffer;
|
|
struct device *dev;
|
|
|
|
pos +=
|
|
sprintf(pos,
|
|
"Inter-| Receive | Transmit\n"
|
|
" face |packets errs drop fifo frame|packets errs drop fifo colls carrier\n");
|
|
for (dev = dev_base; dev != NULL; dev = dev->next) {
|
|
pos = sprintf_stats(pos, dev);
|
|
}
|
|
return pos - buffer;
|
|
}
|
|
|
|
static inline int bad_mask(unsigned long mask, unsigned long addr)
|
|
{
|
|
if (addr & (mask = ~mask))
|
|
return 1;
|
|
mask = ntohl(mask);
|
|
if (mask & (mask+1))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Perform the SIOCxIFxxx calls. */
|
|
static int
|
|
dev_ifsioc(void *arg, unsigned int getset)
|
|
{
|
|
struct ifreq ifr;
|
|
struct device *dev;
|
|
int ret;
|
|
|
|
/* Fetch the caller's info block. */
|
|
int err=verify_area(VERIFY_WRITE, arg, sizeof(struct ifreq));
|
|
if(err)
|
|
return err;
|
|
memcpy_fromfs(&ifr, arg, sizeof(struct ifreq));
|
|
|
|
/* See which interface the caller is talking about. */
|
|
if ((dev = dev_get(ifr.ifr_name)) == NULL) return(-EINVAL);
|
|
|
|
switch(getset) {
|
|
case SIOCGIFFLAGS:
|
|
ifr.ifr_flags = dev->flags;
|
|
memcpy_tofs(arg, &ifr, sizeof(struct ifreq));
|
|
ret = 0;
|
|
break;
|
|
case SIOCSIFFLAGS:
|
|
{
|
|
int old_flags = dev->flags;
|
|
dev->flags = ifr.ifr_flags & (
|
|
IFF_UP | IFF_BROADCAST | IFF_DEBUG | IFF_LOOPBACK |
|
|
IFF_POINTOPOINT | IFF_NOTRAILERS | IFF_RUNNING |
|
|
IFF_NOARP | IFF_PROMISC | IFF_ALLMULTI);
|
|
|
|
if ( (old_flags & IFF_PROMISC) && ((dev->flags & IFF_PROMISC) == 0))
|
|
dev->set_multicast_list(dev,0,NULL);
|
|
if ( (dev->flags & IFF_PROMISC) && ((old_flags & IFF_PROMISC) == 0))
|
|
dev->set_multicast_list(dev,-1,NULL);
|
|
if ((old_flags & IFF_UP) && ((dev->flags & IFF_UP) == 0)) {
|
|
ret = dev_close(dev);
|
|
} else
|
|
{
|
|
ret = (! (old_flags & IFF_UP) && (dev->flags & IFF_UP))
|
|
? dev_open(dev) : 0;
|
|
if(ret<0)
|
|
dev->flags&=~IFF_UP; /* Didnt open so down the if */
|
|
}
|
|
}
|
|
break;
|
|
case SIOCGIFADDR:
|
|
(*(struct sockaddr_in *)
|
|
&ifr.ifr_addr).sin_addr.s_addr = dev->pa_addr;
|
|
(*(struct sockaddr_in *)
|
|
&ifr.ifr_addr).sin_family = dev->family;
|
|
(*(struct sockaddr_in *)
|
|
&ifr.ifr_addr).sin_port = 0;
|
|
memcpy_tofs(arg, &ifr, sizeof(struct ifreq));
|
|
ret = 0;
|
|
break;
|
|
case SIOCSIFADDR:
|
|
dev->pa_addr = (*(struct sockaddr_in *)
|
|
&ifr.ifr_addr).sin_addr.s_addr;
|
|
dev->family = ifr.ifr_addr.sa_family;
|
|
dev->pa_mask = get_mask(dev->pa_addr);
|
|
dev->pa_brdaddr = dev->pa_addr | ~dev->pa_mask;
|
|
ret = 0;
|
|
break;
|
|
case SIOCGIFBRDADDR:
|
|
(*(struct sockaddr_in *)
|
|
&ifr.ifr_broadaddr).sin_addr.s_addr = dev->pa_brdaddr;
|
|
(*(struct sockaddr_in *)
|
|
&ifr.ifr_broadaddr).sin_family = dev->family;
|
|
(*(struct sockaddr_in *)
|
|
&ifr.ifr_broadaddr).sin_port = 0;
|
|
memcpy_tofs(arg, &ifr, sizeof(struct ifreq));
|
|
ret = 0;
|
|
break;
|
|
case SIOCSIFBRDADDR:
|
|
dev->pa_brdaddr = (*(struct sockaddr_in *)
|
|
&ifr.ifr_broadaddr).sin_addr.s_addr;
|
|
ret = 0;
|
|
break;
|
|
case SIOCGIFDSTADDR:
|
|
(*(struct sockaddr_in *)
|
|
&ifr.ifr_dstaddr).sin_addr.s_addr = dev->pa_dstaddr;
|
|
(*(struct sockaddr_in *)
|
|
&ifr.ifr_broadaddr).sin_family = dev->family;
|
|
(*(struct sockaddr_in *)
|
|
&ifr.ifr_broadaddr).sin_port = 0;
|
|
memcpy_tofs(arg, &ifr, sizeof(struct ifreq));
|
|
ret = 0;
|
|
break;
|
|
case SIOCSIFDSTADDR:
|
|
dev->pa_dstaddr = (*(struct sockaddr_in *)
|
|
&ifr.ifr_dstaddr).sin_addr.s_addr;
|
|
ret = 0;
|
|
break;
|
|
case SIOCGIFNETMASK:
|
|
(*(struct sockaddr_in *)
|
|
&ifr.ifr_netmask).sin_addr.s_addr = dev->pa_mask;
|
|
(*(struct sockaddr_in *)
|
|
&ifr.ifr_netmask).sin_family = dev->family;
|
|
(*(struct sockaddr_in *)
|
|
&ifr.ifr_netmask).sin_port = 0;
|
|
memcpy_tofs(arg, &ifr, sizeof(struct ifreq));
|
|
ret = 0;
|
|
break;
|
|
case SIOCSIFNETMASK: {
|
|
unsigned long mask = (*(struct sockaddr_in *)
|
|
&ifr.ifr_netmask).sin_addr.s_addr;
|
|
ret = -EINVAL;
|
|
if (bad_mask(mask,0))
|
|
break;
|
|
dev->pa_mask = mask;
|
|
ret = 0;
|
|
break;
|
|
}
|
|
case SIOCGIFMETRIC:
|
|
ifr.ifr_metric = dev->metric;
|
|
memcpy_tofs(arg, &ifr, sizeof(struct ifreq));
|
|
ret = 0;
|
|
break;
|
|
case SIOCSIFMETRIC:
|
|
dev->metric = ifr.ifr_metric;
|
|
ret = 0;
|
|
break;
|
|
case SIOCGIFMTU:
|
|
ifr.ifr_mtu = dev->mtu;
|
|
memcpy_tofs(arg, &ifr, sizeof(struct ifreq));
|
|
ret = 0;
|
|
break;
|
|
case SIOCSIFMTU:
|
|
dev->mtu = ifr.ifr_mtu;
|
|
ret = 0;
|
|
break;
|
|
case SIOCGIFMEM:
|
|
printk("NET: ioctl(SIOCGIFMEM, 0x%08X)\n", (int)arg);
|
|
ret = -EINVAL;
|
|
break;
|
|
case SIOCSIFMEM:
|
|
printk("NET: ioctl(SIOCSIFMEM, 0x%08X)\n", (int)arg);
|
|
ret = -EINVAL;
|
|
break;
|
|
case SIOCGIFHWADDR:
|
|
memcpy(ifr.ifr_hwaddr,dev->dev_addr, MAX_ADDR_LEN);
|
|
memcpy_tofs(arg,&ifr,sizeof(struct ifreq));
|
|
ret=0;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
return(ret);
|
|
}
|
|
|
|
|
|
/* This function handles all "interface"-type I/O control requests. */
|
|
int
|
|
dev_ioctl(unsigned int cmd, void *arg)
|
|
{
|
|
struct iflink iflink;
|
|
struct ddi_device *dev;
|
|
int ret;
|
|
|
|
switch(cmd) {
|
|
case IP_SET_DEV:
|
|
printk("Your network configuration program needs upgrading.\n");
|
|
return -EINVAL;
|
|
case SIOCGIFCONF:
|
|
(void) dev_ifconf((char *) arg);
|
|
ret = 0;
|
|
break;
|
|
case SIOCGIFFLAGS:
|
|
case SIOCSIFFLAGS:
|
|
case SIOCGIFADDR:
|
|
case SIOCSIFADDR:
|
|
case SIOCGIFDSTADDR:
|
|
case SIOCSIFDSTADDR:
|
|
case SIOCGIFBRDADDR:
|
|
case SIOCSIFBRDADDR:
|
|
case SIOCGIFNETMASK:
|
|
case SIOCSIFNETMASK:
|
|
case SIOCGIFMETRIC:
|
|
case SIOCSIFMETRIC:
|
|
case SIOCGIFMTU:
|
|
case SIOCSIFMTU:
|
|
case SIOCGIFMEM:
|
|
case SIOCSIFMEM:
|
|
case SIOCGIFHWADDR:
|
|
if (!suser()) return(-EPERM);
|
|
ret = dev_ifsioc(arg, cmd);
|
|
break;
|
|
case SIOCSIFLINK:
|
|
if (!suser()) return(-EPERM);
|
|
memcpy_fromfs(&iflink, arg, sizeof(iflink));
|
|
dev = ddi_map(iflink.id);
|
|
if (dev == NULL) return(-EINVAL);
|
|
|
|
/* Now allocate an interface and connect it. */
|
|
printk("AF_INET: DDI \"%s\" linked to stream \"%s\"\n",
|
|
dev->name, iflink.stream);
|
|
ret = 0;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
|
|
/* Initialize the DEV module. */
|
|
void
|
|
dev_init(void)
|
|
{
|
|
struct device *dev, *dev2;
|
|
|
|
/* Add the devices.
|
|
* If the call to dev->init fails, the dev is removed
|
|
* from the chain disconnecting the device until the
|
|
* next reboot.
|
|
*/
|
|
dev2 = NULL;
|
|
for (dev = dev_base; dev != NULL; dev=dev->next) {
|
|
if (dev->init && dev->init(dev)) {
|
|
if (dev2 == NULL) dev_base = dev->next;
|
|
else dev2->next = dev->next;
|
|
} else {
|
|
dev2 = dev;
|
|
}
|
|
}
|
|
|
|
/* Set up some IP addresses. */
|
|
ip_bcast = in_aton("255.255.255.255");
|
|
}
|