| /* |
| * 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. |
| * |
| * The User Datagram Protocol (UDP). |
| * |
| * Version: @(#)udp.c 1.0.13 06/02/93 |
| * |
| * Authors: Ross Biro, <bir7@leland.Stanford.Edu> |
| * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG> |
| * |
| * 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/system.h> |
| #include <asm/segment.h> |
| #include <linux/types.h> |
| #include <linux/sched.h> |
| #include <linux/fcntl.h> |
| #include <linux/socket.h> |
| #include <linux/sockios.h> |
| #include <linux/in.h> |
| #include <linux/errno.h> |
| #include <linux/timer.h> |
| #include <linux/termios.h> |
| #include <linux/mm.h> |
| #include "inet.h" |
| #include "dev.h" |
| #include "ip.h" |
| #include "protocol.h" |
| #include "tcp.h" |
| #include "skbuff.h" |
| #include "sock.h" |
| #include "udp.h" |
| #include "icmp.h" |
| |
| |
| #define min(a,b) ((a)<(b)?(a):(b)) |
| |
| |
| static void |
| print_udp(struct udphdr *uh) |
| { |
| if (inet_debug != DBG_UDP) return; |
| |
| if (uh == NULL) { |
| printk("(NULL)\n"); |
| return; |
| } |
| printk("UDP: source = %d, dest = %d\n", ntohs(uh->source), ntohs(uh->dest)); |
| printk(" len = %d, check = %d\n", ntohs(uh->len), ntohs(uh->check)); |
| } |
| |
| |
| int |
| udp_select(struct sock *sk, int sel_type, select_table *wait) |
| { |
| select_wait(sk->sleep, wait); |
| switch(sel_type) { |
| case SEL_IN: |
| if (sk->rqueue != NULL) { |
| return(1); |
| } |
| return(0); |
| |
| case SEL_OUT: |
| if (sk->prot->wspace(sk) >= MIN_WRITE_SPACE) { |
| return(1); |
| } |
| return(0); |
| |
| case SEL_EX: |
| if (sk->err) return(1); /* can this ever happen? */ |
| return(0); |
| } |
| return(0); |
| } |
| |
| |
| /* |
| * This routine is called by the ICMP module when it gets some |
| * sort of error condition. If err < 0 then the socket should |
| * be closed and the error returned to the user. If err > 0 |
| * it's just the icmp type << 8 | icmp code. |
| * header points to the first 8 bytes of the tcp header. We need |
| * to find the appropriate port. |
| */ |
| void |
| udp_err(int err, unsigned char *header, unsigned long daddr, |
| unsigned long saddr, struct inet_protocol *protocol) |
| { |
| struct udphdr *th; |
| struct sock *sk; |
| |
| DPRINTF((DBG_UDP, |
| "UDP: err(err=%d, header=%X, daddr=%X, saddr=%X, protocl=%X)\n", |
| err, header, daddr, saddr, protocol)); |
| |
| th = (struct udphdr *)header; |
| sk = get_sock(&udp_prot, th->dest, saddr, th->source, daddr); |
| |
| if (sk == NULL) return; |
| 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; |
| |
| /* It's only fatal if we have connected to them. */ |
| if (icmp_err_convert[err & 0xff].fatal && sk->state == TCP_ESTABLISHED) { |
| sk->prot->close(sk, 0); |
| } |
| } |
| |
| |
| static unsigned short |
| udp_check(struct udphdr *uh, int len, |
| unsigned long saddr, unsigned long daddr) |
| { |
| unsigned long sum; |
| |
| DPRINTF((DBG_UDP, "UDP: check(uh=%X, len = %d, saddr = %X, daddr = %X)\n", |
| uh, len, saddr, daddr)); |
| |
| print_udp(uh); |
| |
| __asm__("\t addl %%ecx,%%ebx\n" |
| "\t adcl %%edx,%%ebx\n" |
| "\t adcl $0, %%ebx\n" |
| : "=b"(sum) |
| : "0"(daddr), "c"(saddr), "d"((ntohs(len) << 16) + IPPROTO_UDP*256) |
| : "cx","bx","dx" ); |
| |
| if (len > 3) { |
| __asm__("\tclc\n" |
| "1:\n" |
| "\t lodsl\n" |
| "\t adcl %%eax, %%ebx\n" |
| "\t loop 1b\n" |
| "\t adcl $0, %%ebx\n" |
| : "=b"(sum) , "=S"(uh) |
| : "0"(sum), "c"(len/4) ,"1"(uh) |
| : "ax", "cx", "bx", "si" ); |
| } |
| |
| /* Convert from 32 bits to 16 bits. */ |
| __asm__("\t movl %%ebx, %%ecx\n" |
| "\t shrl $16,%%ecx\n" |
| "\t addw %%cx, %%bx\n" |
| "\t adcw $0, %%bx\n" |
| : "=b"(sum) |
| : "0"(sum) |
| : "bx", "cx"); |
| |
| /* Check for an extra word. */ |
| if ((len & 2) != 0) { |
| __asm__("\t lodsw\n" |
| "\t addw %%ax,%%bx\n" |
| "\t adcw $0, %%bx\n" |
| : "=b"(sum), "=S"(uh) |
| : "0"(sum) ,"1"(uh) |
| : "si", "ax", "bx"); |
| } |
| |
| /* Now check for the extra byte. */ |
| if ((len & 1) != 0) { |
| __asm__("\t lodsb\n" |
| "\t movb $0,%%ah\n" |
| "\t addw %%ax,%%bx\n" |
| "\t adcw $0, %%bx\n" |
| : "=b"(sum) |
| : "0"(sum) ,"S"(uh) |
| : "si", "ax", "bx"); |
| } |
| |
| /* We only want the bottom 16 bits, but we never cleared the top 16. */ |
| return((~sum) & 0xffff); |
| } |
| |
| |
| static void |
| udp_send_check(struct udphdr *uh, unsigned long saddr, |
| unsigned long daddr, int len, struct sock *sk) |
| { |
| uh->check = 0; |
| if (sk && sk->no_check) return; |
| uh->check = udp_check(uh, len, saddr, daddr); |
| } |
| |
| |
| static int |
| udp_send(struct sock *sk, struct sockaddr_in *sin, |
| unsigned char *from, int len) |
| { |
| struct sk_buff *skb; |
| struct device *dev; |
| struct udphdr *uh; |
| unsigned char *buff; |
| unsigned long saddr; |
| int size, tmp; |
| int err; |
| |
| DPRINTF((DBG_UDP, "UDP: send(dst=%s:%d buff=%X len=%d)\n", |
| in_ntoa(sin->sin_addr.s_addr), ntohs(sin->sin_port), |
| from, len)); |
| |
| /* Allocate a copy of the packet. */ |
| size = sizeof(struct sk_buff) + sk->prot->max_header + len; |
| skb = (struct sk_buff *) sk->prot->wmalloc(sk, size, 0, GFP_KERNEL); |
| if (skb == NULL) return(-ENOMEM); |
| |
| skb->lock = 0; |
| skb->mem_addr = skb; |
| skb->mem_len = size; |
| skb->sk = NULL; |
| skb->free = 1; |
| skb->arp = 0; |
| |
| /* Now build the IP and MAC header. */ |
| buff = (unsigned char *) (skb+1); |
| saddr = 0; |
| dev = NULL; |
| DPRINTF((DBG_UDP, "UDP: >> IP_Header: %X -> %X dev=%X prot=%X len=%d\n", |
| saddr, sin->sin_addr.s_addr, dev, IPPROTO_UDP, skb->mem_len)); |
| tmp = sk->prot->build_header(skb, saddr, sin->sin_addr.s_addr, |
| &dev, IPPROTO_UDP, sk->opt, skb->mem_len); |
| if (tmp < 0 ) { |
| sk->prot->wfree(sk, skb->mem_addr, skb->mem_len); |
| return(tmp); |
| } |
| buff += tmp; |
| saddr = dev->pa_addr; |
| DPRINTF((DBG_UDP, "UDP: >> MAC+IP len=%d\n", tmp)); |
| |
| skb->len = tmp + sizeof(struct udphdr) + len; /* len + UDP + IP + MAC */ |
| skb->dev = dev; |
| |
| /* |
| * This code used to hack in some form of fragmentation. |
| * I removed that, since it didn't work anyway, and it made the |
| * code a bad thing to read and understand. -FvK |
| */ |
| if (len > dev->mtu) { |
| printk("UDP: send: length %d > mtu %d (ignored)\n", len, dev->mtu); |
| sk->prot->wfree(sk, skb->mem_addr, skb->mem_len); |
| return(-EINVAL); |
| } |
| |
| /* Fill in the UDP header. */ |
| uh = (struct udphdr *) buff; |
| uh->len = htons(len + sizeof(struct udphdr)); |
| uh->source = sk->dummy_th.source; |
| uh->dest = sin->sin_port; |
| buff = (unsigned char *) (uh + 1); |
| |
| /* Copy the user data. */ |
| err=verify_area(VERIFY_READ, from, len); |
| if(err) |
| return(err); |
| memcpy_fromfs(buff, from, len); |
| |
| /* Set up the UDP checksum. */ |
| udp_send_check(uh, saddr, sin->sin_addr.s_addr, skb->len - tmp, sk); |
| |
| /* Send the datagram to the interface. */ |
| sk->prot->queue_xmit(sk, dev, skb, 1); |
| |
| return(len); |
| } |
| |
| |
| static int |
| udp_sendto(struct sock *sk, unsigned char *from, int len, int noblock, |
| unsigned flags, struct sockaddr_in *usin, int addr_len) |
| { |
| struct sockaddr_in sin; |
| int tmp; |
| int err; |
| |
| DPRINTF((DBG_UDP, "UDP: sendto(len=%d, flags=%X)\n", len, flags)); |
| |
| /* Check the flags. */ |
| if (flags) return(-EINVAL); |
| if (len < 0) return(-EINVAL); |
| if (len == 0) return(0); |
| |
| /* Get and verify the address. */ |
| if (usin) { |
| if (addr_len < sizeof(sin)) return(-EINVAL); |
| err=verify_area(VERIFY_READ, usin, sizeof(sin)); |
| if(err) |
| return err; |
| memcpy_fromfs(&sin, usin, sizeof(sin)); |
| if (sin.sin_family && sin.sin_family != AF_INET) return(-EINVAL); |
| if (sin.sin_port == 0) return(-EINVAL); |
| } else { |
| if (sk->state != TCP_ESTABLISHED) return(-EINVAL); |
| sin.sin_family = AF_INET; |
| sin.sin_port = sk->dummy_th.dest; |
| sin.sin_addr.s_addr = sk->daddr; |
| } |
| sk->inuse = 1; |
| |
| /* Send the packet. */ |
| tmp = udp_send(sk, &sin, from, len); |
| |
| /* The datagram has been sent off. Release the socket. */ |
| release_sock(sk); |
| return(tmp); |
| } |
| |
| |
| static int |
| udp_write(struct sock *sk, unsigned char *buff, int len, int noblock, |
| unsigned flags) |
| { |
| return(udp_sendto(sk, buff, len, noblock, flags, NULL, 0)); |
| } |
| |
| |
| int |
| udp_ioctl(struct sock *sk, int cmd, unsigned long arg) |
| { |
| int err; |
| switch(cmd) { |
| case DDIOCSDBG: |
| { |
| int val; |
| |
| if (!suser()) return(-EPERM); |
| err=verify_area(VERIFY_READ, (void *)arg, sizeof(int)); |
| if(err) |
| return err; |
| val = get_fs_long((int *)arg); |
| switch(val) { |
| case 0: |
| inet_debug = 0; |
| break; |
| case 1: |
| inet_debug = DBG_UDP; |
| break; |
| default: |
| return(-EINVAL); |
| } |
| } |
| break; |
| case TIOCOUTQ: |
| { |
| unsigned long amount; |
| |
| if (sk->state == TCP_LISTEN) return(-EINVAL); |
| amount = sk->prot->wspace(sk)/2; |
| err=verify_area(VERIFY_WRITE,(void *)arg, |
| sizeof(unsigned long)); |
| if(err) |
| return(err); |
| put_fs_long(amount,(unsigned long *)arg); |
| return(0); |
| } |
| |
| case TIOCINQ: |
| #if 0 /* FIXME: */ |
| case FIONREAD: |
| #endif |
| { |
| struct sk_buff *skb; |
| unsigned long amount; |
| |
| if (sk->state == TCP_LISTEN) return(-EINVAL); |
| amount = 0; |
| skb = sk->rqueue; |
| if (skb != NULL) { |
| /* |
| * We will only return the amount |
| * of this packet since that is all |
| * that will be read. |
| */ |
| amount = skb->len; |
| } |
| err=verify_area(VERIFY_WRITE,(void *)arg, |
| sizeof(unsigned long)); |
| if(err) |
| return(err); |
| put_fs_long(amount,(unsigned long *)arg); |
| return(0); |
| } |
| |
| default: |
| return(-EINVAL); |
| } |
| return(0); |
| } |
| |
| |
| /* |
| * This should be easy, if there is something there we\ |
| * return it, otherwise we block. |
| */ |
| int |
| udp_recvfrom(struct sock *sk, unsigned char *to, int len, |
| int noblock, unsigned flags, struct sockaddr_in *sin, |
| int *addr_len) |
| { |
| int copied = 0; |
| struct sk_buff *skb; |
| int er; |
| |
| if (len == 0) return(0); |
| if (len < 0) return(-EINVAL); |
| |
| /* |
| * This will pick up errors that occured while the program |
| * was doing something else. |
| */ |
| if (sk->err) { |
| int err; |
| |
| err = -sk->err; |
| sk->err = 0; |
| return(err); |
| } |
| if (addr_len) { |
| er=verify_area(VERIFY_WRITE, addr_len, sizeof(*addr_len)); |
| if(er) |
| return(er); |
| put_fs_long(sizeof(*sin), addr_len); |
| } |
| if(sin) |
| { |
| er=verify_area(VERIFY_WRITE, sin, sizeof(*sin)); |
| if(er) |
| return(er); |
| } |
| er=verify_area(VERIFY_WRITE,to,len); |
| if(er) |
| return er; |
| sk->inuse = 1; |
| while(sk->rqueue == NULL) { |
| if (sk->shutdown & RCV_SHUTDOWN) { |
| return(0); |
| } |
| |
| if (noblock) { |
| release_sock(sk); |
| return(-EAGAIN); |
| } |
| release_sock(sk); |
| cli(); |
| if (sk->rqueue == NULL) { |
| interruptible_sleep_on(sk->sleep); |
| if (current->signal & ~current->blocked) { |
| 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); |
| /* This was checked in the wrong place |
| verify_area(VERIFY_WRITE, to, copied); |
| */ |
| memcpy_tofs(to, skb->h.raw + sizeof(struct udphdr), copied); |
| |
| /* Copy the address. */ |
| if (sin) { |
| struct sockaddr_in addr; |
| |
| addr.sin_family = AF_INET; |
| addr.sin_port = skb->h.uh->source; |
| addr.sin_addr.s_addr = skb->daddr; |
| /* Also in the wrong place, jumping out here will lose |
| a packet for good and leave the socket in use |
| now tested above |
| 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 |
| udp_read(struct sock *sk, unsigned char *buff, int len, int noblock, |
| unsigned flags) |
| { |
| return(udp_recvfrom(sk, buff, len, noblock, flags, NULL, NULL)); |
| } |
| |
| |
| int |
| udp_connect(struct sock *sk, struct sockaddr_in *usin, int addr_len) |
| { |
| struct sockaddr_in sin; |
| int er; |
| |
| if (addr_len < sizeof(sin)) return(-EINVAL); |
| |
| er=verify_area(VERIFY_READ, usin, sizeof(sin)); |
| if(er) |
| return er; |
| |
| memcpy_fromfs(&sin, usin, sizeof(sin)); |
| if (sin.sin_family && sin.sin_family != AF_INET) return(-EAFNOSUPPORT); |
| sk->daddr = sin.sin_addr.s_addr; |
| sk->dummy_th.dest = sin.sin_port; |
| sk->state = TCP_ESTABLISHED; |
| return(0); |
| } |
| |
| |
| static void |
| udp_close(struct sock *sk, int timeout) |
| { |
| sk->inuse = 1; |
| sk->state = TCP_CLOSE; |
| if (sk->dead) destroy_sock(sk); |
| else release_sock(sk); |
| } |
| |
| |
| /* All we need to do is get the socket, and then do a checksum. */ |
| int |
| udp_rcv(struct sk_buff *skb, struct device *dev, struct options *opt, |
| unsigned long daddr, unsigned short len, |
| unsigned long saddr, int redo, struct inet_protocol *protocol) |
| { |
| struct sock *sk; |
| struct udphdr *uh; |
| |
| uh = (struct udphdr *) skb->h.uh; |
| sk = get_sock(&udp_prot, uh->dest, saddr, uh->source, daddr); |
| if (sk == NULL) { |
| if (chk_addr(daddr) == IS_MYADDR) |
| icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, dev); |
| |
| /* |
| * Hmm. We got an UDP broadcast to a port to which we |
| * don't wanna listen. The only thing we can do now is |
| * to ignore the packet... -FvK |
| */ |
| skb->sk = NULL; |
| kfree_skb(skb, FREE_WRITE); |
| return(0); |
| } |
| |
| if (!redo) { |
| if (uh->check && udp_check(uh, len, saddr, daddr)) { |
| DPRINTF((DBG_UDP, "UDP: bad checksum\n")); |
| skb->sk = NULL; |
| kfree_skb(skb, FREE_WRITE); |
| return(0); |
| } |
| |
| skb->sk = sk; |
| skb->dev = dev; |
| skb->len = len; |
| |
| /* These are supposed to be switched. */ |
| skb->daddr = saddr; |
| skb->saddr = daddr; |
| |
| /* Now deal with the in use. */ |
| cli(); |
| if (sk->inuse) { |
| 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 to the socket. */ |
| if (sk->rmem_alloc + skb->mem_len >= SK_RMEM_MAX) { |
| skb->sk = NULL; |
| kfree_skb(skb, FREE_WRITE); |
| release_sock(sk); |
| return(0); |
| } |
| sk->rmem_alloc += skb->mem_len; |
| |
| /* At this point we should print the thing out. */ |
| DPRINTF((DBG_UDP, "<< \n")); |
| print_udp(uh); |
| |
| /* Now add it to the data chain and wake things up. */ |
| 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; |
| } |
| skb->len = len - sizeof(*uh); |
| |
| if (!sk->dead) wake_up(sk->sleep); |
| |
| release_sock(sk); |
| return(0); |
| } |
| |
| |
| struct proto udp_prot = { |
| sock_wmalloc, |
| sock_rmalloc, |
| sock_wfree, |
| sock_rfree, |
| sock_rspace, |
| sock_wspace, |
| udp_close, |
| udp_read, |
| udp_write, |
| udp_sendto, |
| udp_recvfrom, |
| ip_build_header, |
| udp_connect, |
| NULL, |
| ip_queue_xmit, |
| ip_retransmit, |
| NULL, |
| NULL, |
| udp_rcv, |
| udp_select, |
| udp_ioctl, |
| NULL, |
| NULL, |
| 128, |
| 0, |
| {NULL,}, |
| "UDP" |
| }; |