diff options
Diffstat (limited to 'net/core/sock.c')
-rw-r--r-- | net/core/sock.c | 542 |
1 files changed, 542 insertions, 0 deletions
diff --git a/net/core/sock.c b/net/core/sock.c new file mode 100644 index 000000000..a95586cbc --- /dev/null +++ b/net/core/sock.c @@ -0,0 +1,542 @@ +/* + * 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. + * + * Generic socket support routines. Memory allocators, sk->inuse/release + * handler for protocols to use and generic option handler. + * + * + * Version: @(#)sock.c 1.0.17 06/02/93 + * + * Authors: Ross Biro, <bir7@leland.Stanford.Edu> + * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG> + * Florian La Roche, <flla@stud.uni-sb.de> + * Alan Cox, <A.Cox@swansea.ac.uk> + * + * Fixes: + * Alan Cox : Numerous verify_area() problems + * Alan Cox : Connecting on a connecting socket + * now returns an error for tcp. + * Alan Cox : sock->protocol is set correctly. + * and is not sometimes left as 0. + * Alan Cox : connect handles icmp errors on a + * connect properly. Unfortunately there + * is a restart syscall nasty there. I + * can't match BSD without hacking the C + * library. Ideas urgently sought! + * Alan Cox : Disallow bind() to addresses that are + * not ours - especially broadcast ones!! + * Alan Cox : Socket 1024 _IS_ ok for users. (fencepost) + * Alan Cox : sock_wfree/sock_rfree don't destroy sockets, + * instead they leave that for the DESTROY timer. + * Alan Cox : Clean up error flag in accept + * Alan Cox : TCP ack handling is buggy, the DESTROY timer + * was buggy. Put a remove_sock() in the handler + * for memory when we hit 0. Also altered the timer + * code. The ACK stuff can wait and needs major + * TCP layer surgery. + * Alan Cox : Fixed TCP ack bug, removed remove sock + * and fixed timer/inet_bh race. + * Alan Cox : Added zapped flag for TCP + * Alan Cox : Move kfree_skb into skbuff.c and tidied up surplus code + * Alan Cox : for new sk_buff allocations wmalloc/rmalloc now call alloc_skb + * Alan Cox : kfree_s calls now are kfree_skbmem so we can track skb resources + * Alan Cox : Supports socket option broadcast now as does udp. Packet and raw need fixing. + * Alan Cox : Added RCVBUF,SNDBUF size setting. It suddenly occurred to me how easy it was so... + * Rick Sladkey : Relaxed UDP rules for matching packets. + * C.E.Hawkins : IFF_PROMISC/SIOCGHWADDR support + * Pauline Middelink : identd support + * Alan Cox : Fixed connect() taking signals I think. + * Alan Cox : SO_LINGER supported + * Alan Cox : Error reporting fixes + * Anonymous : inet_create tidied up (sk->reuse setting) + * Alan Cox : inet sockets don't set sk->type! + * Alan Cox : Split socket option code + * Alan Cox : Callbacks + * Alan Cox : Nagle flag for Charles & Johannes stuff + * Alex : Removed restriction on inet fioctl + * Alan Cox : Splitting INET from NET core + * Alan Cox : Fixed bogus SO_TYPE handling in getsockopt() + * Adam Caldwell : Missing return in SO_DONTROUTE/SO_DEBUG code + * Alan Cox : Split IP from generic code + * Alan Cox : New kfree_skbmem() + * Alan Cox : Make SO_DEBUG superuser only. + * Alan Cox : Allow anyone to clear SO_DEBUG + * (compatibility fix) + * + * To Fix: + * + * + * 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 <linux/config.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/socket.h> +#include <linux/in.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/string.h> +#include <linux/sockios.h> +#include <linux/net.h> +#include <linux/fcntl.h> +#include <linux/mm.h> +#include <linux/interrupt.h> + +#include <asm/segment.h> +#include <asm/system.h> + +#include <linux/inet.h> +#include <linux/netdevice.h> +#include <net/ip.h> +#include <net/protocol.h> +#include <net/arp.h> +#include <net/rarp.h> +#include <net/route.h> +#include <net/tcp.h> +#include <net/udp.h> +#include <linux/skbuff.h> +#include <net/sock.h> +#include <net/raw.h> +#include <net/icmp.h> + +#define min(a,b) ((a)<(b)?(a):(b)) + +/* + * This is meant for all protocols to use and covers goings on + * at the socket level. Everything here is generic. + */ + +int sock_setsockopt(struct sock *sk, int level, int optname, + char *optval, int optlen) +{ + int val; + int valbool; + int err; + struct linger ling; + + if (optval == NULL) + return(-EINVAL); + + err=verify_area(VERIFY_READ, optval, sizeof(int)); + if(err) + return err; + + val = get_fs_long((unsigned long *)optval); + valbool = val?1:0; + + switch(optname) + { + case SO_DEBUG: + if(val && !suser()) + return(-EPERM); + sk->debug=valbool; + return 0; + case SO_REUSEADDR: + sk->reuse = valbool; + return(0); + case SO_TYPE: + case SO_ERROR: + return(-ENOPROTOOPT); + case SO_DONTROUTE: + sk->localroute=valbool; + return 0; + case SO_BROADCAST: + sk->broadcast=valbool; + return 0; + case SO_SNDBUF: + if(val>32767) + val=32767; + if(val<256) + val=256; + sk->sndbuf=val; + return 0; + + case SO_RCVBUF: + if(val>32767) + val=32767; + if(val<256) + val=256; + sk->rcvbuf=val; + return(0); + + case SO_KEEPALIVE: + sk->keepopen = valbool; + return(0); + + case SO_OOBINLINE: + sk->urginline = valbool; + return(0); + + case SO_NO_CHECK: + sk->no_check = valbool; + return(0); + + case SO_PRIORITY: + if (val >= 0 && val < DEV_NUMBUFFS) + { + sk->priority = val; + } + else + { + return(-EINVAL); + } + return(0); + + + case SO_LINGER: + err=verify_area(VERIFY_READ,optval,sizeof(ling)); + if(err) + return err; + memcpy_fromfs(&ling,optval,sizeof(ling)); + if(ling.l_onoff==0) + sk->linger=0; + else + { + sk->lingertime=ling.l_linger; + sk->linger=1; + } + return 0; + + + default: + return(-ENOPROTOOPT); + } +} + + +int sock_getsockopt(struct sock *sk, int level, int optname, + char *optval, int *optlen) +{ + int val; + int err; + struct linger ling; + + switch(optname) + { + case SO_DEBUG: + val = sk->debug; + break; + + case SO_DONTROUTE: + val = sk->localroute; + break; + + case SO_BROADCAST: + val= sk->broadcast; + break; + + case SO_SNDBUF: + val=sk->sndbuf; + break; + + case SO_RCVBUF: + val =sk->rcvbuf; + break; + + case SO_REUSEADDR: + val = sk->reuse; + break; + + case SO_KEEPALIVE: + val = sk->keepopen; + break; + + case SO_TYPE: + val = sk->type; + break; + + case SO_ERROR: + val = sk->err; + sk->err = 0; + break; + + case SO_OOBINLINE: + val = sk->urginline; + break; + + case SO_NO_CHECK: + val = sk->no_check; + break; + + case SO_PRIORITY: + val = sk->priority; + break; + + case SO_LINGER: + err=verify_area(VERIFY_WRITE,optval,sizeof(ling)); + if(err) + return err; + err=verify_area(VERIFY_WRITE,optlen,sizeof(int)); + if(err) + return err; + put_fs_long(sizeof(ling),(unsigned long *)optlen); + ling.l_onoff=sk->linger; + ling.l_linger=sk->lingertime; + memcpy_tofs(optval,&ling,sizeof(ling)); + return 0; + + + + default: + return(-ENOPROTOOPT); + } + err=verify_area(VERIFY_WRITE, optlen, sizeof(int)); + if(err) + return err; + put_fs_long(sizeof(int),(unsigned long *) optlen); + + err=verify_area(VERIFY_WRITE, optval, sizeof(int)); + if(err) + return err; + put_fs_long(val,(unsigned long *)optval); + + return(0); +} + + +struct sk_buff *sock_wmalloc(struct sock *sk, unsigned long size, int force, int priority) +{ + if (sk) + { + if (sk->wmem_alloc + size < sk->sndbuf || force) + { + struct sk_buff * c = alloc_skb(size, priority); + if (c) + { + unsigned long flags; + save_flags(flags); + cli(); + sk->wmem_alloc+= c->mem_len; + restore_flags(flags); /* was sti(); */ + } + return c; + } + return(NULL); + } + return(alloc_skb(size, priority)); +} + + +struct sk_buff *sock_rmalloc(struct sock *sk, unsigned long size, int force, int priority) +{ + if (sk) + { + if (sk->rmem_alloc + size < sk->rcvbuf || force) + { + struct sk_buff *c = alloc_skb(size, priority); + if (c) + { + unsigned long flags; + save_flags(flags); + cli(); + sk->rmem_alloc += c->mem_len; + restore_flags(flags); /* was sti(); */ + } + return(c); + } + return(NULL); + } + return(alloc_skb(size, priority)); +} + + +unsigned long sock_rspace(struct sock *sk) +{ + int amt; + + if (sk != NULL) + { + if (sk->rmem_alloc >= sk->rcvbuf-2*MIN_WINDOW) + return(0); + amt = min((sk->rcvbuf-sk->rmem_alloc)/2-MIN_WINDOW, MAX_WINDOW); + if (amt < 0) + return(0); + return(amt); + } + return(0); +} + + +unsigned long sock_wspace(struct sock *sk) +{ + if (sk != NULL) + { + if (sk->shutdown & SEND_SHUTDOWN) + return(0); + if (sk->wmem_alloc >= sk->sndbuf) + return(0); + return(sk->sndbuf-sk->wmem_alloc ); + } + return(0); +} + + +void sock_wfree(struct sock *sk, struct sk_buff *skb, unsigned long size) +{ +#ifdef CONFIG_SKB_CHECK + IS_SKB(skb); +#endif + kfree_skbmem(skb, size); + if (sk) + { + unsigned long flags; + save_flags(flags); + cli(); + sk->wmem_alloc -= size; + restore_flags(flags); + /* In case it might be waiting for more memory. */ + sk->write_space(sk); + return; + } +} + + +void sock_rfree(struct sock *sk, struct sk_buff *skb, unsigned long size) +{ +#ifdef CONFIG_SKB_CHECK + IS_SKB(skb); +#endif + kfree_skbmem(skb, size); + if (sk) + { + unsigned long flags; + save_flags(flags); + cli(); + sk->rmem_alloc -= size; + restore_flags(flags); + } +} + +/* + * Generic send/receive buffer handlers + */ + +struct sk_buff *sock_alloc_send_skb(struct sock *sk, unsigned long size, int noblock, int *errcode) +{ + struct sk_buff *skb; + int err; + + sk->inuse=1; + + do + { + if(sk->err!=0) + { + cli(); + err= -sk->err; + sk->err=0; + sti(); + *errcode=err; + return NULL; + } + + if(sk->shutdown&SEND_SHUTDOWN) + { + *errcode=-EPIPE; + return NULL; + } + + skb = sock_wmalloc(sk, size, 0, GFP_KERNEL); + + if(skb==NULL) + { + unsigned long tmp; + + sk->socket->flags |= SO_NOSPACE; + if(noblock) + { + *errcode=-EAGAIN; + return NULL; + } + if(sk->shutdown&SEND_SHUTDOWN) + { + *errcode=-EPIPE; + return NULL; + } + tmp = sk->wmem_alloc; + cli(); + if(sk->shutdown&SEND_SHUTDOWN) + { + sti(); + *errcode=-EPIPE; + return NULL; + } + + if( tmp <= sk->wmem_alloc) + { + sk->socket->flags &= ~SO_NOSPACE; + interruptible_sleep_on(sk->sleep); + if (current->signal & ~current->blocked) + { + sti(); + *errcode = -ERESTARTSYS; + return NULL; + } + } + sti(); + } + } + while(skb==NULL); + + return skb; +} + + +void release_sock(struct sock *sk) +{ + unsigned long flags; +#ifdef CONFIG_INET + struct sk_buff *skb; +#endif + + if (!sk->prot) + return; + /* + * Make the backlog atomic. If we don't do this there is a tiny + * window where a packet may arrive between the sk->blog being + * tested and then set with sk->inuse still 0 causing an extra + * unwanted re-entry into release_sock(). + */ + + save_flags(flags); + cli(); + if (sk->blog) + { + restore_flags(flags); + return; + } + sk->blog=1; + sk->inuse = 1; + restore_flags(flags); +#ifdef CONFIG_INET + /* See if we have any packets built up. */ + while((skb = skb_dequeue(&sk->back_log)) != NULL) + { + sk->blog = 1; + if (sk->prot->rcv) + sk->prot->rcv(skb, skb->dev, sk->opt, + skb->saddr, skb->len, skb->daddr, 1, + /* Only used for/by raw sockets. */ + (struct inet_protocol *)sk->pair); + } +#endif + sk->blog = 0; + sk->inuse = 0; +#ifdef CONFIG_INET + if (sk->dead && sk->state == TCP_CLOSE) + { + /* Should be about 2 rtt's */ + reset_timer(sk, TIME_DONE, min(sk->rtt * 2, TCP_DONE_TIME)); + } +#endif +} + + |