| /* |
| * 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_interruptible(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); |
| cli(); |
| if (!--skb->lock) { |
| 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; |
| } |