blob: 506e95da1d74a3f3a44a274e1e106a5ec32e4386 [file] [log] [blame]
/*
* 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.
*
* A saner implementation of the skbuff stuff scattered everywhere
* in the old NET2D code.
*
* Authors: Alan Cox <iiitac@pyr.swan.ac.uk>
*
* Fixes:
* Alan Cox : Tracks memory and number of buffers for kernel memory report
* and memory leak hunting.
* Alan Cox : More generic kfree handler
*/
#include <linux/config.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/in.h>
#include "inet.h"
#include "dev.h"
#include "ip.h"
#include "protocol.h"
#include "arp.h"
#include "route.h"
#include "tcp.h"
#include "udp.h"
#include "skbuff.h"
#include "sock.h"
/* Socket buffer operations. Ideally much of this list swap stuff ought to be using
exch instructions on the 386, and CAS/CAS2 on a 68K. This is the boring generic
slow C version. No doubt when Linus sees this comment he'll do horrible things
to this code 8-)
*/
/*
* Resource tracking variables
*/
volatile unsigned long net_memory=0;
volatile unsigned long net_skbcount=0;
/*
* Debugging paranoia. Can go later when this crud stack works
*/
void skb_check(struct sk_buff *skb, int line, char *file)
{
if(skb->magic_debug_cookie==SK_FREED_SKB)
{
printk("File: %s Line %d, found a freed skb lurking in the undergrowth!\n",
file,line);
printk("skb=%p, real size=%ld, claimed size=%ld, magic=%d, list=%p, free=%d\n",
skb,skb->truesize,skb->mem_len,skb->magic,skb->list,skb->free);
}
if(skb->magic_debug_cookie!=SK_GOOD_SKB)
{
printk("File: %s Line %d, passed a non skb!\n", file,line);
printk("skb=%p, real size=%ld, claimed size=%ld, magic=%d, list=%p, free=%d\n",
skb,skb->truesize,skb->mem_len,skb->magic,skb->list,skb->free);
}
if(skb->mem_len!=skb->truesize)
{
printk("File: %s Line %d, Dubious size setting!\n",file,line);
printk("skb=%p, real size=%ld, claimed size=%ld, magic=%d, list=%p\n",
skb,skb->truesize,skb->mem_len,skb->magic,skb->list);
}
/* Guess it might be acceptable then */
}
/*
* Insert an sk_buff at the start of a list.
*/
void skb_queue_head(struct sk_buff *volatile* list,struct sk_buff *newsk)
{
unsigned long flags;
IS_SKB(newsk);
if(newsk->list)
printk("Suspicious queue head: sk_buff on list!\n");
save_flags(flags);
cli();
newsk->list=list;
newsk->next=*list;
if(*list)
newsk->prev=(*list)->prev;
else
newsk->prev=newsk;
newsk->prev->next=newsk;
newsk->next->prev=newsk;
IS_SKB(newsk->prev);
IS_SKB(newsk->next);
*list=newsk;
restore_flags(flags);
}
/*
* Insert an sk_buff at the end of a list.
*/
void skb_queue_tail(struct sk_buff *volatile* list, struct sk_buff *newsk)
{
unsigned long flags;
if(newsk->list)
printk("Suspicious queue tail: sk_buff on list!\n");
IS_SKB(newsk);
save_flags(flags);
cli();
newsk->list=list;
if(*list)
{
(*list)->prev->next=newsk;
newsk->prev=(*list)->prev;
newsk->next=*list;
(*list)->prev=newsk;
}
else
{
newsk->next=newsk;
newsk->prev=newsk;
*list=newsk;
}
IS_SKB(newsk->prev);
IS_SKB(newsk->next);
restore_flags(flags);
}
/*
* Remove an sk_buff from a list. This routine is also interrupt safe
* so you can grab read and free buffers as another process adds them.
*/
struct sk_buff *skb_dequeue(struct sk_buff *volatile* list)
{
long flags;
struct sk_buff *result;
save_flags(flags);
cli();
if(*list==NULL)
{
restore_flags(flags);
return(NULL);
}
result=*list;
if(result->next==result)
*list=NULL;
else
{
result->next->prev=result->prev;
result->prev->next=result->next;
*list=result->next;
}
IS_SKB(result);
restore_flags(flags);
if(result->list!=list)
printk("Dequeued packet has invalid list pointer\n");
result->list=0;
result->next=0;
result->prev=0;
return(result);
}
/*
* Insert a packet before another one in a list.
*/
void skb_insert(struct sk_buff *old, struct sk_buff *newsk)
{
unsigned long flags;
IS_SKB(old);
IS_SKB(newsk);
if(!old->list)
printk("insert before unlisted item!\n");
if(newsk->list)
printk("inserted item is already on a list.\n");
save_flags(flags);
cli();
newsk->list=old->list;
newsk->next=old;
newsk->prev=old->prev;
newsk->next->prev=newsk;
newsk->prev->next=newsk;
restore_flags(flags);
}
/*
* Place a packet after a given packet in a list.
*/
void skb_append(struct sk_buff *old, struct sk_buff *newsk)
{
unsigned long flags;
IS_SKB(old);
IS_SKB(newsk);
if(!old->list)
printk("append before unlisted item!\n");
if(newsk->list)
printk("append item is already on a list.\n");
save_flags(flags);
cli();
newsk->list=old->list;
newsk->prev=old;
newsk->next=old->next;
newsk->next->prev=newsk;
newsk->prev->next=newsk;
restore_flags(flags);
}
/*
* Remove an sk_buff from its list. Works even without knowing the list it
* is sitting on, which can be handy at times. It also means that THE LIST
* MUST EXIST when you unlink. Thus a list must have its contents unlinked
* _FIRST_.
*/
void skb_unlink(struct sk_buff *skb)
{
unsigned long flags;
save_flags(flags);
cli();
IS_SKB(skb);
if(skb->list)
{
skb->next->prev=skb->prev;
skb->prev->next=skb->next;
if(*skb->list==skb)
{
if(skb->next==skb)
*skb->list=NULL;
else
*skb->list=skb->next;
}
skb->next=0;
skb->prev=0;
skb->list=0;
}
restore_flags(flags);
}
/*
* An skbuff list has had its head reassigned. Move all the list
* pointers. Must be called with ints off during the whole head
* shifting
*/
void skb_new_list_head(struct sk_buff *volatile* list)
{
struct sk_buff *skb=skb_peek(list);
if(skb!=NULL)
{
do
{
IS_SKB(skb);
skb->list=list;
skb=skb->next;
}
while(skb!=*list);
}
}
/*
* Peek an sk_buff. Unlike most other operations you _MUST_
* be careful with this one. A peek leaves the buffer on the
* list and someone else may run off with it. For an interrupt
* type system cli() peek the buffer copy the data and sti();
*/
struct sk_buff *skb_peek(struct sk_buff *volatile* list)
{
return *list;
}
/*
* Get a clone of an sk_buff. This is the safe way to peek at
* a socket queue without accidents. Its a bit long but most
* of it acutally ends up as tiny bits of inline assembler
* anyway. Only the memcpy of upto 4K with ints off is not
* as nice as I'd like.
*/
struct sk_buff *skb_peek_copy(struct sk_buff *volatile* list)
{
struct sk_buff *orig,*newsk;
unsigned long flags;
unsigned int len;
/* Now for some games to avoid races */
do
{
save_flags(flags);
cli();
orig=skb_peek(list);
if(orig==NULL)
{
restore_flags(flags);
return NULL;
}
IS_SKB(orig);
len=orig->truesize;
restore_flags(flags);
newsk=alloc_skb(len,GFP_KERNEL); /* May sleep */
if(newsk==NULL) /* Oh dear... not to worry */
return NULL;
save_flags(flags);
cli();
if(skb_peek(list)!=orig) /* List changed go around another time */
{
restore_flags(flags);
newsk->sk=NULL;
newsk->free=1;
newsk->mem_addr=newsk;
newsk->mem_len=len;
kfree_skb(newsk, FREE_WRITE);
continue;
}
IS_SKB(orig);
IS_SKB(newsk);
memcpy(newsk,orig,len);
newsk->list=NULL;
newsk->magic=0;
newsk->next=NULL;
newsk->prev=NULL;
newsk->mem_addr=newsk;
newsk->h.raw+=((char *)newsk-(char *)orig);
newsk->link3=NULL;
newsk->sk=NULL;
newsk->free=1;
}
while(0);
restore_flags(flags);
return(newsk);
}
/*
* Free an sk_buff. This still knows about things it should
* not need to like protocols and sockets.
*/
void kfree_skb(struct sk_buff *skb, int rw)
{
if (skb == NULL) {
printk("kfree_skb: skb = NULL\n");
return;
}
IS_SKB(skb);
if(skb->lock)
{
skb->free=1; /* Free when unlocked */
return;
}
if(skb->free == 2)
printk("Warning: kfree_skb passed an skb that nobody set the free flag on!\n");
if(skb->list)
printk("Warning: kfree_skb passed an skb still on a list.\n");
skb->magic = 0;
if (skb->sk)
{
if(skb->sk->prot!=NULL)
{
if (rw)
skb->sk->prot->rfree(skb->sk, skb->mem_addr, skb->mem_len);
else
skb->sk->prot->wfree(skb->sk, skb->mem_addr, skb->mem_len);
}
else
{
/* Non INET - default wmalloc/rmalloc handler */
if (rw)
skb->sk->rmem_alloc-=skb->mem_len;
else
skb->sk->wmem_alloc-=skb->mem_len;
if(!skb->sk->dead)
wake_up(skb->sk->sleep);
kfree_skbmem(skb->mem_addr,skb->mem_len);
}
}
else
kfree_skbmem(skb->mem_addr, skb->mem_len);
}
/*
* Allocate a new skbuff. We do this ourselves so we can fill in a few 'private'
* fields and also do memory statistics to find all the [BEEP] leaks.
*/
struct sk_buff *alloc_skb(unsigned int size,int priority)
{
struct sk_buff *skb=(struct sk_buff *)kmalloc(size,priority);
if(skb==NULL)
return NULL;
skb->free= 2; /* Invalid so we pick up forgetful users */
skb->list= 0; /* Not on a list */
skb->lock= 0;
skb->truesize=size;
skb->mem_len=size;
skb->mem_addr=skb;
skb->fraglist=NULL;
net_memory+=size;
net_skbcount++;
skb->magic_debug_cookie=SK_GOOD_SKB;
skb->users=0;
return skb;
}
/*
* Free an skbuff by memory
*/
void kfree_skbmem(void *mem,unsigned size)
{
struct sk_buff *x=mem;
IS_SKB(x);
if(x->magic_debug_cookie==SK_GOOD_SKB)
{
x->magic_debug_cookie=SK_FREED_SKB;
kfree_s(mem,size);
net_skbcount--;
net_memory-=size;
}
}
/*
* Skbuff device locking
*/
void skb_kept_by_device(struct sk_buff *skb)
{
skb->lock++;
}
void skb_device_release(struct sk_buff *skb, int mode)
{
unsigned long flags;
save_flags(flags);
skb->lock--;
if(skb->lock==0)
{
if(skb->free==1)
kfree_skb(skb,mode);
}
restore_flags(flags);
}
int skb_device_locked(struct sk_buff *skb)
{
if(skb->lock)
return 1;
return 0;
}