2.11BSD/sys/netinet/udp_usrreq.c
/*
* Copyright (c) 1982, 1986 Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms are permitted
* provided that this notice is preserved and that due credit is given
* to the University of California at Berkeley. The name of the University
* may not be used to endorse or promote products derived from this
* software without specific prior written permission. This software
* is provided ``as is'' without express or implied warranty.
*
* @(#)udp_usrreq.c 7.5.2 (2.11BSD GTE) 1995/10/09
*/
#include "param.h"
#include "user.h"
#include "mbuf.h"
#include "protosw.h"
#include "socket.h"
#include "socketvar.h"
#include "errno.h"
#include "../net/if.h"
#include "../net/route.h"
#include "domain.h"
#include "in.h"
#include "in_pcb.h"
#include "in_systm.h"
#include "ip.h"
#include "ip_var.h"
#include "ip_icmp.h"
#include "udp.h"
#include "udp_var.h"
struct inpcb *udp_last_inpcb = &udb;
/*
* UDP protocol implementation.
* Per RFC 768, August, 1980.
*/
udp_init()
{
udb.inp_next = udb.inp_prev = &udb;
}
#ifndef COMPAT_42
int udpcksum = 1;
#else
int udpcksum = 0; /* XXX */
#endif
struct sockaddr_in udp_in = { AF_INET };
udp_input(m0, ifp)
struct mbuf *m0;
struct ifnet *ifp;
{
register struct udpiphdr *ui;
register struct inpcb *inp;
register struct mbuf *m;
int len;
struct ip ip;
udpstat.udps_ipackets++;
/*
* Get IP and UDP header together in first mbuf.
*/
m = m0;
if ((m->m_off > MMAXOFF || m->m_len < sizeof (struct udpiphdr)) &&
(m = m_pullup(m, sizeof (struct udpiphdr))) == 0) {
udpstat.udps_hdrops++;
return;
}
ui = mtod(m, struct udpiphdr *);
if (((struct ip *)ui)->ip_hl > (sizeof (struct ip) >> 2))
ip_stripoptions((struct ip *)ui, (struct mbuf *)0);
/*
* Make mbuf data length reflect UDP length.
* If not enough data to reflect UDP length, drop.
*/
len = ntohs((u_short)ui->ui_ulen);
if (((struct ip *)ui)->ip_len != len) {
if (len > ((struct ip *)ui)->ip_len) {
udpstat.udps_badlen++;
goto bad;
}
m_adj(m, len - ((struct ip *)ui)->ip_len);
/* ((struct ip *)ui)->ip_len = len; */
}
/*
* Save a copy of the IP header in case we want restore it for ICMP.
*/
ip = *(struct ip*)ui;
/*
* Checksum extended UDP header and data.
*/
if (udpcksum && ui->ui_sum) {
ui->ui_next = ui->ui_prev = 0;
ui->ui_pad = 0;
ui->ui_x1 = 0;
ui->ui_len = ui->ui_ulen;
if (ui->ui_sum = in_cksum(m, len + sizeof (struct ip))) {
udpstat.udps_badsum++;
m_freem(m);
return;
}
}
/*
* Locate pcb for datagram.
*/
inp = udp_last_inpcb;
if (inp->inp_lport != ui->ui_dport ||
inp->inp_fport != ui->ui_sport ||
inp->inp_faddr.s_addr != ui->ui_src.s_addr ||
inp->inp_laddr.s_addr != ui->ui_dst.s_addr) {
inp = in_pcblookup(&udb, ui->ui_src, ui->ui_sport,
ui->ui_dst, ui->ui_dport, INPLOOKUP_WILDCARD);
if (inp)
udp_last_inpcb = inp;
udpstat.udpps_pcbcachemiss++;
}
if (inp == 0) {
udpstat.udps_noport++;
/* don't send ICMP response for broadcast packet */
if (in_broadcast(ui->ui_dst)) {
udpstat.udps_noportbcast++;
goto bad;
}
*(struct ip *)ui = ip;
icmp_error((struct ip *)ui, ICMP_UNREACH, ICMP_UNREACH_PORT,
ifp);
return;
}
/*
* Construct sockaddr format source address.
* Stuff source address and datagram in user buffer.
*/
udp_in.sin_port = ui->ui_sport;
udp_in.sin_addr = ui->ui_src;
m->m_len -= sizeof (struct udpiphdr);
m->m_off += sizeof (struct udpiphdr);
if (sbappendaddr(&inp->inp_socket->so_rcv, (struct sockaddr *)&udp_in,
m, (struct mbuf *)0) == 0) {
udpstat.udps_fullsock++;
goto bad;
}
sorwakeup(inp->inp_socket);
return;
bad:
m_freem(m);
}
/*
* Notify a udp user of an asynchronous error;
* just wake up so that he can collect error status.
*/
udp_notify(inp, errno)
register struct inpcb *inp;
int errno;
{
inp->inp_socket->so_error = errno;
sorwakeup(inp->inp_socket);
sowwakeup(inp->inp_socket);
}
udp_ctlinput(cmd, sa, ip)
register int cmd;
struct sockaddr *sa;
register struct ip *ip;
{
register struct udphdr *uh;
extern struct in_addr zeroin_addr;
extern u_char inetctlerrmap[];
if ((unsigned)cmd > PRC_NCMDS || inetctlerrmap[cmd] == 0)
return;
if (ip) {
uh = (struct udphdr *)((caddr_t)ip + (ip->ip_hl << 2));
in_pcbnotify(&udb, sa, uh->uh_dport, ip->ip_src, uh->uh_sport,
cmd, udp_notify);
} else
in_pcbnotify(&udb, sa, 0, zeroin_addr, 0, cmd, udp_notify);
}
udp_output(inp, m0, addr, control)
register struct inpcb *inp;
struct mbuf *m0;
struct mbuf *addr, *control;
{
register struct mbuf *m = m0;
register struct udpiphdr *ui;
register int len = 0;
struct in_addr laddr;
int s, error = 0;
if (addr) {
laddr = inp->inp_laddr;
if (inp->inp_faddr.s_addr != INADDR_ANY) {
error = EISCONN;
goto release;
}
/*
* Must block input while temporarily connected.
*/
s = splnet();
error = in_pcbconnect(inp, addr);
if (error) {
splx(s);
goto release;
}
} else {
if (inp->inp_faddr.s_addr == INADDR_ANY) {
error = ENOTCONN;
goto release;
}
}
/*
* Calculate data length and get a mbuf
* for UDP and IP headers.
*/
for (m = m0; m; m = m->m_next)
len += m->m_len;
MGET(m, M_WAIT, MT_HEADER);
/*
* Fill in mbuf with extended UDP header
* and addresses and length put into network format.
*/
m->m_off = MMAXOFF - sizeof (struct udpiphdr);
m->m_len = sizeof (struct udpiphdr);
m->m_next = m0;
ui = mtod(m, struct udpiphdr *);
ui->ui_next = ui->ui_prev = 0;
ui->ui_pad = 0;
ui->ui_x1 = 0;
ui->ui_pr = IPPROTO_UDP;
ui->ui_len = htons((u_short)len + sizeof (struct udphdr));
ui->ui_src = inp->inp_laddr;
ui->ui_dst = inp->inp_faddr;
ui->ui_sport = inp->inp_lport;
ui->ui_dport = inp->inp_fport;
ui->ui_ulen = ui->ui_len;
/*
* Stuff checksum and output datagram.
*/
ui->ui_sum = 0;
if (udpcksum) {
if ((ui->ui_sum = in_cksum(m, sizeof (struct udpiphdr) + len)) == 0)
ui->ui_sum = 0xffff;
}
((struct ip *)ui)->ip_len = sizeof (struct udpiphdr) + len;
((struct ip *)ui)->ip_ttl = ip_defttl;
udpstat.udps_opackets++;
error = ip_output(m, inp->inp_options, &inp->inp_route,
inp->inp_socket->so_options & (SO_DONTROUTE | SO_BROADCAST));
if (addr) {
in_pcbdisconnect(inp);
inp->inp_laddr = laddr;
splx(s);
}
return(error);
release:
m_freem(m);
return(error);
}
int udp_sendspace = 2048; /* really max datagram size */
int udp_recvspace = 4 * (1024+sizeof(struct sockaddr_in)); /* 4 1K dgrams */
/*ARGSUSED*/
udp_usrreq(so, req, m, nam, rights)
struct socket *so;
int req;
struct mbuf *m, *nam, *rights;
{
register struct inpcb *inp = sotoinpcb(so);
int error = 0;
register int s;
if (req == PRU_CONTROL)
return (in_control(so, (int)m, (caddr_t)nam,
(struct ifnet *)rights));
if (rights && rights->m_len) {
error = EINVAL;
goto release;
}
if (inp == NULL && req != PRU_ATTACH) {
error = EINVAL;
goto release;
}
switch (req) {
case PRU_ATTACH:
if (inp != NULL) {
error = EINVAL;
break;
}
s = splnet();
error = in_pcballoc(so, &udb);
splx(s);
if (error)
break;
error = soreserve(so, udp_sendspace, udp_recvspace);
if (error)
break;
break;
case PRU_DETACH:
udp_detach(inp);
break;
case PRU_BIND:
s = splnet();
error = in_pcbbind(inp, nam);
splx(s);
break;
case PRU_LISTEN:
error = EOPNOTSUPP;
break;
case PRU_CONNECT:
if (inp->inp_faddr.s_addr != INADDR_ANY) {
error = EISCONN;
break;
}
s = splnet();
error = in_pcbconnect(inp, nam);
splx(s);
if (error == 0)
soisconnected(so);
break;
case PRU_CONNECT2:
error = EOPNOTSUPP;
break;
case PRU_ACCEPT:
error = EOPNOTSUPP;
break;
case PRU_DISCONNECT:
if (inp->inp_faddr.s_addr == INADDR_ANY) {
error = ENOTCONN;
break;
}
s = splnet();
in_pcbdisconnect(inp);
inp->inp_laddr.s_addr = INADDR_ANY;
splx(s);
so->so_state &= ~SS_ISCONNECTED; /* XXX */
break;
case PRU_SHUTDOWN:
socantsendmore(so);
break;
case PRU_SEND:
return(udp_output(inp, m, nam, rights));
case PRU_ABORT:
soisdisconnected(so);
udp_detach(inp);
break;
case PRU_SOCKADDR:
in_setsockaddr(inp, nam);
break;
case PRU_PEERADDR:
in_setpeeraddr(inp, nam);
break;
case PRU_SENSE:
/*
* stat: don't bother with a blocksize.
*/
return (0);
case PRU_SENDOOB:
case PRU_FASTTIMO:
case PRU_SLOWTIMO:
case PRU_PROTORCV:
case PRU_PROTOSEND:
error = EOPNOTSUPP;
break;
case PRU_RCVD:
case PRU_RCVOOB:
return (EOPNOTSUPP); /* do not free mbuf's */
default:
panic("udp_usrreq");
}
release:
if (m != NULL)
m_freem(m);
return (error);
}
udp_detach(inp)
register struct inpcb *inp;
{
register int s = splnet();
if (inp == udp_last_inpcb)
udp_last_inpcb = &udb;
in_pcbdetach(inp);
splx(s);
}
/*
* Sysctl for udp variables.
*/
udp_sysctl(name, namelen, oldp, oldlenp, newp, newlen)
int *name;
u_int namelen;
void *oldp;
size_t *oldlenp;
void *newp;
size_t newlen;
{
/* All sysctl names at this level are terminal. */
if (namelen != 1)
return (ENOTDIR);
switch (name[0]) {
case UDPCTL_CHECKSUM:
return (sysctl_int(oldp, oldlenp, newp, newlen, &udpcksum));
default:
return (ENOPROTOOPT);
}
/* NOTREACHED */
}