NetBSD/sys/netccitt/pk_usrreq.c
thorpej d844a3ac41 First-draft if_detach() implementation, originally from Bill Studnemund,
although this version has been changed somewhat:
- reference counting on ifaddrs isn't as complete as Bill's original
  work was.  This is hard to get right, and we should attack one
  protocol at a time.
- This doesn't do reference counting or dynamic allocation of ifnets yet.
- This version introduces a new PRU -- PRU_PURGEADDR, which is used to
  purge an ifaddr from a protocol.  The old method Bill used didn't work
  on all protocols, and it only worked on some because it was Very Lucky.

This mostly works ... i.e. works for my USB Ethernet, except for a dangling
ifaddr reference left by the IPv6 code; have not yet tracked this down.
2000-02-01 22:52:04 +00:00

632 lines
16 KiB
C

/* $NetBSD: pk_usrreq.c,v 1.17 2000/02/01 22:52:07 thorpej Exp $ */
/*
* Copyright (c) 1984 University of British Columbia.
* Copyright (c) 1992 Computer Science Department IV,
* University of Erlangen-Nuremberg, Germany.
* Copyright (c) 1991, 1992, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by the
* Laboratory for Computation Vision and the Computer Science Department
* of the the University of British Columbia and the Computer Science
* Department (IV) of the University of Erlangen-Nuremberg, Germany.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)pk_usrreq.c 8.2 (Berkeley) 1/9/95
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/protosw.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/proc.h>
#include <net/if.h>
#include <net/if_types.h>
#include <net/route.h>
#include <netccitt/x25.h>
#include <netccitt/pk.h>
#include <netccitt/pk_var.h>
#include <netccitt/pk_extern.h>
static void pk_setsockaddr __P((struct pklcd *, struct mbuf *));
static void pk_setpeeraddr __P((struct pklcd *, struct mbuf *));
static void old_to_new __P((struct mbuf *));
static void new_to_old __P((struct mbuf *));
static void
pk_setsockaddr(lcp, nam)
struct pklcd *lcp;
struct mbuf *nam;
{
nam->m_len = sizeof(struct sockaddr_x25);
bcopy(lcp->lcd_ceaddr, mtod(nam, caddr_t), (size_t)nam->m_len);
if (lcp->lcd_flags & X25_OLDSOCKADDR)
new_to_old(nam);
}
static void
pk_setpeeraddr(lcp, nam)
struct pklcd *lcp;
struct mbuf *nam;
{
nam->m_len = sizeof(struct sockaddr_x25);
bcopy(lcp->lcd_craddr, mtod(nam, caddr_t), (size_t)nam->m_len);
if (lcp->lcd_flags & X25_OLDSOCKADDR)
new_to_old(nam);
}
/*
*
* X.25 Packet level protocol interface to socket abstraction.
*
* Process an X.25 user request on a logical channel. If this is a send
* request then m is the mbuf chain of the send data. If this is a timer
* expiration (called from the software clock routine) them timertype is
* the particular timer.
*
*/
int
pk_usrreq(so, req, m, nam, control, p)
struct socket *so;
int req;
struct mbuf *m, *nam, *control;
struct proc *p;
{
register struct pklcd *lcp;
int s;
register int error = 0;
if (req == PRU_CONTROL)
return (pk_control(so, (long)m, (caddr_t)nam,
(struct ifnet *)control, p));
s = splsoftnet();
lcp = (struct pklcd *)so->so_pcb;
#ifdef DIAGNOSTIC
if (req != PRU_SEND && req != PRU_SENDOOB && control)
panic("pk_usrreq: unexpected control mbuf");
#endif
if (lcp == 0 && req != PRU_ATTACH) {
error = EINVAL;
goto release;
}
/*
pk_trace (pkcbhead, TR_USER, (struct pklcd *)0,
req, (struct x25_packet *)0);
*/
switch (req) {
/*
* X.25 attaches to socket via PRU_ATTACH and allocates a
* logical channel descriptor. If the socket is to receive
* connections, then the LISTEN state is entered.
*/
case PRU_ATTACH:
if (lcp != 0) {
error = EISCONN;
break;
}
lcp = pk_attach(so);
if (lcp == 0)
error = ENOBUFS;
break;
/*
* Detach a logical channel from the socket. If the state of
* the channel is embryonic, simply discard it. Otherwise we
* have to initiate a PRU_DISCONNECT which will finish later.
*/
case PRU_DETACH:
pk_disconnect(lcp);
break;
/*
* Give the socket an address.
*/
case PRU_BIND:
if (nam->m_len == sizeof(struct x25_sockaddr))
old_to_new(nam);
error = pk_bind(lcp, nam);
break;
/*
* Prepare to accept connections.
*/
case PRU_LISTEN:
error = pk_listen(lcp);
break;
/*
* Initiate a CALL REQUEST to peer entity. Enter state
* SENT_CALL and mark the socket as connecting. Set timer
* waiting for CALL ACCEPT or CLEAR.
*/
case PRU_CONNECT:
if (nam->m_len == sizeof(struct x25_sockaddr))
old_to_new(nam);
if (pk_checksockaddr(nam)) {
error = EINVAL;
break;
}
error = pk_connect(lcp, mtod(nam, struct sockaddr_x25 *));
break;
case PRU_CONNECT2:
error = EOPNOTSUPP;
break;
/*
* Initiate a disconnect to peer entity via a CLEAR REQUEST
* packet. The socket will be disconnected when we receive a
* confirmation or a clear collision.
*/
case PRU_DISCONNECT:
pk_disconnect(lcp);
break;
/*
* Accept an INCOMING CALL. Most of the work has already been
* done by pk_input. Just return the callers address to the
* user.
*/
case PRU_ACCEPT:
if (lcp->lcd_craddr == NULL)
break;
pk_setpeeraddr(lcp, nam);
break;
case PRU_SHUTDOWN:
socantsendmore(so);
break;
/*
* After a receive, we should send a RR.
*/
case PRU_RCVD:
pk_flowcontrol(lcp, /* sbspace (&so -> so_rcv) <= */ 0, 1);
break;
/*
* Do send by placing data on the socket output queue.
*/
case PRU_SEND:
if (control) {
register struct cmsghdr *ch = mtod(m, struct cmsghdr *);
control->m_len -= sizeof(*ch);
control->m_data += sizeof(*ch);
error = pk_ctloutput(PRCO_SETOPT, so, ch->cmsg_level,
ch->cmsg_type, &control);
if (error)
break;
}
if (m)
error = pk_send(m, lcp);
break;
/*
* Abort a virtual circuit. For example all completed calls
* waiting acceptance.
*/
case PRU_ABORT:
pk_disconnect(lcp);
break;
case PRU_SENSE:
/*
* stat: don't bother with a blocksize.
*/
splx(s);
return (0);
/*
* Receive INTERRUPT packet.
*/
case PRU_RCVOOB:
if (so->so_options & SO_OOBINLINE) {
register struct mbuf *n = so->so_rcv.sb_mb;
if (n && n->m_type == MT_OOBDATA) {
unsigned len = n->m_pkthdr.len;
so->so_rcv.sb_mb = n->m_nextpkt;
if (len != n->m_len &&
(n = m_pullup(n, len)) == 0)
break;
m->m_len = len;
bcopy(mtod(m, caddr_t), mtod(n, caddr_t), len);
m_freem(n);
}
break;
}
m->m_len = 1;
*mtod(m, char *) = lcp->lcd_intrdata;
break;
/*
* Send INTERRUPT packet.
*/
case PRU_SENDOOB:
if (control) {
register struct cmsghdr *ch = mtod(m, struct cmsghdr *);
control->m_len -= sizeof(*ch);
control->m_data += sizeof(*ch);
error = pk_ctloutput(PRCO_SETOPT, so, ch->cmsg_level,
ch->cmsg_type, &control);
if (error)
break;
}
if (m) {
MCHTYPE(m, MT_OOBDATA);
error = pk_send(m, lcp);
}
break;
case PRU_SOCKADDR:
if (lcp->lcd_ceaddr == 0) {
error = EADDRNOTAVAIL;
break;
}
pk_setsockaddr(lcp, nam);
break;
case PRU_PEERADDR:
if (lcp->lcd_state != DATA_TRANSFER) {
error = ENOTCONN;
break;
}
pk_setpeeraddr(lcp, nam);
break;
default:
panic("pk_usrreq");
}
release:
splx(s);
return (error);
}
/*
* If you want to use UBC X.25 level 3 in conjunction with some other X.25
* level 2 driver, have the ifp -> if_ioctl routine assign pk_start to ia ->
* ia_start when called with SIOCSIFCONF_X25.
*/
/* ARGSUSED */
int
pk_start(lcp)
register struct pklcd *lcp;
{
pk_output(lcp);
return (0); /* XXX pk_output should return a value */
}
struct sockaddr_x25 pk_sockmask = {
offsetof(struct sockaddr_x25, x25_addr[0]), /* x25_len */
0, /* x25_family */
-1, /* x25_net id */
};
/* ARGSUSED */
int
pk_control(so, cmd, data, ifp, p)
struct socket *so;
u_long cmd;
caddr_t data;
register struct ifnet *ifp;
struct proc *p;
{
register struct ifreq_x25 *ifr = (struct ifreq_x25 *) data;
register struct ifaddr *ifa = 0;
register struct x25_ifaddr *ia = 0;
int error = 0, s, old_maxlcn;
/*
* Find address for this interface, if it exists.
*/
if (ifp)
for (ifa = ifp->if_addrlist.tqh_first; ifa != 0;
ifa = ifa->ifa_list.tqe_next)
if (ifa->ifa_addr->sa_family == AF_CCITT)
break;
ia = (struct x25_ifaddr *) ifa;
switch (cmd) {
case SIOCGIFCONF_X25:
if (ifa == 0)
return (EADDRNOTAVAIL);
ifr->ifr_xc = ia->ia_xc;
return (0);
case SIOCSIFCONF_X25:
if (p == 0 || (error = suser(p->p_ucred, &p->p_acflag)))
return (EPERM);
if (ifp == 0)
panic("pk_control");
if (ifa == (struct ifaddr *) 0) {
MALLOC(ia, struct x25_ifaddr *, sizeof(*ia),
M_IFADDR, M_WAITOK);
if (ia == 0)
return (ENOBUFS);
bzero((caddr_t) ia, sizeof(*ia));
TAILQ_INSERT_TAIL(&ifp->if_addrlist, &ia->ia_ifa,
ifa_list);
ifa = &ia->ia_ifa;
IFAREF(ifa);
ifa->ifa_netmask = (struct sockaddr *) & pk_sockmask;
ifa->ifa_addr = (struct sockaddr *) & ia->ia_xc.xc_addr;
ifa->ifa_dstaddr = (struct sockaddr *) & ia->ia_dstaddr; /* XXX */
ia->ia_ifp = ifp;
ia->ia_dstaddr.x25_family = AF_CCITT;
ia->ia_dstaddr.x25_len = pk_sockmask.x25_len;
} else if (ISISO8802(ifp) == 0) {
rtinit(ifa, (int) RTM_DELETE, 0);
}
old_maxlcn = ia->ia_maxlcn;
ia->ia_xc = ifr->ifr_xc;
ia->ia_dstaddr.x25_net = ia->ia_xc.xc_addr.x25_net;
if (ia->ia_maxlcn != old_maxlcn && old_maxlcn != 0) {
/* VERY messy XXX */
register struct pkcb *pkp;
FOR_ALL_PKCBS(pkp)
if (pkp->pk_ia == ia)
pk_resize(pkp);
}
/*
* Give the interface a chance to initialize if this
p * is its first address, and to validate the address.
*/
ia->ia_start = pk_start;
s = splimp();
if (ifp->if_ioctl)
error = (*ifp->if_ioctl) (ifp, SIOCSIFCONF_X25,
(caddr_t) ifa);
if (error)
ifp->if_flags &= ~IFF_UP;
else if (ISISO8802(ifp) == 0)
error = rtinit(ifa, (int) RTM_ADD, RTF_UP);
splx(s);
return (error);
default:
if (ifp == 0 || ifp->if_ioctl == 0)
return (EOPNOTSUPP);
return ((*ifp->if_ioctl) (ifp, cmd, data));
}
}
int
pk_ctloutput(cmd, so, level, optname, mp)
struct socket *so;
struct mbuf **mp;
int cmd, level, optname;
{
struct proc *p = curproc; /* XXX */
register struct mbuf *m = *mp;
register struct pklcd *lcp = (struct pklcd *) so->so_pcb;
int error = EOPNOTSUPP;
if (m == 0)
return (EINVAL);
if (cmd == PRCO_SETOPT)
switch (optname) {
case PK_FACILITIES:
if (m == 0)
return (EINVAL);
lcp->lcd_facilities = m;
*mp = 0;
return (0);
case PK_ACCTFILE:
if (p == 0 || (error = suser(p->p_ucred, &p->p_acflag)))
error = EPERM;
else if (m->m_len)
error = pk_accton(mtod(m, char *));
else
error = pk_accton((char *) 0);
break;
case PK_RTATTACH:
error = pk_rtattach(so, m);
break;
case PK_PRLISTEN:
error = pk_user_protolisten(mtod(m, u_char *));
}
if (*mp) {
(void) m_freem(*mp);
*mp = 0;
}
return (error);
}
/*
* Do an in-place conversion of an "old style"
* socket address to the new style
*/
static void
old_to_new(m)
register struct mbuf *m;
{
register struct x25_sockaddr *oldp;
register struct sockaddr_x25 *newp;
register char *ocp, *ncp;
struct sockaddr_x25 new;
oldp = mtod(m, struct x25_sockaddr *);
newp = &new;
bzero((caddr_t) newp, sizeof(*newp));
newp->x25_family = AF_CCITT;
newp->x25_len = sizeof(*newp);
newp->x25_opts.op_flags = (oldp->xaddr_facilities & X25_REVERSE_CHARGE)
| X25_MQBIT | X25_OLDSOCKADDR;
if (oldp->xaddr_facilities & XS_HIPRIO) /* Datapac specific */
newp->x25_opts.op_psize = X25_PS128;
bcopy((caddr_t) oldp->xaddr_addr, newp->x25_addr,
(unsigned) min(oldp->xaddr_len, sizeof(newp->x25_addr) - 1));
if (bcmp((caddr_t) oldp->xaddr_proto, newp->x25_udata, 4) != 0) {
bcopy((caddr_t) oldp->xaddr_proto, newp->x25_udata, 4);
newp->x25_udlen = 4;
}
ocp = (caddr_t) oldp->xaddr_userdata;
ncp = newp->x25_udata + 4;
while (*ocp && ocp < (caddr_t) oldp->xaddr_userdata + 12) {
if (newp->x25_udlen == 0)
newp->x25_udlen = 4;
*ncp++ = *ocp++;
newp->x25_udlen++;
}
bcopy((caddr_t) newp, mtod(m, char *), sizeof(*newp));
m->m_len = sizeof(*newp);
}
/*
* Do an in-place conversion of a new style
* socket address to the old style
*/
static void
new_to_old(m)
register struct mbuf *m;
{
register struct x25_sockaddr *oldp;
register struct sockaddr_x25 *newp;
register char *ocp, *ncp;
struct x25_sockaddr old;
oldp = &old;
newp = mtod(m, struct sockaddr_x25 *);
bzero((caddr_t) oldp, sizeof(*oldp));
oldp->xaddr_facilities = newp->x25_opts.op_flags & X25_REVERSE_CHARGE;
if (newp->x25_opts.op_psize == X25_PS128)
oldp->xaddr_facilities |= XS_HIPRIO; /* Datapac specific */
ocp = (char *) oldp->xaddr_addr;
ncp = newp->x25_addr;
while (*ncp) {
*ocp++ = *ncp++;
oldp->xaddr_len++;
}
bcopy(newp->x25_udata, (caddr_t) oldp->xaddr_proto, 4);
if (newp->x25_udlen > 4)
bcopy(newp->x25_udata + 4, (caddr_t) oldp->xaddr_userdata,
(unsigned) (newp->x25_udlen - 4));
bcopy((caddr_t) oldp, mtod(m, char *), sizeof(*oldp));
m->m_len = sizeof(*oldp);
}
int
pk_checksockaddr(m)
struct mbuf *m;
{
register struct sockaddr_x25 *sa = mtod(m, struct sockaddr_x25 *);
register char *cp;
if (m->m_len != sizeof(struct sockaddr_x25))
return (1);
if (sa->x25_family != AF_CCITT ||
sa->x25_udlen > sizeof(sa->x25_udata))
return (1);
for (cp = sa->x25_addr; *cp; cp++) {
if (*cp < '0' || *cp > '9' ||
cp >= &sa->x25_addr[sizeof(sa->x25_addr) - 1])
return (1);
}
return (0);
}
int
pk_send(m, v)
register struct mbuf *m;
void *v;
{
struct pklcd *lcp = v;
int mqbit = 0, error = 0;
register struct x25_packet *xp;
register struct socket *so;
if (m->m_type == MT_OOBDATA) {
if (lcp->lcd_intrconf_pending)
error = ETOOMANYREFS;
if (m->m_pkthdr.len > 32)
error = EMSGSIZE;
M_PREPEND(m, PKHEADERLN, M_WAITOK);
if (m == 0 || error)
goto bad;
*(mtod(m, octet *)) = 0;
xp = mtod(m, struct x25_packet *);
X25SBITS(xp->bits, fmt_identifier, 1);
xp->packet_type = X25_INTERRUPT;
SET_LCN(xp, lcp->lcd_lcn);
sbinsertoob((so = lcp->lcd_so) ?
&so->so_snd : &lcp->lcd_sb, m);
goto send;
}
/*
* Application has elected (at call setup time) to prepend
* a control byte to each packet written indicating m-bit
* and q-bit status. Examine and then discard this byte.
*/
if (lcp->lcd_flags & X25_MQBIT) {
if (m->m_len < 1) {
m_freem(m);
return (EMSGSIZE);
}
mqbit = *(mtod(m, u_char *));
m->m_len--;
m->m_data++;
m->m_pkthdr.len--;
}
error = pk_fragment(lcp, m, mqbit & 0x80, mqbit & 0x40, 1);
send:
if (error == 0 && lcp->lcd_state == DATA_TRANSFER)
lcp->lcd_send(lcp); /* XXXXXXXXX fix pk_output!!! */
return (error);
bad:
if (m)
m_freem(m);
return (error);
}