freebsd-nq/sys/netncp/ncp_sock.c
Robert Watson b0668f7151 soreceive_generic(), and sopoll_generic(). Add new functions sosend(),
soreceive(), and sopoll(), which are wrappers for pru_sosend,
pru_soreceive, and pru_sopoll, and are now used univerally by socket
consumers rather than either directly invoking the old so*() functions
or directly invoking the protocol switch method (about an even split
prior to this commit).

This completes an architectural change that was begun in 1996 to permit
protocols to provide substitute implementations, as now used by UDP.
Consumers now uniformly invoke sosend(), soreceive(), and sopoll() to
perform these operations on sockets -- in particular, distributed file
systems and socket system calls.

Architectural head nod:	sam, gnn, wollman
2006-07-24 15:20:08 +00:00

474 lines
12 KiB
C

/*-
* Copyright (c) 1999, 2001 Boris Popov
* All rights reserved.
*
* 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 Boris Popov.
* 4. Neither the name of the author nor the names of any co-contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
*
* Low level socket routines
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/protosw.h>
#include <sys/kernel.h>
#include <sys/uio.h>
#include <sys/syslog.h>
#include <sys/mbuf.h>
#include <sys/condvar.h>
#include <net/route.h>
#include <netipx/ipx.h>
#include <netipx/ipx_pcb.h>
#include <netncp/ncp.h>
#include <netncp/ncp_conn.h>
#include <netncp/ncp_sock.h>
#include <netncp/ncp_subr.h>
#include <netncp/ncp_rq.h>
#define ipx_setnullnet(x) ((x).x_net.s_net[0]=0); ((x).x_net.s_net[1]=0);
#define ipx_setnullhost(x) ((x).x_host.s_host[0] = 0); \
((x).x_host.s_host[1] = 0); ((x).x_host.s_host[2] = 0);
/*int ncp_poll(struct socket *so, int events);*/
/*static int ncp_getsockname(struct socket *so, caddr_t asa, int *alen);*/
static int ncp_soconnect(struct socket *so, struct sockaddr *target,
struct thread *td);
/* This will need only if native IP used, or (unlikely) NCP will be
* implemented on the socket level
*/
static int
ncp_soconnect(struct socket *so, struct sockaddr *target, struct thread *td)
{
int error, s;
error = soconnect(so, (struct sockaddr*)target, td);
if (error)
return error;
/*
* Wait for the connection to complete. Cribbed from the
* connect system call but with the wait timing out so
* that interruptible mounts don't hang here for a long time.
*/
error = EIO;
s = splnet();
while ((so->so_state & SS_ISCONNECTING) && so->so_error == 0) {
(void) tsleep((caddr_t)&so->so_timeo, PSOCK, "ncpcon", 2 * hz);
if ((so->so_state & SS_ISCONNECTING) &&
so->so_error == 0 /*&& rep &&*/) {
so->so_state &= ~SS_ISCONNECTING;
splx(s);
goto bad;
}
}
if (so->so_error) {
error = so->so_error;
so->so_error = 0;
splx(s);
goto bad;
}
splx(s);
error=0;
bad:
return error;
}
#ifdef notyet
static int
ncp_getsockname(struct socket *so, caddr_t asa, int *alen) {
struct sockaddr *sa;
int len=0, error;
sa = 0;
error = (*so->so_proto->pr_usrreqs->pru_sockaddr)(so, &sa);
if (error==0) {
if (sa) {
len = min(len, sa->sa_len);
bcopy(sa, (caddr_t)asa, (u_int)len);
}
*alen=len;
}
if (sa)
FREE(sa, M_SONAME);
return (error);
}
#endif
int ncp_sock_recv(struct socket *so, struct mbuf **mp, int *rlen)
{
struct uio auio;
struct thread *td = curthread; /* XXX */
int error,flags,len;
auio.uio_resid = len = 1000000;
auio.uio_td = td;
flags = MSG_DONTWAIT;
/* error = soreceive(so, 0, &auio, (struct mbuf **)0, (struct mbuf **)0,
&flags);*/
error = soreceive(so, 0, &auio, mp, (struct mbuf **)0, &flags);
*rlen = len - auio.uio_resid;
/* if (!error) {
*rlen=iov.iov_len;
} else
*rlen=0;*/
#ifdef NCP_SOCKET_DEBUG
if (error)
printf("ncp_recv: err=%d\n", error);
#endif
return (error);
}
int
ncp_sock_send(struct socket *so, struct mbuf *top, struct ncp_rq *rqp)
{
struct thread *td = curthread; /* XXX */
struct sockaddr *to = 0;
struct ncp_conn *conn = rqp->nr_conn;
struct mbuf *m;
int error, flags=0;
int sendwait;
for (;;) {
m = m_copym(top, 0, M_COPYALL, M_TRYWAIT);
/* NCPDDEBUG(m);*/
error = sosend(so, to, 0, m, 0, flags, td);
if (error == 0 || error == EINTR || error == ENETDOWN)
break;
if (rqp->rexmit == 0) break;
rqp->rexmit--;
tsleep(&sendwait, PWAIT, "ncprsn", conn->li.timeout * hz);
error = ncp_chkintr(conn, td);
if (error == EINTR) break;
}
if (error) {
log(LOG_INFO, "ncp_send: error %d for server %s", error, conn->li.server);
}
return error;
}
int
ncp_poll(struct socket *so, int events)
{
struct thread *td = curthread;
struct ucred *cred = NULL;
return (sopoll(so, events, cred, td));
}
int
ncp_sock_rselect(struct socket *so, struct thread *td, struct timeval *tv,
int events)
{
struct timeval atv, rtv, ttv;
int ncoll, timo, error = 0;
if (tv) {
atv = *tv;
if (itimerfix(&atv)) {
error = EINVAL;
goto done_noproclock;
}
getmicrouptime(&rtv);
timevaladd(&atv, &rtv);
}
timo = 0;
mtx_lock(&sellock);
retry:
ncoll = nselcoll;
mtx_lock_spin(&sched_lock);
td->td_flags |= TDF_SELECT;
mtx_unlock_spin(&sched_lock);
mtx_unlock(&sellock);
TAILQ_INIT(&td->td_selq);
error = ncp_poll(so, events);
mtx_lock(&sellock);
if (error) {
error = 0;
goto done;
}
if (tv) {
getmicrouptime(&rtv);
if (timevalcmp(&rtv, &atv, >=))
goto done;
ttv = atv;
timevalsub(&ttv, &rtv);
timo = tvtohz(&ttv);
}
/*
* An event of our interest may occur during locking a thread.
* In order to avoid missing the event that occurred during locking
* the process, test TDF_SELECT and rescan file descriptors if
* necessary.
*/
mtx_lock_spin(&sched_lock);
if ((td->td_flags & TDF_SELECT) == 0 || nselcoll != ncoll) {
mtx_unlock_spin(&sched_lock);
goto retry;
}
mtx_unlock_spin(&sched_lock);
if (timo > 0)
error = cv_timedwait(&selwait, &sellock, timo);
else {
cv_wait(&selwait, &sellock);
error = 0;
}
done:
clear_selinfo_list(td);
mtx_lock_spin(&sched_lock);
td->td_flags &= ~TDF_SELECT;
mtx_unlock_spin(&sched_lock);
mtx_unlock(&sellock);
done_noproclock:
if (error == ERESTART)
error = 0;
return (error);
}
/*
* Connect to specified server via IPX
*/
static int
ncp_sock_connect_ipx(struct ncp_conn *conn)
{
struct sockaddr_ipx sipx;
struct ipxpcb *npcb;
struct thread *td = conn->td;
int addrlen, error, count;
sipx.sipx_port = htons(0);
for (count = 0;;count++) {
if (count > (IPXPORT_WELLKNOWN-IPXPORT_RESERVED)*2) {
error = EADDRINUSE;
goto bad;
}
conn->ncp_so = conn->wdg_so = NULL;
checkbad(socreate(AF_IPX, &conn->ncp_so, SOCK_DGRAM, 0, td->td_ucred, td));
if (conn->li.opt & NCP_OPT_WDOG)
checkbad(socreate(AF_IPX, &conn->wdg_so, SOCK_DGRAM, 0, td->td_ucred, td));
addrlen = sizeof(sipx);
sipx.sipx_family = AF_IPX;
ipx_setnullnet(sipx.sipx_addr);
ipx_setnullhost(sipx.sipx_addr);
sipx.sipx_len = addrlen;
error = sobind(conn->ncp_so, (struct sockaddr *)&sipx, td);
if (error == 0) {
if ((conn->li.opt & NCP_OPT_WDOG) == 0)
break;
sipx.sipx_addr = sotoipxpcb(conn->ncp_so)->ipxp_laddr;
sipx.sipx_port = htons(ntohs(sipx.sipx_port) + 1);
ipx_setnullnet(sipx.sipx_addr);
ipx_setnullhost(sipx.sipx_addr);
error = sobind(conn->wdg_so, (struct sockaddr *)&sipx, td);
}
if (!error) break;
if (error != EADDRINUSE) goto bad;
sipx.sipx_port = htons((ntohs(sipx.sipx_port)+4) & 0xfff8);
soclose(conn->ncp_so);
if (conn->wdg_so)
soclose(conn->wdg_so);
}
npcb = sotoipxpcb(conn->ncp_so);
npcb->ipxp_dpt = IPXPROTO_NCP;
/* IPXrouted must be running, i.e. route must be presented */
conn->li.ipxaddr.sipx_len = sizeof(struct sockaddr_ipx);
checkbad(ncp_soconnect(conn->ncp_so, &conn->li.saddr, td));
if (conn->wdg_so) {
sotoipxpcb(conn->wdg_so)->ipxp_laddr.x_net = npcb->ipxp_laddr.x_net;
sotoipxpcb(conn->wdg_so)->ipxp_laddr.x_host= npcb->ipxp_laddr.x_host;
}
if (!error) {
conn->flags |= NCPFL_SOCONN;
}
#ifdef NCPBURST
if (ncp_burst_enabled) {
checkbad(socreate(AF_IPX, &conn->bc_so, SOCK_DGRAM, 0, td));
bzero(&sipx, sizeof(sipx));
sipx.sipx_len = sizeof(sipx);
checkbad(sobind(conn->bc_so, (struct sockaddr *)&sipx, td));
checkbad(ncp_soconnect(conn->bc_so, &conn->li.saddr, td));
}
#endif
if (!error) {
conn->flags |= NCPFL_SOCONN;
ncp_sock_checksum(conn, 0);
}
return error;
bad:
ncp_sock_disconnect(conn);
return (error);
}
int
ncp_sock_checksum(struct ncp_conn *conn, int enable)
{
if (enable) {
sotoipxpcb(conn->ncp_so)->ipxp_flags |= IPXP_CHECKSUM;
} else {
sotoipxpcb(conn->ncp_so)->ipxp_flags &= ~IPXP_CHECKSUM;
}
return 0;
}
/*
* Connect to specified server via IP
*/
static int
ncp_sock_connect_in(struct ncp_conn *conn)
{
struct sockaddr_in sin;
struct thread *td = conn->td;
int addrlen = sizeof(sin), error;
conn->flags = 0;
bzero(&sin,addrlen);
conn->ncp_so = conn->wdg_so = NULL;
checkbad(socreate(AF_INET, &conn->ncp_so, SOCK_DGRAM, IPPROTO_UDP, td->td_ucred, td));
sin.sin_family = AF_INET;
sin.sin_len = addrlen;
checkbad(sobind(conn->ncp_so, (struct sockaddr *)&sin, td));
checkbad(ncp_soconnect(conn->ncp_so,(struct sockaddr*)&conn->li.addr, td));
if (!error)
conn->flags |= NCPFL_SOCONN;
return error;
bad:
ncp_sock_disconnect(conn);
return (error);
}
int
ncp_sock_connect(struct ncp_conn *ncp)
{
int error;
switch (ncp->li.saddr.sa_family) {
case AF_IPX:
error = ncp_sock_connect_ipx(ncp);
break;
case AF_INET:
error = ncp_sock_connect_in(ncp);
break;
default:
return EPROTONOSUPPORT;
}
return error;
}
/*
* Connection expected to be locked
*/
int
ncp_sock_disconnect(struct ncp_conn *conn) {
register struct socket *so;
conn->flags &= ~(NCPFL_SOCONN | NCPFL_ATTACHED | NCPFL_LOGGED);
if (conn->ncp_so) {
so = conn->ncp_so;
conn->ncp_so = (struct socket *)0;
soshutdown(so, 2);
soclose(so);
}
if (conn->wdg_so) {
so = conn->wdg_so;
conn->wdg_so = (struct socket *)0;
soshutdown(so, 2);
soclose(so);
}
#ifdef NCPBURST
if (conn->bc_so) {
so = conn->bc_so;
conn->bc_so = (struct socket *)NULL;
soshutdown(so, 2);
soclose(so);
}
#endif
return 0;
}
static void
ncp_watchdog(struct ncp_conn *conn) {
char *buf;
struct mbuf *m;
int error, len, flags;
struct socket *so;
struct sockaddr *sa;
struct uio auio;
sa = NULL;
while (conn->wdg_so) { /* not a loop */
so = conn->wdg_so;
auio.uio_resid = len = 1000000;
auio.uio_td = curthread;
flags = MSG_DONTWAIT;
error = soreceive(so, (struct sockaddr**)&sa, &auio, &m,
(struct mbuf**)0, &flags);
if (error) break;
len -= auio.uio_resid;
NCPSDEBUG("got watch dog %d\n",len);
if (len != 2) break;
buf = mtod(m, char*);
if (buf[1] != '?') break;
buf[1] = 'Y';
error = sosend(so, (struct sockaddr*)sa, 0, m, 0, 0, curthread);
NCPSDEBUG("send watch dog %d\n",error);
break;
}
if (sa) FREE(sa, M_SONAME);
return;
}
void
ncp_check_conn(struct ncp_conn *conn) {
int s;
if (conn == NULL || !(conn->flags & NCPFL_ATTACHED))
return;
s = splnet();
ncp_check_rq(conn);
splx(s);
if (conn->li.saddr.sa_family == AF_IPX)
ncp_watchdog(conn);
}