blob: a7c40d9e19e47776d1fb43a7de18c6e48d83f58f [file] [log] [blame]
/* raw.c - implements raw ip sockets. */
/*
Copyright (C) 1992 Ross Biro
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, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
The Author may be reached as bir7@leland.stanford.edu or
C/O Department of Mathematics; Stanford University; Stanford, CA 94305
*/
/* $Id: raw.c,v 0.8.4.12 1993/01/26 22:04:00 bir7 Exp $ */
/* $Log: raw.c,v $
* Revision 0.8.4.12 1993/01/26 22:04:00 bir7
* Added support for proc fs.
*
* Revision 0.8.4.11 1993/01/23 18:00:11 bir7
* Added volatile keyword
*
* Revision 0.8.4.10 1993/01/22 23:21:38 bir7
* Merged with 99 pl4
*
* Revision 0.8.4.9 1992/12/12 19:25:04 bir7
* Cleaned up Log messages.
*
* Revision 0.8.4.8 1992/12/12 01:50:49 bir7
* Fixed bug in call to err routine.
*
* Revision 0.8.4.7 1992/12/06 11:31:47 bir7
* added raw_err.
*
* Revision 0.8.4.6 1992/11/18 15:38:03 bir7
* Works now.
*
*
* Revision 0.8.4.4 1992/11/17 09:27:07 bir7
* Fixed error in header building.
*
* Revision 0.8.4.3 1992/11/16 16:13:40 bir7
* Added debuggin information.
*
* Revision 0.8.4.2 1992/11/10 10:38:48 bir7
* Change free_s to kfree_s and accidently changed free_skb to kfree_skb.
*
* Revision 0.8.4.1 1992/11/10 00:17:18 bir7
* version change only.
*
* Revision 0.8.3.3 1992/11/10 00:14:47 bir7
* Changed malloc to kmalloc and added Id and Log
* */
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/fcntl.h>
#include <linux/socket.h>
#include <netinet/in.h>
#include "timer.h"
#include "ip.h"
#include "tcp.h"
#include "sock.h"
#include <linux/errno.h>
#include <linux/timer.h>
#include <asm/system.h>
#include <asm/segment.h>
#include <linux/mm.h>
#include <linux/kernel.h>
#include "icmp.h"
#ifdef PRINTK
#undef PRINTK
#endif
#undef RAW_DEBUG
#ifdef RAW_DEBUG
#define PRINTK(x) printk x
#else
#define PRINTK(x) /**/
#endif
extern struct proto raw_prot;
static unsigned long
min(unsigned long a, unsigned long b)
{
if (a < b) return (a);
return (b);
}
/* raw_err gets called by the icmp module. */
void
raw_err (int err, unsigned char *header, unsigned long daddr,
unsigned long saddr, struct ip_protocol *protocol)
{
volatile struct sock *sk;
PRINTK (("raw_err (err=%d, header=%X, daddr=%X, saddr=%X, ip_protocl=%X)\n"));
if (protocol == NULL) return;
sk = protocol->data;
if (sk == NULL) return;
/* This is meaningless in raw sockets. */
if (err & 0xff00 == (ICMP_SOURCE_QUENCH << 8))
{
if (sk->cong_window > 1)
sk->cong_window = sk->cong_window/2;
return;
}
sk->err = icmp_err_convert[err & 0xff].errno;
/* none of them are fatal for raw sockets. */
/* if (icmp_err_convert[err & 0xff].fatal)
{
sk->prot->close(sk, 0);
} */
return;
}
/* this should be the easiest of all, all we do is copy it into
a buffer. */
int
raw_rcv (struct sk_buff *skb, struct device *dev, struct options *opt,
unsigned long daddr, unsigned short len, unsigned long saddr,
int redo, struct ip_protocol *protocol)
{
volatile struct sock *sk;
PRINTK (("raw_rcv (skb=%X, dev=%X, opt=%X, daddr=%X,\n"
" len=%d, saddr=%X, redo=%d, protocol=%X)\n",
skb, dev, opt, daddr, len, saddr, redo, protocol));
if (skb == NULL) return (0);
if (protocol == NULL)
{
kfree_skb (skb, FREE_READ);
return (0);
}
sk = protocol->data;
if (sk == NULL)
{
kfree_skb (skb, FREE_READ);
return (0);
}
/* now we need to copy this into memory. */
skb->sk = sk;
skb->len = len;
skb->dev = dev;
skb->saddr = daddr;
skb->daddr = saddr;
if (!redo )
{
/* now see if we are in use. */
cli();
if (sk->inuse)
{
PRINTK (("raw_rcv adding to backlog. \n"));
if (sk->back_log == NULL)
{
sk->back_log = skb;
skb->next = skb;
skb->prev = skb;
}
else
{
skb->next = sk->back_log;
skb->prev = sk->back_log->prev;
skb->prev->next = skb;
skb->next->prev = skb;
}
sti();
return (0);
}
sk->inuse = 1;
sti();
}
/* charge it too the socket. */
if (sk->rmem_alloc + skb->mem_len >= SK_RMEM_MAX)
{
skb->sk = NULL;
kfree_skb (skb, FREE_READ);
return (0);
}
sk->rmem_alloc += skb->mem_len;
/* now just put it onto the queue. */
if (sk->rqueue == NULL)
{
sk->rqueue = skb;
skb->next = skb;
skb->prev = skb;
}
else
{
skb->next = sk->rqueue;
skb->prev = sk->rqueue->prev;
skb->prev->next = skb;
skb->next->prev = skb;
}
wake_up (sk->sleep);
release_sock (sk);
return (0);
}
/* this will do terrible things if len + ipheader + devheader > dev->mtu */
static int
raw_sendto (volatile struct sock *sk, unsigned char *from, int len,
int noblock,
unsigned flags, struct sockaddr_in *usin, int addr_len)
{
struct sk_buff *skb;
struct device *dev=NULL;
struct sockaddr_in sin;
int tmp;
PRINTK (("raw_sendto (sk=%X, from=%X, len=%d, noblock=%d, flags=%X,\n"
" usin=%X, addr_len = %d)\n", sk, from, len, noblock,
flags, usin, addr_len));
/* check the flags. */
if (flags) return (-EINVAL);
if (len < 0) return (-EINVAL);
/* get and verify the address. */
if (usin)
{
if (addr_len < sizeof (sin))
return (-EINVAL);
/* verify_area (VERIFY_WRITE, usin, sizeof (sin));*/
memcpy_fromfs (&sin, usin, sizeof(sin));
if (sin.sin_family &&
sin.sin_family != AF_INET)
return (-EINVAL);
}
else
{
if (sk->state != TCP_ESTABLISHED)
return (-EINVAL);
sin.sin_family = AF_INET;
sin.sin_port = sk->protocol;
sin.sin_addr.s_addr = sk->daddr;
}
if (sin.sin_port == 0) sin.sin_port = sk->protocol;
sk->inuse = 1;
skb = NULL;
while (skb == NULL)
{
skb = sk->prot->wmalloc (sk, len+sizeof (*skb) + sk->prot->max_header,
0, GFP_KERNEL);
/* this shouldn't happen, but it could. */
/* need to change this to sleep. */
if (skb == NULL)
{
int tmp;
PRINTK (("raw_sendto: write buffer full?\n"));
if (noblock) return (-EAGAIN);
tmp = sk->wmem_alloc;
release_sock (sk);
cli();
if (tmp <= sk->wmem_alloc)
{
interruptible_sleep_on (sk->sleep);
if (current->signal & ~current->blocked)
{
sti();
return (-ERESTARTSYS);
}
}
sk->inuse = 1;
sti();
}
}
skb->lock = 0;
skb->mem_addr = skb;
skb->mem_len = len + sizeof (*skb) +sk->prot->max_header;
skb->sk = sk;
skb->free = 1; /* these two should be unecessary. */
skb->arp = 0;
tmp = sk->prot->build_header (skb, sk->saddr,
sin.sin_addr.s_addr, &dev,
sk->protocol, sk->opt, skb->mem_len);
if (tmp < 0)
{
PRINTK (("raw_sendto: error building ip header.\n"));
sk->prot->wfree (sk, skb->mem_addr, skb->mem_len);
release_sock (sk);
return (tmp);
}
/* verify_area (VERIFY_WRITE, from, len);*/
memcpy_fromfs ((unsigned char *)(skb+1)+tmp, from, len);
skb->len = tmp + len;
sk->prot->queue_xmit (sk, dev, skb, 1);
release_sock (sk);
return (len);
}
static int
raw_write (volatile struct sock *sk, unsigned char *buff, int len, int noblock,
unsigned flags)
{
return (raw_sendto (sk, buff, len, noblock, flags, NULL, 0));
}
static void
raw_close (volatile struct sock *sk, int timeout)
{
sk->inuse = 1;
sk->state = TCP_CLOSE;
PRINTK (("raw_close: deleting ip_protocol %d\n",
((struct ip_protocol *)sk->pair)->protocol));
if (delete_ip_protocol ((struct ip_protocol *)sk->pair) < 0)
PRINTK (("raw_close: delete_ip_protocol failed. \n"));
kfree_s ((void *)sk->pair, sizeof (struct ip_protocol));
sk->pair = NULL;
release_sock (sk);
}
static int
raw_init (volatile struct sock *sk)
{
struct ip_protocol *p;
p = kmalloc (sizeof (*p), GFP_KERNEL);
if (p == NULL) return (-ENOMEM);
p->handler = raw_rcv;
p->protocol = sk->protocol;
p->data = (void *)sk;
p->err_handler = raw_err;
add_ip_protocol (p);
/* we need to remember this somewhere. */
sk->pair = (volatile struct sock *)p;
PRINTK (("raw init added protocol %d\n", sk->protocol));
return (0);
}
int
raw_recvfrom (volatile struct sock *sk, unsigned char *to, int len,
int noblock,
unsigned flags, struct sockaddr_in *sin, int *addr_len)
{
/* this should be easy, if there is something there we
return it, otherwise we block. */
int copied=0;
struct sk_buff *skb;
PRINTK (("raw_recvfrom (sk=%X, to=%X, len=%d, noblock=%d, flags=%X,\n"
" sin=%X, addr_len=%X)\n", sk, to, len, noblock,
flags, sin, addr_len));
if (len == 0) return (0);
if (len < 0) return (-EINVAL);
if (sk->shutdown & RCV_SHUTDOWN) return (0);
if (addr_len)
{
verify_area (VERIFY_WRITE, addr_len, sizeof(*addr_len));
put_fs_long (sizeof (*sin), addr_len);
}
sk->inuse = 1;
while (sk->rqueue == NULL)
{
if (noblock)
{
release_sock (sk);
if (copied) return (copied);
return (-EAGAIN);
}
release_sock (sk);
cli();
if (sk->rqueue == NULL)
{
interruptible_sleep_on (sk->sleep);
if (current->signal & ~current->blocked)
{
sti();
return (-ERESTARTSYS);
}
}
sk->inuse = 1;
sti();
}
skb = sk->rqueue;
if (!(flags & MSG_PEEK))
{
if (skb->next == skb )
{
sk->rqueue = NULL;
}
else
{
sk->rqueue = (struct sk_buff *)sk->rqueue ->next;
skb->prev->next = skb->next;
skb->next->prev = skb->prev;
}
}
copied = min (len, skb->len);
verify_area (VERIFY_WRITE, to, copied);
memcpy_tofs (to, skb->h.raw, copied);
/* copy the address. */
if (sin)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = skb->daddr;
verify_area (VERIFY_WRITE, sin, sizeof (*sin));
memcpy_tofs(sin, &addr, sizeof (*sin));
}
if (!(flags & MSG_PEEK))
{
kfree_skb (skb, FREE_READ);
}
release_sock (sk);
return (copied);
}
int
raw_read (volatile struct sock *sk, unsigned char *buff, int len, int noblock,
unsigned flags)
{
return (raw_recvfrom (sk, buff, len, noblock, flags, NULL, NULL));
}
int udp_connect (volatile struct sock *sk, struct sockaddr_in *usin,
int addr_len);
int udp_select (volatile struct sock *sk, int sel_type, select_table *wait);
struct proto raw_prot =
{
sock_wmalloc,
sock_rmalloc,
sock_wfree,
sock_rfree,
sock_rspace,
sock_wspace,
raw_close,
raw_read,
raw_write,
raw_sendto,
raw_recvfrom,
ip_build_header,
udp_connect,
NULL,
ip_queue_xmit,
ip_retransmit,
NULL,
NULL,
raw_rcv,
udp_select,
NULL,
raw_init,
NULL,
128,
0,
{NULL,},
"RAW"
};