From 734d32634595a436e86c323a23be0f22d992d511 Mon Sep 17 00:00:00 2001 From: jhb Date: Mon, 13 Jan 2014 21:29:34 +0000 Subject: [PATCH] MFC 197775,197777-197779,197781,197794,243152,243313,255478: First cut at implementing SOCK_SEQPACKET support for UNIX (local) domain sockets. This allows for reliable bi-directional datagram communication over UNIX domain sockets, in contrast to SOCK_DGRAM (M:N, unreliable) or SOCK_STERAM (bi-directional bytestream). Largely, this reuses existing UNIX domain socket code. This allows applications requiring record- oriented semantics to do so reliably via local IPC. git-svn-id: svn://svn.freebsd.org/base/stable/8@260606 ccf9f872-aa2e-dd11-9fc8-001c23d0bc1f --- share/man/man4/unix.4 | 15 +- sys/kern/uipc_usrreq.c | 143 +++++- .../sockets/unix_seqpacket/Makefile | 9 + .../sockets/unix_seqpacket/unix_seqpacket.c | 140 ++++++ .../sockets/unix_seqpacket_exercise/Makefile | 9 + .../unix_seqpacket_exercise.c | 435 ++++++++++++++++++ usr.bin/netstat/main.c | 5 +- usr.bin/netstat/netstat.h | 2 +- usr.bin/netstat/unix.c | 26 +- 9 files changed, 755 insertions(+), 29 deletions(-) create mode 100644 tools/regression/sockets/unix_seqpacket/Makefile create mode 100644 tools/regression/sockets/unix_seqpacket/unix_seqpacket.c create mode 100644 tools/regression/sockets/unix_seqpacket_exercise/Makefile create mode 100644 tools/regression/sockets/unix_seqpacket_exercise/unix_seqpacket_exercise.c diff --git a/share/man/man4/unix.4 b/share/man/man4/unix.4 index 89944ce7a..97e797c2f 100644 --- a/share/man/man4/unix.4 +++ b/share/man/man4/unix.4 @@ -32,7 +32,7 @@ .\" @(#)unix.4 8.1 (Berkeley) 6/9/93 .\" $FreeBSD$ .\" -.Dd July 15, 2001 +.Dd October 5, 2009 .Dt UNIX 4 .Os .Sh NAME @@ -52,7 +52,8 @@ mechanisms. The .Ux Ns -domain family supports the -.Dv SOCK_STREAM +.Dv SOCK_STREAM , +.Dv SOCK_SEQPACKET , and .Dv SOCK_DGRAM socket types and uses @@ -127,11 +128,14 @@ The .Ux Ns -domain protocol family is comprised of simple transport protocols that support the -.Dv SOCK_STREAM +.Dv SOCK_STREAM , +.Dv SOCK_SEQPACKET , and .Dv SOCK_DGRAM abstractions. .Dv SOCK_STREAM +and +.Dv SOCK_SEQPACKET sockets also support the communication of .Ux file descriptors through the use of the @@ -206,8 +210,9 @@ and tested with .Xr getsockopt 2 : .Bl -tag -width ".Dv LOCAL_CONNWAIT" .It Dv LOCAL_CREDS -This option may be enabled on a -.Dv SOCK_DGRAM +This option may be enabled on +.Dv SOCK_DGRAM , +.Dv SOCK_SEQPACKET , or a .Dv SOCK_STREAM socket. diff --git a/sys/kern/uipc_usrreq.c b/sys/kern/uipc_usrreq.c index f69bea1bd..2e87dbcf4 100644 --- a/sys/kern/uipc_usrreq.c +++ b/sys/kern/uipc_usrreq.c @@ -50,7 +50,8 @@ * garbage collector to find and tear down cycles of disconnected sockets. * * TODO: - * SEQPACKET, RDM + * RDM + * distinguish datagram size limits from flow control limits in SEQPACKET * rethink name space problems * need a proper out-of-band */ @@ -113,6 +114,7 @@ static ino_t unp_ino; /* Prototype for fake inode numbers. */ static int unp_rights; /* (g) File descriptors in flight. */ static struct unp_head unp_shead; /* (l) List of stream sockets. */ static struct unp_head unp_dhead; /* (l) List of datagram sockets. */ +static struct unp_head unp_sphead; /* (l) List of seqpacket sockets. */ struct unp_defer { SLIST_ENTRY(unp_defer) ud_link; @@ -154,10 +156,14 @@ static u_long unpst_sendspace = PIPSIZ; static u_long unpst_recvspace = PIPSIZ; static u_long unpdg_sendspace = 2*1024; /* really max datagram size */ static u_long unpdg_recvspace = 4*1024; +static u_long unpsp_sendspace = PIPSIZ; /* really max datagram size */ +static u_long unpsp_recvspace = PIPSIZ; SYSCTL_NODE(_net, PF_LOCAL, local, CTLFLAG_RW, 0, "Local domain"); SYSCTL_NODE(_net_local, SOCK_STREAM, stream, CTLFLAG_RW, 0, "SOCK_STREAM"); SYSCTL_NODE(_net_local, SOCK_DGRAM, dgram, CTLFLAG_RW, 0, "SOCK_DGRAM"); +SYSCTL_NODE(_net_local, SOCK_SEQPACKET, seqpacket, CTLFLAG_RW, 0, + "SOCK_SEQPACKET"); SYSCTL_ULONG(_net_local_stream, OID_AUTO, sendspace, CTLFLAG_RW, &unpst_sendspace, 0, "Default stream send space."); @@ -167,6 +173,10 @@ SYSCTL_ULONG(_net_local_dgram, OID_AUTO, maxdgram, CTLFLAG_RW, &unpdg_sendspace, 0, "Default datagram send space."); SYSCTL_ULONG(_net_local_dgram, OID_AUTO, recvspace, CTLFLAG_RW, &unpdg_recvspace, 0, "Default datagram receive space."); +SYSCTL_ULONG(_net_local_seqpacket, OID_AUTO, maxseqpacket, CTLFLAG_RW, + &unpsp_sendspace, 0, "Default seqpacket send space."); +SYSCTL_ULONG(_net_local_seqpacket, OID_AUTO, recvspace, CTLFLAG_RW, + &unpsp_recvspace, 0, "Default seqpacket receive space."); SYSCTL_INT(_net_local, OID_AUTO, inflight, CTLFLAG_RD, &unp_rights, 0, "File descriptors in flight."); SYSCTL_INT(_net_local, OID_AUTO, deferred, CTLFLAG_RD, @@ -282,6 +292,7 @@ static void unp_process_defers(void * __unused, int); */ static struct domain localdomain; static struct pr_usrreqs uipc_usrreqs_dgram, uipc_usrreqs_stream; +static struct pr_usrreqs uipc_usrreqs_seqpacket; static struct protosw localsw[] = { { .pr_type = SOCK_STREAM, @@ -296,6 +307,20 @@ static struct protosw localsw[] = { .pr_flags = PR_ATOMIC|PR_ADDR|PR_RIGHTS, .pr_usrreqs = &uipc_usrreqs_dgram }, +{ + .pr_type = SOCK_SEQPACKET, + .pr_domain = &localdomain, + + /* + * XXXRW: For now, PR_ADDR because soreceive will bump into them + * due to our use of sbappendaddr. A new sbappend variants is needed + * that supports both atomic record writes and control data. + */ + .pr_flags = PR_ADDR|PR_ATOMIC|PR_CONNREQUIRED|PR_WANTRCVD| + PR_RIGHTS, + .pr_ctloutput = &uipc_ctloutput, + .pr_usrreqs = &uipc_usrreqs_seqpacket, +}, }; static struct domain localdomain = { @@ -378,6 +403,11 @@ uipc_attach(struct socket *so, int proto, struct thread *td) recvspace = unpdg_recvspace; break; + case SOCK_SEQPACKET: + sendspace = unpsp_sendspace; + recvspace = unpsp_recvspace; + break; + default: panic("uipc_attach"); } @@ -397,8 +427,22 @@ uipc_attach(struct socket *so, int proto, struct thread *td) UNP_LIST_LOCK(); unp->unp_gencnt = ++unp_gencnt; unp_count++; - LIST_INSERT_HEAD(so->so_type == SOCK_DGRAM ? &unp_dhead : &unp_shead, - unp, unp_link); + switch (so->so_type) { + case SOCK_STREAM: + LIST_INSERT_HEAD(&unp_shead, unp, unp_link); + break; + + case SOCK_DGRAM: + LIST_INSERT_HEAD(&unp_dhead, unp, unp_link); + break; + + case SOCK_SEQPACKET: + LIST_INSERT_HEAD(&unp_sphead, unp, unp_link); + break; + + default: + panic("uipc_attach"); + } UNP_LIST_UNLOCK(); return (0); @@ -732,11 +776,8 @@ uipc_rcvd(struct socket *so, int flags) unp = sotounpcb(so); KASSERT(unp != NULL, ("uipc_rcvd: unp == NULL")); - if (so->so_type == SOCK_DGRAM) - panic("uipc_rcvd DGRAM?"); - - if (so->so_type != SOCK_STREAM) - panic("uipc_rcvd unknown socktype"); + if (so->so_type != SOCK_STREAM && so->so_type != SOCK_SEQPACKET) + panic("uipc_rcvd socktype %d", so->so_type); /* * Adjust backpressure on sender and wakeup any waiting to write. @@ -851,6 +892,7 @@ uipc_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, break; } + case SOCK_SEQPACKET: case SOCK_STREAM: if ((so->so_state & SS_ISCONNECTED) == 0) { if (nam != NULL) { @@ -893,7 +935,8 @@ uipc_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, SOCKBUF_LOCK(&so2->so_rcv); if (unp2->unp_flags & UNP_WANTCRED) { /* - * Credentials are passed only once on SOCK_STREAM. + * Credentials are passed only once on SOCK_STREAM + * and SOCK_SEQPACKET. */ unp2->unp_flags &= ~UNP_WANTCRED; control = unp_addsockcred(td, control); @@ -902,11 +945,33 @@ uipc_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, * Send to paired receive port, and then reduce send buffer * hiwater marks to maintain backpressure. Wake up readers. */ - if (control != NULL) { - if (sbappendcontrol_locked(&so2->so_rcv, m, control)) + switch (so->so_type) { + case SOCK_STREAM: + if (control != NULL) { + if (sbappendcontrol_locked(&so2->so_rcv, m, + control)) + control = NULL; + } else + sbappend_locked(&so2->so_rcv, m); + break; + + case SOCK_SEQPACKET: { + const struct sockaddr *from; + + from = &sun_noname; + if (sbappendaddr_locked(&so2->so_rcv, from, m, + control)) control = NULL; - } else - sbappend_locked(&so2->so_rcv, m); + break; + } + } + + /* + * XXXRW: While fine for SOCK_STREAM, this conflates maximum + * datagram size and back-pressure for SOCK_SEQPACKET, which + * can lead to undesired return of EMSGSIZE on send instead + * of more desirable blocking. + */ mbcnt_delta = so2->so_rcv.sb_mbcnt - unp2->unp_mbcnt; unp2->unp_mbcnt = so2->so_rcv.sb_mbcnt; sbcc = so2->so_rcv.sb_cc; @@ -969,7 +1034,8 @@ uipc_sense(struct socket *so, struct stat *sb) UNP_LINK_RLOCK(); UNP_PCB_LOCK(unp); unp2 = unp->unp_conn; - if (so->so_type == SOCK_STREAM && unp2 != NULL) { + if ((so->so_type == SOCK_STREAM || so->so_type == SOCK_SEQPACKET) && + unp2 != NULL) { so2 = unp2->unp_socket; sb->st_blksize += so2->so_rcv.sb_cc; } @@ -1039,6 +1105,26 @@ static struct pr_usrreqs uipc_usrreqs_dgram = { .pru_close = uipc_close, }; +static struct pr_usrreqs uipc_usrreqs_seqpacket = { + .pru_abort = uipc_abort, + .pru_accept = uipc_accept, + .pru_attach = uipc_attach, + .pru_bind = uipc_bind, + .pru_connect = uipc_connect, + .pru_connect2 = uipc_connect2, + .pru_detach = uipc_detach, + .pru_disconnect = uipc_disconnect, + .pru_listen = uipc_listen, + .pru_peeraddr = uipc_peeraddr, + .pru_rcvd = uipc_rcvd, + .pru_send = uipc_send, + .pru_sense = uipc_sense, + .pru_shutdown = uipc_shutdown, + .pru_sockaddr = uipc_sockaddr, + .pru_soreceive = soreceive_generic, /* XXX: or...? */ + .pru_close = uipc_close, +}; + static struct pr_usrreqs uipc_usrreqs_stream = { .pru_abort = uipc_abort, .pru_accept = uipc_accept, @@ -1340,6 +1426,7 @@ unp_connect2(struct socket *so, struct socket *so2, int req) break; case SOCK_STREAM: + case SOCK_SEQPACKET: unp2->unp_conn = unp; if (req == PRU_CONNECT && ((unp->unp_flags | unp2->unp_flags) & UNP_CONNWAIT)) @@ -1377,6 +1464,7 @@ unp_disconnect(struct unpcb *unp, struct unpcb *unp2) break; case SOCK_STREAM: + case SOCK_SEQPACKET: soisdisconnected(unp->unp_socket); unp2->unp_conn = NULL; soisdisconnected(unp2->unp_socket); @@ -1402,7 +1490,22 @@ unp_pcblist(SYSCTL_HANDLER_ARGS) struct unp_head *head; struct xunpcb *xu; - head = ((intptr_t)arg1 == SOCK_DGRAM ? &unp_dhead : &unp_shead); + switch ((intptr_t)arg1) { + case SOCK_STREAM: + head = &unp_shead; + break; + + case SOCK_DGRAM: + head = &unp_dhead; + break; + + case SOCK_SEQPACKET: + head = &unp_sphead; + break; + + default: + panic("unp_pcblist: arg1 %d", (int)(intptr_t)arg1); + } /* * The process of preparing the PCB list is too time-consuming and @@ -1515,6 +1618,9 @@ SYSCTL_PROC(_net_local_dgram, OID_AUTO, pcblist, CTLFLAG_RD, SYSCTL_PROC(_net_local_stream, OID_AUTO, pcblist, CTLFLAG_RD, (caddr_t)(long)SOCK_STREAM, 0, unp_pcblist, "S,xunpcb", "List of active local stream sockets"); +SYSCTL_PROC(_net_local_seqpacket, OID_AUTO, pcblist, CTLFLAG_RD, + (caddr_t)(long)SOCK_SEQPACKET, 0, unp_pcblist, "S,xunpcb", + "List of active local seqpacket sockets"); static void unp_shutdown(struct unpcb *unp) @@ -1526,7 +1632,8 @@ unp_shutdown(struct unpcb *unp) UNP_PCB_LOCK_ASSERT(unp); unp2 = unp->unp_conn; - if (unp->unp_socket->so_type == SOCK_STREAM && unp2 != NULL) { + if ((unp->unp_socket->so_type == SOCK_STREAM || + (unp->unp_socket->so_type == SOCK_SEQPACKET)) && unp2 != NULL) { so = unp2->unp_socket; if (so != NULL) socantrcvmore(so); @@ -1692,6 +1799,7 @@ unp_init(void) NULL, EVENTHANDLER_PRI_ANY); LIST_INIT(&unp_dhead); LIST_INIT(&unp_shead); + LIST_INIT(&unp_sphead); SLIST_INIT(&unp_defers); TASK_INIT(&unp_gc_task, 0, unp_gc, NULL); TASK_INIT(&unp_defer_task, 0, unp_process_defers, NULL); @@ -2065,7 +2173,8 @@ SYSCTL_INT(_net_local, OID_AUTO, taskcount, CTLFLAG_RD, &unp_taskcount, 0, static void unp_gc(__unused void *arg, int pending) { - struct unp_head *heads[] = { &unp_dhead, &unp_shead, NULL }; + struct unp_head *heads[] = { &unp_dhead, &unp_shead, &unp_sphead, + NULL }; struct unp_head **head; struct file *f, **unref; struct unpcb *unp; diff --git a/tools/regression/sockets/unix_seqpacket/Makefile b/tools/regression/sockets/unix_seqpacket/Makefile new file mode 100644 index 000000000..304506524 --- /dev/null +++ b/tools/regression/sockets/unix_seqpacket/Makefile @@ -0,0 +1,9 @@ +# +# $FreeBSD$ +# + +PROG= unix_seqpacket +CFLAGS+= -Wall -Werror +NO_MAN= + +.include diff --git a/tools/regression/sockets/unix_seqpacket/unix_seqpacket.c b/tools/regression/sockets/unix_seqpacket/unix_seqpacket.c new file mode 100644 index 000000000..40baa64a7 --- /dev/null +++ b/tools/regression/sockets/unix_seqpacket/unix_seqpacket.c @@ -0,0 +1,140 @@ +/*- + * Copyright (c) 2009 Robert N. M. Watson + * All rights reserved. + * + * This software was developed at the University of Cambridge Computer + * Laboratory with support from a grant from Google, Inc. + * + * 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. + * + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include + +#include +#include +#include +#include +#include +#include + +#define FAILERR(str) err(-1, "%s: %s", __func__, str) +#define FAILERRX(str) errx(-1, "%s: %s", __func__, str) + +static void +test_socket(void) +{ + int s; + + s = socket(PF_LOCAL, SOCK_SEQPACKET, 0); + if (s < 0) + FAILERR("socket"); + (void)close(s); +} + +static void +test_socketpair(void) +{ + int sv[2]; + + if (socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, sv) < 0) + FAILERR("socketpair"); + (void)close(sv[0]); + (void)close(sv[1]); +} + +static void +test_listen_unbound(void) +{ + int s; + + s = socket(PF_LOCAL, SOCK_SEQPACKET, 0); + if (s < 0) + FAILERR("socket"); + if (listen(s, -1) == 0) + FAILERRX("listen"); + (void)close(s); +} + +static void +test_bind(void) +{ + struct sockaddr_un sun; + char path[PATH_MAX]; + int s; + + snprintf(path, sizeof(path), "/tmp/lds.XXXXXXXXX"); + if (mktemp(path) == NULL) + FAILERR("mktemp"); + s = socket(PF_LOCAL, SOCK_SEQPACKET, 0); + if (s < 0) + FAILERR("socket"); + bzero(&sun, sizeof(sun)); + sun.sun_family = AF_LOCAL; + sun.sun_len = sizeof(sun); + strlcpy(sun.sun_path, path, sizeof(sun.sun_path)); + if (bind(s, (struct sockaddr *)&sun, sizeof(sun)) < 0) + FAILERR("bind"); + close(s); + (void)unlink(path); +} + +static void +test_listen_bound(void) +{ + struct sockaddr_un sun; + char path[PATH_MAX]; + int s; + + snprintf(path, sizeof(path), "/tmp/lds.XXXXXXXXX"); + if (mktemp(path) == NULL) + FAILERR("mktemp"); + s = socket(PF_LOCAL, SOCK_SEQPACKET, 0); + if (s < 0) + FAILERR("socket"); + bzero(&sun, sizeof(sun)); + sun.sun_family = AF_LOCAL; + sun.sun_len = sizeof(sun); + strlcpy(sun.sun_path, path, sizeof(sun.sun_path)); + if (bind(s, (struct sockaddr *)&sun, sizeof(sun)) < 0) + FAILERR("bind"); + if (listen(s, -1)) { + (void)unlink(path); + FAILERR("bind"); + } + close(s); + (void)unlink(path); +} + +int +main(int argc, char *argv[]) +{ + + test_socket(); + test_socketpair(); + test_listen_unbound(); + test_bind(); + test_listen_bound(); +} diff --git a/tools/regression/sockets/unix_seqpacket_exercise/Makefile b/tools/regression/sockets/unix_seqpacket_exercise/Makefile new file mode 100644 index 000000000..494575b17 --- /dev/null +++ b/tools/regression/sockets/unix_seqpacket_exercise/Makefile @@ -0,0 +1,9 @@ +# +# $FreeBSD$ +# + +PROG=unix_seqpacket_exercise +CFLAGS+=-Wall -Werror +NO_MAN= + +.include diff --git a/tools/regression/sockets/unix_seqpacket_exercise/unix_seqpacket_exercise.c b/tools/regression/sockets/unix_seqpacket_exercise/unix_seqpacket_exercise.c new file mode 100644 index 000000000..a16c38497 --- /dev/null +++ b/tools/regression/sockets/unix_seqpacket_exercise/unix_seqpacket_exercise.c @@ -0,0 +1,435 @@ +/*- + * Copyright (c) 2009 Robert N. M. Watson + * All rights reserved. + * + * This software was developed at the University of Cambridge Computer + * Laboratory with support from a grant from Google, Inc. + * + * 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. + * + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define min(x, y) (x < y ? x : y) + +#define BUFLEN 32768 + +#define SEQPACKET_RCVBUF (131072-16) +#define SEQPACKET_SNDBUF (131072-16) + +#define FAILERR(str) err(-1, "%s: %s", __func__, str) +#define FAILNERR(str, n) err(-1, "%s %zd: %s", __func__, n, str) +#define FAILNMERR(str, n, m) err(-1, "%s %zd %d: %s", __func__, n, m, str) +#define FAILERRX(str) errx(-1, "%s: %s", __func__, str) +#define FAILNERRX(str, n) errx(-1, "%s %zd: %s", __func__, n, str) +#define FAILNMERRX(str, n, m) errx(-1, "%s %zd %d: %s", __func__, n, m, str) + +static int ann = 0; + +#define ANN() (ann ? warnx("%s: start", __func__) : 0) +#define ANNN(n) (ann ? warnx("%s %zd: start", __func__, (n)) : 0) +#define ANNNM(n, m) (ann ? warnx("%s %zd %d: start", __func__, (n), (m)):0) + +#define OK() warnx("%s: ok", __func__) +#define OKN(n) warnx("%s %zd: ok", __func__, (n)) +#define OKNM(n, m) warnx("%s %zd %d: ok", __func__, (n), (m)) + +#ifdef SO_NOSIGPIPE +#define NEW_SOCKET(s) do { \ + int i; \ + \ + (s) = socket(PF_LOCAL, SOCK_SEQPACKET, 0); \ + if ((s) < 0) \ + FAILERR("socket"); \ + \ + i = 1; \ + if (setsockopt((s), SOL_SOCKET, SO_NOSIGPIPE, &i, sizeof(i)) < 0) \ + FAILERR("setsockopt SO_NOSIGPIPE"); \ + \ + i = SEQPACKET_RCVBUF; \ + if (setsockopt((s), SOL_SOCKET, SO_RCVBUF, &i, sizeof(i)) < 0) \ + FAILERR("setsockopt SO_RCVBUF"); \ + \ + i = SEQPACKET_SNDBUF; \ + if (setsockopt((s), SOL_SOCKET, SO_SNDBUF, &i, sizeof(i)) < 0) \ + FAILERR("setsockopt SO_SNDBUF"); \ +} while (0) +#else +#define NEW_SOCKET(s) do { \ + int i; \ + \ + (s) = socket(PF_LOCAL, SOCK_SEQPACKET, 0); \ + if ((s) < 0) \ + FAILERR("socket"); \ + \ + i = SEQPACKET_RCVBUF; \ + if (setsockopt((s), SOL_SOCKET, SO_RCVBUF, &i, sizeof(i)) < 0) \ + FAILERR("setsockopt SO_RCVBUF"); \ + \ + i = SEQPACKET_SNDBUF; \ + if (setsockopt((s), SOL_SOCKET, SO_SNDBUF, &i, sizeof(i)) < 0) \ + FAILERR("setsockopt SO_SNDBUF"); \ +} while (0) +#endif + +static void +server(int s_listen) +{ + char buffer[BUFLEN]; + ssize_t ssize_recv, ssize_send; + socklen_t socklen; + int i, s_accept; + + while (1) { + s_accept = accept(s_listen, NULL, 0); + if (s_accept >= 0) { + i = SEQPACKET_RCVBUF; + if (setsockopt(s_accept, SOL_SOCKET, SO_RCVBUF, &i, + sizeof(i)) < 0) { + warn("server: setsockopt SO_RCVBUF"); + close(s_accept); + continue; + } + + if (getsockopt(s_accept, SOL_SOCKET, SO_RCVBUF, &i, + &socklen) < 0) { + warn("server: getsockopt SO_RCVBUF"); + close(s_accept); + continue; + } + if (i != SEQPACKET_RCVBUF) { + warnx("server: getsockopt SO_RCVBUF wrong %d", + i); + close(s_accept); + continue; + } + + socklen = sizeof(i); + if (getsockopt(s_accept, SOL_SOCKET, SO_SNDBUF, &i, + &socklen) < 0) { + warn("server: getsockopt SO_SNDBUF"); + close(s_accept); + continue; + } + if (i != SEQPACKET_SNDBUF) { + warnx("server: getsockopt SO_SNDBUF wrong %d", + i); + close(s_accept); + continue; + } + + do { + ssize_recv = recv(s_accept, buffer, + sizeof(buffer), 0); + if (ssize_recv == 0) + break; + if (ssize_recv < 0) { + warn("server: recv"); + break; + } + ssize_send = send(s_accept, buffer, + ssize_recv, 0); + if (ssize_send == 0) + break; + if (ssize_send < 0) { + warn("server: send"); + break; + } + if (ssize_send != ssize_recv) + warnx("server: recv %zd sent %zd", + ssize_recv, ssize_send); + } while (1); + close(s_accept); + } else + warn("server: accept"); + } +} + +static void +test_connect(struct sockaddr_un *sun) +{ + int s; + + ANN(); + NEW_SOCKET(s); + if (connect(s, (struct sockaddr *)sun, sizeof(*sun)) < 0) + FAILERR("connect"); + (void)close(s); + OK(); +} + +static void +test_connect_send(struct sockaddr_un *sun) +{ + ssize_t ssize; + char ch; + int s; + + ANN(); + NEW_SOCKET(s); + if (connect(s, (struct sockaddr *)sun, sizeof(*sun)) < 0) + FAILERR("connect"); + ssize = send(s, &ch, sizeof(ch), 0); + if (ssize < 0) + FAILERR("send"); + if (ssize != sizeof(ch)) + FAILERRX("send wrong size"); + (void)close(s); + OK(); +} + +static void +test_connect_shutdown_send(struct sockaddr_un *sun) +{ + ssize_t ssize; + char ch; + int s; + + ANN(); + NEW_SOCKET(s); + if (connect(s, (struct sockaddr *)sun, sizeof(*sun)) < 0) + FAILERR("connect"); + if (shutdown(s, SHUT_RDWR) < 0) + FAILERR("shutdown SHUT_RDWR"); + ssize = send(s, &ch, sizeof(ch), 0); + if (ssize >= 0) + FAILERRX("send"); + if (errno != EPIPE) + FAILERR("send unexpected error"); + (void)close(s); + OK(); +} + +static void +test_connect_send_recv(struct sockaddr_un *sun, size_t size) +{ + char buf[size + 4]; /* Detect extra bytes. */ + size_t truncsize; + ssize_t ssize; + int s; + + ANNN(size); + NEW_SOCKET(s); + if (connect(s, (struct sockaddr *)sun, sizeof(*sun)) < 0) + FAILNERR("connect", size); + ssize = send(s, buf, size, 0); + if (ssize < 0 && size >= SEQPACKET_RCVBUF) + goto out; + if (ssize < 0) + FAILNERR("send", size); + if (ssize == 0) + FAILNERR("send eof", size); + if (ssize != size) + FAILNERRX("send size", size); + + truncsize = min(size, BUFLEN); + ssize = recv(s, buf, sizeof(buf), 0); + if (ssize < 0) + FAILNERR("recv", size); + if (ssize == 0) + FAILNERRX("recv eof", size); + if (ssize < truncsize) + FAILNERRX("recv too few bytes", size); + if (ssize > truncsize) + FAILNERRX("recv too many bytes", size); +out: + (void)close(s); + OKN(size); +} + +static void +test_connect_send_recv_count(struct sockaddr_un *sun, int count, size_t size) +{ + char buf[size + 4]; /* Detect extra bytes and coalescing. */ + size_t truncsize; + ssize_t ssize; + int i, s; + + ANNNM(size, count); + NEW_SOCKET(s); + if (connect(s, (struct sockaddr *)sun, sizeof(*sun)) < 0) + FAILNMERR("connect", size, count); + for (i = 0; i < count; i++) { + usleep(5000); + ssize = send(s, buf, size, 0); + if (ssize < 0 && size >= SEQPACKET_RCVBUF) + goto out; + if (ssize < 0) + FAILNMERR("send", size, count); + if (ssize == 0) + FAILNMERRX("send eof", size, count); + if (ssize != size) + FAILNMERRX("send size", size, count); + } + + truncsize = min(size, BUFLEN); + for (i = 0; i < count; i++) { + ssize = recv(s, buf, sizeof(buf), 0); + if (ssize < 0) + FAILNMERR("recv", size, count); + if (ssize == 0) + FAILNMERRX("recv eof", size, count); + if (ssize < truncsize) + FAILNMERRX("recv too few bytes", size, count); + if (ssize > truncsize) + FAILNMERRX("recv too many bytes", size, count); + } +out: + (void)close(s); + OKNM(size, count); +} + +static void +test_sendto(struct sockaddr_un *sun) +{ + ssize_t ssize; + char ch; + int s; + + ANN(); + NEW_SOCKET(s); + ssize = sendto(s, &ch, sizeof(ch), 0, (struct sockaddr *)sun, + sizeof(*sun)); + if (ssize < 0) + FAILERR("sendto"); + (void)close(s); + OK(); +} + +static void +client(struct sockaddr_un *sun) +{ + size_t sizes[] = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, + 4096, 8192, 16384, 32768, 65536 /*, 131072 */}; + int c, i; + + test_connect(sun); + test_connect_send(sun); + test_connect_shutdown_send(sun); + + /* + * Try a range of sizes and packet counts. + */ + for (i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++) + test_connect_send_recv(sun, sizes[i]); + for (c = 1; c <= 8; c++) { + for (i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++) + test_connect_send_recv_count(sun, c, sizes[i]); + } + test_sendto(sun); + printf("client done\n"); +} + +int +main(int argc, char *argv[]) +{ + struct sockaddr_un sun; + char path[PATH_MAX]; + pid_t pid_client, pid_server; + int i, s_listen; + + snprintf(path, sizeof(path), "/tmp/lds_exercise.XXXXXXXXX"); + if (mktemp(path) == NULL) + FAILERR("mktemp"); + + s_listen = socket(PF_LOCAL, SOCK_SEQPACKET, 0); + if (s_listen < 0) { + (void)unlink(path); + FAILERR("socket"); + } + + i = SEQPACKET_RCVBUF; + if (setsockopt(s_listen, SOL_SOCKET, SO_RCVBUF, &i, sizeof(i)) < 0) { + (void)unlink(path); + FAILERR("setsockopt SO_RCVBUF"); + } + + i = SEQPACKET_SNDBUF; + if (setsockopt(s_listen, SOL_SOCKET, SO_SNDBUF, &i, sizeof(i)) < 0) { + (void)unlink(path); + FAILERR("setsockopt SO_SNDBUF"); + } + + i = 1; + if (setsockopt(s_listen, SOL_SOCKET, SO_NOSIGPIPE, &i, sizeof(i)) + < 0) { + (void)unlink(path); + FAILERR("setsockopt SO_NOSIGPIPE"); + } + + bzero(&sun, sizeof(sun)); + sun.sun_len = sizeof(sun); + sun.sun_family = AF_LOCAL; + strlcpy(sun.sun_path, path, sizeof(sun.sun_path)); + + if (bind(s_listen, (struct sockaddr *)&sun, sizeof(sun)) < 0) { + (void)unlink(path); + FAILERR("bind"); + } + + if (listen(s_listen, -1) < 0) { + (void)unlink(path); + FAILERR("listen"); + } + + pid_server = fork(); + if (pid_server < 0) { + (void)unlink(path); + FAILERR("fork"); + } + if (pid_server == 0) { + server(s_listen); + return (0); + } + + pid_client = fork(); + if (pid_client < 0) { + (void)kill(pid_server, SIGKILL); + (void)unlink(path); + FAILERR("fork"); + } + if (pid_client == 0) { + client(&sun); + return (0); + } + + /* + * When the client is done, kill the server and clean up. + */ + (void)waitpid(pid_client, NULL, 0); + (void)kill(pid_server, SIGKILL); + (void)unlink(path); + return (0); +} diff --git a/usr.bin/netstat/main.c b/usr.bin/netstat/main.c index d9556400e..b8e662dd0 100644 --- a/usr.bin/netstat/main.c +++ b/usr.bin/netstat/main.c @@ -186,6 +186,8 @@ static struct nlist nl[] = { { .n_name = "_mfctablesize" }, #define N_ARPSTAT 55 { .n_name = "_arpstat" }, +#define N_UNP_SPHEAD 56 + { .n_name = "unp_sphead" }, { .n_name = NULL }, }; @@ -627,7 +629,8 @@ main(int argc, char *argv[]) #endif /* NETGRAPH */ if ((af == AF_UNIX || af == AF_UNSPEC) && !sflag) unixpr(nl[N_UNP_COUNT].n_value, nl[N_UNP_GENCNT].n_value, - nl[N_UNP_DHEAD].n_value, nl[N_UNP_SHEAD].n_value); + nl[N_UNP_DHEAD].n_value, nl[N_UNP_SHEAD].n_value, + nl[N_UNP_SPHEAD].n_value); exit(0); } diff --git a/usr.bin/netstat/netstat.h b/usr.bin/netstat/netstat.h index 98a03893d..6678def54 100644 --- a/usr.bin/netstat/netstat.h +++ b/usr.bin/netstat/netstat.h @@ -158,7 +158,7 @@ void ddp_stats(u_long, const char *, int, int); void netgraphprotopr(u_long, const char *, int, int); #endif -void unixpr(u_long, u_long, u_long, u_long); +void unixpr(u_long, u_long, u_long, u_long, u_long); void esis_stats(u_long, const char *, int, int); void clnp_stats(u_long, const char *, int, int); diff --git a/usr.bin/netstat/unix.c b/usr.bin/netstat/unix.c index 209fc8d49..0ad8f34a5 100644 --- a/usr.bin/netstat/unix.c +++ b/usr.bin/netstat/unix.c @@ -193,21 +193,37 @@ fail: } void -unixpr(u_long count_off, u_long gencnt_off, u_long dhead_off, u_long shead_off) +unixpr(u_long count_off, u_long gencnt_off, u_long dhead_off, u_long shead_off, + u_long sphead_off) { char *buf; int ret, type; struct xsocket *so; struct xunpgen *xug, *oxug; struct xunpcb *xunp; + u_long head_off; for (type = SOCK_STREAM; type <= SOCK_SEQPACKET; type++) { if (live) ret = pcblist_sysctl(type, &buf); - else - ret = pcblist_kvm(count_off, gencnt_off, - type == SOCK_STREAM ? shead_off : - (type == SOCK_DGRAM ? dhead_off : 0), &buf); + else { + head_off = 0; + switch (type) { + case SOCK_STREAM: + head_off = shead_off; + break; + + case SOCK_DGRAM: + head_off = dhead_off; + break; + + case SOCK_SEQPACKET: + head_off = sphead_off; + break; + } + ret = pcblist_kvm(count_off, gencnt_off, head_off, + &buf); + } if (ret == -1) continue; if (ret < 0) -- 2.45.0