Index: head/lib/libc/sys/getsockopt.2 =================================================================== --- head/lib/libc/sys/getsockopt.2 +++ head/lib/libc/sys/getsockopt.2 @@ -187,6 +187,7 @@ .It Dv SO_LISTENQLEN Ta "get complete queue length of the socket (get only)" .It Dv SO_LISTENINCQLEN Ta "get incomplete queue length of the socket (get only)" .It Dv SO_USER_COOKIE Ta "set the 'so_user_cookie' value for the socket (uint32_t, set only)" +.It Dv SO_TS_CLOCK Ta "set specific format of timestamp returned by SO_TIMESTAMP" .El .Pp .Dv SO_DEBUG @@ -435,7 +436,7 @@ .Dv SO_BINTIME . The .Vt cmsghdr -fields have the following values for TIMESTAMP: +fields have the following values for TIMESTAMP by default: .Bd -literal cmsg_len = CMSG_LEN(sizeof(struct timeval)); cmsg_level = SOL_SOCKET; @@ -450,6 +451,24 @@ cmsg_type = SCM_BINTIME; .Ed .Pp +Additional timestamp types are available by following +.Dv SO_TIMESTAMP +with +.Dv SO_TS_CLOCK , +which requests specific timestamp format to be returned instead of +.Dv SCM_TIMESTAMP when +.Dv SO_TIMESTAMP is enabled. +The following +.Dv SO_TS_CLOCK +values are recognized in +.Fx : +.Bl -column SO_TS_CLOCK -offset indent +.It Dv SO_TS_REALTIME_MICRO Ta "realtime (SCM_TIMESTAMP, struct timeval), default" +.It Dv SO_TS_BINTIME Ta "realtime (SCM_BINTIME, struct bintime)" +.It Dv SO_TS_REALTIME Ta "realtime (SCM_REALTIME, struct timespec)" +.It Dv SO_TS_MONOTONIC Ta "monotonic time (SCM_MONOTONIC, struct timespec)" +.El +.Pp .Dv SO_ACCEPTCONN , .Dv SO_TYPE , .Dv SO_PROTOCOL Index: head/sys/kern/uipc_socket.c =================================================================== --- head/sys/kern/uipc_socket.c +++ head/sys/kern/uipc_socket.c @@ -2687,6 +2687,18 @@ #endif break; + case SO_TS_CLOCK: + error = sooptcopyin(sopt, &optval, sizeof optval, + sizeof optval); + if (error) + goto bad; + if (optval < 0 || optval > SO_TS_CLOCK_MAX) { + error = EINVAL; + goto bad; + } + so->so_ts_clock = optval; + break; + default: if (V_socket_hhh[HHOOK_SOCKET_OPT]->hhh_nhooks > 0) error = hhook_run_socket(so, sopt, @@ -2874,6 +2886,10 @@ optval = so->so_incqlen; goto integer; + case SO_TS_CLOCK: + optval = so->so_ts_clock; + goto integer; + default: if (V_socket_hhh[HHOOK_SOCKET_OPT]->hhh_nhooks > 0) error = hhook_run_socket(so, sopt, Index: head/sys/kern/uipc_usrreq.c =================================================================== --- head/sys/kern/uipc_usrreq.c +++ head/sys/kern/uipc_usrreq.c @@ -1899,6 +1899,7 @@ struct filedescent *fde, **fdep, *fdev; struct file *fp; struct timeval *tv; + struct timespec *ts; int i, *fdp; void *data; socklen_t clen = control->m_len, datalen; @@ -2019,6 +2020,30 @@ bintime(bt); break; + case SCM_REALTIME: + *controlp = sbcreatecontrol(NULL, sizeof(*ts), + SCM_REALTIME, SOL_SOCKET); + if (*controlp == NULL) { + error = ENOBUFS; + goto out; + } + ts = (struct timespec *) + CMSG_DATA(mtod(*controlp, struct cmsghdr *)); + nanotime(ts); + break; + + case SCM_MONOTONIC: + *controlp = sbcreatecontrol(NULL, sizeof(*ts), + SCM_MONOTONIC, SOL_SOCKET); + if (*controlp == NULL) { + error = ENOBUFS; + goto out; + } + ts = (struct timespec *) + CMSG_DATA(mtod(*controlp, struct cmsghdr *)); + nanouptime(ts); + break; + default: error = EINVAL; goto out; Index: head/sys/netinet/ip_input.c =================================================================== --- head/sys/netinet/ip_input.c +++ head/sys/netinet/ip_input.c @@ -1157,30 +1157,48 @@ icmp_error(mcopy, type, code, dest.s_addr, mtu); } +#define CHECK_SO_CT(sp, ct) \ + (((sp->so_options & SO_TIMESTAMP) && (sp->so_ts_clock == ct)) ? 1 : 0) + void ip_savecontrol(struct inpcb *inp, struct mbuf **mp, struct ip *ip, struct mbuf *m) { - if (inp->inp_socket->so_options & (SO_BINTIME | SO_TIMESTAMP)) { + if ((inp->inp_socket->so_options & SO_BINTIME) || + CHECK_SO_CT(inp->inp_socket, SO_TS_BINTIME)) { struct bintime bt; bintime(&bt); - if (inp->inp_socket->so_options & SO_BINTIME) { - *mp = sbcreatecontrol((caddr_t)&bt, sizeof(bt), - SCM_BINTIME, SOL_SOCKET); - if (*mp) - mp = &(*mp)->m_next; - } - if (inp->inp_socket->so_options & SO_TIMESTAMP) { - struct timeval tv; + *mp = sbcreatecontrol((caddr_t)&bt, sizeof(bt), + SCM_BINTIME, SOL_SOCKET); + if (*mp) + mp = &(*mp)->m_next; + } + if (CHECK_SO_CT(inp->inp_socket, SO_TS_REALTIME_MICRO)) { + struct timeval tv; - bintime2timeval(&bt, &tv); - *mp = sbcreatecontrol((caddr_t)&tv, sizeof(tv), - SCM_TIMESTAMP, SOL_SOCKET); - if (*mp) - mp = &(*mp)->m_next; - } + microtime(&tv); + *mp = sbcreatecontrol((caddr_t)&tv, sizeof(tv), + SCM_TIMESTAMP, SOL_SOCKET); + if (*mp) + mp = &(*mp)->m_next; + } else if (CHECK_SO_CT(inp->inp_socket, SO_TS_REALTIME)) { + struct timespec ts; + + nanotime(&ts); + *mp = sbcreatecontrol((caddr_t)&ts, sizeof(ts), + SCM_REALTIME, SOL_SOCKET); + if (*mp) + mp = &(*mp)->m_next; + } else if (CHECK_SO_CT(inp->inp_socket, SO_TS_MONOTONIC)) { + struct timespec ts; + + nanouptime(&ts); + *mp = sbcreatecontrol((caddr_t)&ts, sizeof(ts), + SCM_MONOTONIC, SOL_SOCKET); + if (*mp) + mp = &(*mp)->m_next; } if (inp->inp_flags & INP_RECVDSTADDR) { *mp = sbcreatecontrol((caddr_t)&ip->ip_dst, Index: head/sys/netinet6/ip6_input.c =================================================================== --- head/sys/netinet6/ip6_input.c +++ head/sys/netinet6/ip6_input.c @@ -1226,13 +1226,48 @@ #ifdef SO_TIMESTAMP if ((inp->inp_socket->so_options & SO_TIMESTAMP) != 0) { - struct timeval tv; + union { + struct timeval tv; + struct bintime bt; + struct timespec ts; + } t; + + switch (inp->inp_socket->so_ts_clock) { + case SO_TS_REALTIME_MICRO: + microtime(&t.tv); + *mp = sbcreatecontrol((caddr_t) &t.tv, sizeof(t.tv), + SCM_TIMESTAMP, SOL_SOCKET); + if (*mp) + mp = &(*mp)->m_next; + break; - microtime(&tv); - *mp = sbcreatecontrol((caddr_t) &tv, sizeof(tv), - SCM_TIMESTAMP, SOL_SOCKET); - if (*mp) - mp = &(*mp)->m_next; + case SO_TS_BINTIME: + bintime(&t.bt); + *mp = sbcreatecontrol((caddr_t)&t.bt, sizeof(t.bt), + SCM_BINTIME, SOL_SOCKET); + if (*mp) + mp = &(*mp)->m_next; + break; + + case SO_TS_REALTIME: + nanotime(&t.ts); + *mp = sbcreatecontrol((caddr_t)&t.ts, sizeof(t.ts), + SCM_REALTIME, SOL_SOCKET); + if (*mp) + mp = &(*mp)->m_next; + break; + + case SO_TS_MONOTONIC: + nanouptime(&t.ts); + *mp = sbcreatecontrol((caddr_t)&t.ts, sizeof(t.ts), + SCM_MONOTONIC, SOL_SOCKET); + if (*mp) + mp = &(*mp)->m_next; + break; + + default: + panic("unknown (corrupted) so_ts_clock"); + } } #endif Index: head/sys/sys/socket.h =================================================================== --- head/sys/sys/socket.h +++ head/sys/sys/socket.h @@ -158,6 +158,16 @@ #define SO_USER_COOKIE 0x1015 /* user cookie (dummynet etc.) */ #define SO_PROTOCOL 0x1016 /* get socket protocol (Linux name) */ #define SO_PROTOTYPE SO_PROTOCOL /* alias for SO_PROTOCOL (SunOS name) */ +#define SO_TS_CLOCK 0x1017 /* clock type used for SO_TIMESTAMP */ +#endif + +#if __BSD_VISIBLE +#define SO_TS_REALTIME_MICRO 0 /* microsecond resolution, realtime */ +#define SO_TS_BINTIME 1 /* sub-nanosecond resolution, realtime */ +#define SO_TS_REALTIME 2 /* nanosecond resolution, realtime */ +#define SO_TS_MONOTONIC 3 /* nanosecond resolution, monotonic */ +#define SO_TS_DEFAULT SO_TS_REALTIME_MICRO +#define SO_TS_CLOCK_MAX SO_TS_MONOTONIC #endif /* @@ -534,6 +544,8 @@ #define SCM_TIMESTAMP 0x02 /* timestamp (struct timeval) */ #define SCM_CREDS 0x03 /* process creds (struct cmsgcred) */ #define SCM_BINTIME 0x04 /* timestamp (struct bintime) */ +#define SCM_REALTIME 0x05 /* timestamp (struct timespec) */ +#define SCM_MONOTONIC 0x06 /* timestamp (struct timespec) */ #endif #if __BSD_VISIBLE Index: head/sys/sys/socketvar.h =================================================================== --- head/sys/sys/socketvar.h +++ head/sys/sys/socketvar.h @@ -127,6 +127,8 @@ int so_fibnum; /* routing domain for this socket */ uint32_t so_user_cookie; + int so_ts_clock; /* type of the clock used for timestamps */ + void *so_pspare[2]; /* packet pacing / general use */ int so_ispare[2]; /* packet pacing / general use */ }; Index: head/tools/regression/sockets/udp_pingpong/Makefile =================================================================== --- head/tools/regression/sockets/udp_pingpong/Makefile +++ head/tools/regression/sockets/udp_pingpong/Makefile @@ -0,0 +1,7 @@ +# $FreeBSD$ + +PROG= udp_pingpong +MAN= +WARNS?= 6 + +.include Index: head/tools/regression/sockets/udp_pingpong/udp_pingpong.c =================================================================== --- head/tools/regression/sockets/udp_pingpong/udp_pingpong.c +++ head/tools/regression/sockets/udp_pingpong/udp_pingpong.c @@ -0,0 +1,651 @@ +/*- + * Copyright (c) 2017 Maksym Sobolyev + * 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. + * + * 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. + */ + +/* + * The test that setups two processes A and B and make A sending + * B UDP packet(s) and B send it back. The time of sending is recorded + * in the payload and time of the arrival is either determined by + * reading clock after recv() completes or using kernel-supplied + * via recvmsg(). End-to-end time t(A->B->A) is then calculated + * and compared against time for both t(A->B) + t(B->A) to make + * sure it makes sense. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NPKTS 1000 +#define PKT_SIZE 128 +/* Timeout to receive pong on the side A, 100ms */ +#define SRECV_TIMEOUT (1 * 100) +/* + * Timeout to receive ping on the side B. 4x as large as on the side A, + * so that in the case of packet loss the side A will have a chance to + * realize that and send few more before B bails out. + */ +#define RRECV_TIMEOUT (SRECV_TIMEOUT * 4) +#define MIN_NRECV ((NPKTS * 99) / 100) /* 99% */ + +//#define SIMULATE_PLOSS + +struct trip_ts { + struct timespec sent; + struct timespec recvd; +}; + +struct test_pkt { + int pnum; + struct trip_ts tss[2]; + int lost; + unsigned char data[PKT_SIZE]; +}; + +struct test_ctx { + const char *name; + int fds[2]; + struct pollfd pfds[2]; + union { + struct sockaddr_in v4; + struct sockaddr_in6 v6; + } sin[2]; + struct test_pkt test_pkts[NPKTS]; + int nsent; + int nrecvd; + clockid_t clock; + int use_recvmsg; + int ts_type; +}; + +struct rtt { + struct timespec a2b; + struct timespec b2a; + struct timespec e2e; + struct timespec a2b_b2a; +}; + +#define SEC(x) ((x)->tv_sec) +#define NSEC(x) ((x)->tv_nsec) +#define NSEC_MAX 1000000000L +#define NSEC_IN_USEC 1000L + +#define timespecsub2(r, v, u) \ + do { \ + SEC(r) = SEC(v) - SEC(u); \ + NSEC(r) = NSEC(v) - NSEC(u); \ + if (NSEC(r) < 0 && (SEC(r) > 0 || NSEC(r) <= -NSEC_MAX)) { \ + SEC(r)--; \ + NSEC(r) += NSEC_MAX; \ + } \ + } while (0); + +#define timespecadd2(r, v, u) \ + do { \ + SEC(r) = SEC(v) + SEC(u); \ + NSEC(r) = NSEC(v) + NSEC(u); \ + if (NSEC(r) >= NSEC_MAX) { \ + SEC(r)++; \ + NSEC(r) -= NSEC_MAX; \ + } \ + } while (0); + +#define timespeccmp(t, c, u) \ + ((SEC(t) == SEC(u)) ? \ + (NSEC(t) c NSEC(u)) : \ + (SEC(t) c SEC(u))) + +#define timeval2timespec(tv, ts) \ + do { \ + SEC(ts) = (tv)->tv_sec; \ + NSEC(ts) = (tv)->tv_usec * NSEC_IN_USEC; \ + } while (0); + +static const struct timespec zero_ts; +/* 0.01s, should be more than enough for the loopback communication */ +static const struct timespec max_ts = {.tv_nsec = (NSEC_MAX / 100)}; + +enum ts_types {TT_TIMESTAMP = -2, TT_BINTIME = -1, + TT_REALTIME_MICRO = SO_TS_REALTIME_MICRO, TT_TS_BINTIME = SO_TS_BINTIME, + TT_REALTIME = SO_TS_REALTIME, TT_MONOTONIC = SO_TS_MONOTONIC}; + +static clockid_t +get_clock_type(struct test_ctx *tcp) +{ + switch (tcp->ts_type) { + case TT_TIMESTAMP: + case TT_BINTIME: + case TT_REALTIME_MICRO: + case TT_TS_BINTIME: + case TT_REALTIME: + return (CLOCK_REALTIME); + + case TT_MONOTONIC: + return (CLOCK_MONOTONIC); + } + abort(); +} + +static int +get_scm_type(struct test_ctx *tcp) +{ + switch (tcp->ts_type) { + case TT_TIMESTAMP: + case TT_REALTIME_MICRO: + return (SCM_TIMESTAMP); + + case TT_BINTIME: + case TT_TS_BINTIME: + return (SCM_BINTIME); + + case TT_REALTIME: + return (SCM_REALTIME); + + case TT_MONOTONIC: + return (SCM_MONOTONIC); + } + abort(); +} + +static size_t +get_scm_size(struct test_ctx *tcp) +{ + switch (tcp->ts_type) { + case TT_TIMESTAMP: + case TT_REALTIME_MICRO: + return (sizeof(struct timeval)); + + case TT_BINTIME: + case TT_TS_BINTIME: + return (sizeof(struct bintime)); + + case TT_REALTIME: + case TT_MONOTONIC: + return (sizeof(struct timespec)); + } + abort(); +} + +static void +setup_ts_sockopt(struct test_ctx *tcp, int fd) +{ + int rval, oname1, oname2, sval1, sval2; + + oname1 = SO_TIMESTAMP; + oname2 = -1; + sval2 = -1; + + switch (tcp->ts_type) { + case TT_REALTIME_MICRO: + case TT_TS_BINTIME: + case TT_REALTIME: + case TT_MONOTONIC: + oname2 = SO_TS_CLOCK; + sval2 = tcp->ts_type; + break; + + case TT_TIMESTAMP: + break; + + case TT_BINTIME: + oname1 = SO_BINTIME; + break; + + default: + abort(); + } + + sval1 = 1; + rval = setsockopt(fd, SOL_SOCKET, oname1, &sval1, + sizeof(sval1)); + if (rval != 0) { + err(1, "%s: setup_udp: setsockopt(%d, %d, 1)", tcp->name, + fd, oname1); + } + if (oname2 == -1) + return; + rval = setsockopt(fd, SOL_SOCKET, oname2, &sval2, + sizeof(sval2)); + if (rval != 0) { + err(1, "%s: setup_udp: setsockopt(%d, %d, %d)", + tcp->name, fd, oname2, sval2); + } +} + + +static void +setup_udp(struct test_ctx *tcp) +{ + int i; + socklen_t sin_len, af_len; + + af_len = sizeof(tcp->sin[0].v4); + for (i = 0; i < 2; i++) { + tcp->sin[i].v4.sin_len = af_len; + tcp->sin[i].v4.sin_family = AF_INET; + tcp->sin[i].v4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + tcp->fds[i] = socket(PF_INET, SOCK_DGRAM, 0); + if (tcp->fds[i] < 0) + err(1, "%s: setup_udp: socket", tcp->name); + if (bind(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], af_len) < 0) + err(1, "%s: setup_udp: bind(%s, %d)", tcp->name, + inet_ntoa(tcp->sin[i].v4.sin_addr), 0); + sin_len = af_len; + if (getsockname(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], &sin_len) < 0) + err(1, "%s: setup_udp: getsockname(%d)", tcp->name, tcp->fds[i]); + if (tcp->use_recvmsg != 0) { + setup_ts_sockopt(tcp, tcp->fds[i]); + } + + tcp->pfds[i].fd = tcp->fds[i]; + tcp->pfds[i].events = POLLIN; + } + + if (connect(tcp->fds[0], (struct sockaddr *)&tcp->sin[1], af_len) < 0) + err(1, "%s: setup_udp: connect(%s, %d)", tcp->name, + inet_ntoa(tcp->sin[1].v4.sin_addr), ntohs(tcp->sin[1].v4.sin_port)); + if (connect(tcp->fds[1], (struct sockaddr *)&tcp->sin[0], af_len) < 0) + err(1, "%s: setup_udp: connect(%s, %d)", tcp->name, + inet_ntoa(tcp->sin[0].v4.sin_addr), ntohs(tcp->sin[0].v4.sin_port)); +} + +static char * +inet_ntoa6(const void *sin6_addr) +{ + static char straddr[INET6_ADDRSTRLEN]; + + inet_ntop(AF_INET6, sin6_addr, straddr, sizeof(straddr)); + return (straddr); +} + +static void +setup_udp6(struct test_ctx *tcp) +{ + int i; + socklen_t sin_len, af_len; + + af_len = sizeof(tcp->sin[0].v6); + for (i = 0; i < 2; i++) { + tcp->sin[i].v6.sin6_len = af_len; + tcp->sin[i].v6.sin6_family = AF_INET6; + tcp->sin[i].v6.sin6_addr = in6addr_loopback; + tcp->fds[i] = socket(PF_INET6, SOCK_DGRAM, 0); + if (tcp->fds[i] < 0) + err(1, "%s: setup_udp: socket", tcp->name); + if (bind(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], af_len) < 0) + err(1, "%s: setup_udp: bind(%s, %d)", tcp->name, + inet_ntoa6(&tcp->sin[i].v6.sin6_addr), 0); + sin_len = af_len; + if (getsockname(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], &sin_len) < 0) + err(1, "%s: setup_udp: getsockname(%d)", tcp->name, tcp->fds[i]); + if (tcp->use_recvmsg != 0) { + setup_ts_sockopt(tcp, tcp->fds[i]); + } + + tcp->pfds[i].fd = tcp->fds[i]; + tcp->pfds[i].events = POLLIN; + } + + if (connect(tcp->fds[0], (struct sockaddr *)&tcp->sin[1], af_len) < 0) + err(1, "%s: setup_udp: connect(%s, %d)", tcp->name, + inet_ntoa6(&tcp->sin[1].v6.sin6_addr), + ntohs(tcp->sin[1].v6.sin6_port)); + if (connect(tcp->fds[1], (struct sockaddr *)&tcp->sin[0], af_len) < 0) + err(1, "%s: setup_udp: connect(%s, %d)", tcp->name, + inet_ntoa6(&tcp->sin[0].v6.sin6_addr), + ntohs(tcp->sin[0].v6.sin6_port)); +} + +static void +teardown_udp(struct test_ctx *tcp) +{ + + close(tcp->fds[0]); + close(tcp->fds[1]); +} + +static void +send_pkt(struct test_ctx *tcp, int pnum, int fdidx, const char *face) +{ + ssize_t r; + size_t slen; + + slen = sizeof(tcp->test_pkts[pnum]); + clock_gettime(get_clock_type(tcp), &tcp->test_pkts[pnum].tss[fdidx].sent); + r = send(tcp->fds[fdidx], &tcp->test_pkts[pnum], slen, 0); + if (r < 0) { + err(1, "%s: %s: send(%d)", tcp->name, face, tcp->fds[fdidx]); + } + if (r < (ssize_t)slen) { + errx(1, "%s: %s: send(%d): short send", tcp->name, face, + tcp->fds[fdidx]); + } + tcp->nsent += 1; +} + +#define PDATA(tcp, i) ((tcp)->test_pkts[(i)].data) + +static void +hdr_extract_ts(struct test_ctx *tcp, struct msghdr *mhp, struct timespec *tp) +{ + int scm_type; + size_t scm_size; + union { + struct timespec ts; + struct bintime bt; + struct timeval tv; + } tdata; + struct cmsghdr *cmsg; + + scm_type = get_scm_type(tcp); + scm_size = get_scm_size(tcp); + for (cmsg = CMSG_FIRSTHDR(mhp); cmsg != NULL; + cmsg = CMSG_NXTHDR(mhp, cmsg)) { + if ((cmsg->cmsg_level == SOL_SOCKET) && + (cmsg->cmsg_type == scm_type)) { + memcpy(&tdata, CMSG_DATA(cmsg), scm_size); + break; + } + } + if (cmsg == NULL) { + abort(); + } + switch (tcp->ts_type) { + case TT_REALTIME: + case TT_MONOTONIC: + *tp = tdata.ts; + break; + + case TT_TIMESTAMP: + case TT_REALTIME_MICRO: + timeval2timespec(&tdata.tv, tp); + break; + + case TT_BINTIME: + case TT_TS_BINTIME: + bintime2timespec(&tdata.bt, tp); + break; + + default: + abort(); + } +} + +static void +recv_pkt_recvmsg(struct test_ctx *tcp, int fdidx, const char *face, void *buf, + size_t rlen, struct timespec *tp) +{ + /* We use a union to make sure hdr is aligned */ + union { + struct cmsghdr hdr; + unsigned char buf[CMSG_SPACE(1024)]; + } cmsgbuf; + struct msghdr msg; + struct iovec iov; + ssize_t rval; + + memset(&msg, '\0', sizeof(msg)); + iov.iov_base = buf; + iov.iov_len = rlen; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + + rval = recvmsg(tcp->fds[fdidx], &msg, 0); + if (rval < 0) { + err(1, "%s: %s: recvmsg(%d)", tcp->name, face, tcp->fds[fdidx]); + } + if (rval < (ssize_t)rlen) { + errx(1, "%s: %s: recvmsg(%d): short recv", tcp->name, face, + tcp->fds[fdidx]); + } + + hdr_extract_ts(tcp, &msg, tp); +} + +static void +recv_pkt_recv(struct test_ctx *tcp, int fdidx, const char *face, void *buf, + size_t rlen, struct timespec *tp) +{ + ssize_t rval; + + rval = recv(tcp->fds[fdidx], buf, rlen, 0); + clock_gettime(get_clock_type(tcp), tp); + if (rval < 0) { + err(1, "%s: %s: recv(%d)", tcp->name, face, tcp->fds[fdidx]); + } + if (rval < (ssize_t)rlen) { + errx(1, "%s: %s: recv(%d): short recv", tcp->name, face, + tcp->fds[fdidx]); + } +} + +static int +recv_pkt(struct test_ctx *tcp, int fdidx, const char *face, int tout) +{ + int pr; + struct test_pkt recv_buf; + size_t rlen; + + pr = poll(&tcp->pfds[fdidx], 1, tout); + if (pr < 0) { + err(1, "%s: %s: poll(%d)", tcp->name, face, tcp->fds[fdidx]); + } + if (pr == 0) { + return (-1); + } + if(tcp->pfds[fdidx].revents != POLLIN) { + errx(1, "%s: %s: poll(%d): unexpected result", tcp->name, face, + tcp->fds[fdidx]); + } + rlen = sizeof(recv_buf); + if (tcp->use_recvmsg == 0) { + recv_pkt_recv(tcp, fdidx, face, &recv_buf, rlen, + &recv_buf.tss[fdidx].recvd); + } else { + recv_pkt_recvmsg(tcp, fdidx, face, &recv_buf, rlen, + &recv_buf.tss[fdidx].recvd); + } + if (recv_buf.pnum < 0 || recv_buf.pnum >= NPKTS || + memcmp(recv_buf.data, PDATA(tcp, recv_buf.pnum), PKT_SIZE) != 0) { + errx(1, "%s: %s: recv(%d): corrupted data, packet %d", tcp->name, + face, tcp->fds[fdidx], recv_buf.pnum); + } + tcp->nrecvd += 1; + memcpy(tcp->test_pkts[recv_buf.pnum].tss, recv_buf.tss, + sizeof(recv_buf.tss)); + tcp->test_pkts[recv_buf.pnum].lost = 0; + return (recv_buf.pnum); +} + +static void +test_server(struct test_ctx *tcp) +{ + int i, j; + + for (i = 0; i < NPKTS; i++) { + send_pkt(tcp, i, 0, __FUNCTION__); + j = recv_pkt(tcp, 0, __FUNCTION__, SRECV_TIMEOUT); + if (j < 0) { + warnx("packet %d is lost", i); + /* timeout */ + continue; + } + } +} + +static void +test_client(struct test_ctx *tcp) +{ + int i, j; + + for (i = 0; i < NPKTS; i++) { + j = recv_pkt(tcp, 1, __FUNCTION__, RRECV_TIMEOUT); + if (j < 0) { + /* timeout */ + return; + } +#if defined(SIMULATE_PLOSS) + if ((i % 99) == 0) { + warnx("dropping packet %d", i); + continue; + } +#endif + send_pkt(tcp, j, 1, __FUNCTION__); + } +} + +static void +calc_rtt(struct test_pkt *tpp, struct rtt *rttp) +{ + + timespecsub2(&rttp->a2b, &tpp->tss[1].recvd, &tpp->tss[0].sent); + timespecsub2(&rttp->b2a, &tpp->tss[0].recvd, &tpp->tss[1].sent); + timespecadd2(&rttp->a2b_b2a, &rttp->a2b, &rttp->b2a); + timespecsub2(&rttp->e2e, &tpp->tss[0].recvd, &tpp->tss[0].sent); +} + +static void +test_run(int ts_type, int use_ipv6, int use_recvmsg, const char *name) +{ + struct test_ctx test_ctx; + pid_t pid, cpid; + int i, j, status; + + printf("Testing %s via %s: ", name, (use_ipv6 == 0) ? "IPv4" : "IPv6"); + fflush(stdout); + bzero(&test_ctx, sizeof(test_ctx)); + test_ctx.name = name; + test_ctx.use_recvmsg = use_recvmsg; + test_ctx.ts_type = ts_type; + if (use_ipv6 == 0) { + setup_udp(&test_ctx); + } else { + setup_udp6(&test_ctx); + } + for (i = 0; i < NPKTS; i++) { + test_ctx.test_pkts[i].pnum = i; + test_ctx.test_pkts[i].lost = 1; + for (j = 0; j < PKT_SIZE; j++) { + test_ctx.test_pkts[i].data[j] = (unsigned char)random(); + } + } + cpid = fork(); + if (cpid < 0) { + err(1, "%s: fork()", test_ctx.name); + } + if (cpid == 0) { + test_client(&test_ctx); + exit(0); + } + test_server(&test_ctx); + pid = waitpid(cpid, &status, 0); + if (pid == (pid_t)-1) { + err(1, "%s: waitpid(%d)", test_ctx.name, cpid); + } + + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != EXIT_SUCCESS) { + errx(1, "client exit status is %d", + WEXITSTATUS(status)); + } + } else { + if (WIFSIGNALED(status)) + errx(1, "abnormal termination of client, signal %d%s", + WTERMSIG(status), WCOREDUMP(status) ? + " (core file generated)" : ""); + else + errx(1, "termination of client, unknown status"); + } + if (test_ctx.nrecvd < MIN_NRECV) { + errx(1, "packet loss is too high %d received out of %d, min %d", + test_ctx.nrecvd, test_ctx.nsent, MIN_NRECV); + } + for (i = 0; i < NPKTS; i++) { + struct rtt rtt; + if (test_ctx.test_pkts[i].lost != 0) { + continue; + } + calc_rtt(&test_ctx.test_pkts[i], &rtt); + if (!timespeccmp(&rtt.e2e, >, &rtt.a2b_b2a)) + errx(1, "end-to-end trip time is too small"); + if (!timespeccmp(&rtt.e2e, <, &max_ts)) + errx(1, "end-to-end trip time is too large"); + if (!timespeccmp(&rtt.a2b, >, &zero_ts)) + errx(1, "A2B trip time is not positive"); + if (!timespeccmp(&rtt.b2a, >, &zero_ts)) + errx(1, "B2A trip time is not positive"); + } + teardown_udp(&test_ctx); +} + +int +main(void) +{ + int i; + + srandomdev(); + + for (i = 0; i < 2; i++) { + test_run(0, i, 0, "send()/recv()"); + printf("OK\n"); + test_run(TT_TIMESTAMP, i, 1, + "send()/recvmsg(), setsockopt(SO_TIMESTAMP, 1)"); + printf("OK\n"); + if (i == 0) { + test_run(TT_BINTIME, i, 1, + "send()/recvmsg(), setsockopt(SO_BINTIME, 1)"); + printf("OK\n"); + } + test_run(TT_REALTIME_MICRO, i, 1, + "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_REALTIME_MICRO)"); + printf("OK\n"); + test_run(TT_TS_BINTIME, i, 1, + "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_BINTIME)"); + printf("OK\n"); + test_run(TT_REALTIME, i, 1, + "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_REALTIME)"); + printf("OK\n"); + test_run(TT_MONOTONIC, i, 1, + "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_MONOTONIC)"); + printf("OK\n"); + } + exit(0); +} Index: head/tools/regression/sockets/unix_cmsg/Makefile =================================================================== --- head/tools/regression/sockets/unix_cmsg/Makefile +++ head/tools/regression/sockets/unix_cmsg/Makefile @@ -16,8 +16,14 @@ REXP_timeval= 's|%%TTYPE%%|timeval|g ; s|%%DTYPE%%|timeval|g ; \ s|%%SCM_TTYPE%%|SCM_TIMESTAMP|g ; \ s|%%MAJ_MEMB%%|tv_sec|g ; s|%%MIN_MEMB%%|tv_usec|g' +REXP_timespec_real= 's|%%TTYPE%%|timespec_real|g ; s|%%DTYPE%%|timespec|g ; \ + s|%%SCM_TTYPE%%|SCM_REALTIME|g ; \ + s|%%MAJ_MEMB%%|tv_sec|g ; s|%%MIN_MEMB%%|tv_nsec|g' +REXP_timespec_mono= 's|%%TTYPE%%|timespec_mono|g ; s|%%DTYPE%%|timespec|g ; \ + s|%%SCM_TTYPE%%|SCM_MONOTONIC|g ; \ + s|%%MAJ_MEMB%%|tv_sec|g ; s|%%MIN_MEMB%%|tv_nsec|g' -.for ttype in bintime timeval +.for ttype in bintime timeval timespec_real timespec_mono AUTOSRCS+= t_${ttype}.h t_${ttype}.c t_${ttype}.o: t_${ttype}.c t_${ttype}.h Index: head/tools/regression/sockets/unix_cmsg/unix_cmsg.c =================================================================== --- head/tools/regression/sockets/unix_cmsg/unix_cmsg.c +++ head/tools/regression/sockets/unix_cmsg/unix_cmsg.c @@ -52,6 +52,8 @@ #include "t_sockcred.h" #include "t_cmsgcred_sockcred.h" #include "t_cmsg_len.h" +#include "t_timespec_real.h" +#include "t_timespec_mono.h" /* * There are tables with tests descriptions and pointers to test @@ -117,7 +119,19 @@ { .func = t_peercred, .desc = "Check LOCAL_PEERCRED socket option" + }, +#if defined(SCM_REALTIME) + { + .func = t_timespec_real, + .desc = "Sending, receiving realtime" + }, +#endif +#if defined(SCM_MONOTONIC) + { + .func = t_timespec_mono, + .desc = "Sending, receiving monotonic time (uptime)" } +#endif }; #define TEST_STREAM_TBL_SIZE \ @@ -152,6 +166,18 @@ { .func = t_cmsg_len, .desc = "Check cmsghdr.cmsg_len" + }, +#endif +#if defined(SCM_REALTIME) + { + .func = t_timespec_real, + .desc = "Sending, receiving realtime" + }, +#endif +#if defined(SCM_MONOTONIC) + { + .func = t_timespec_mono, + .desc = "Sending, receiving monotonic time (uptime)" } #endif };