sources to return timestamps when SO_TIMESTAMP is enabled. Two additional clock sources are: o nanosecond resolution realtime clock (equivalent of CLOCK_REALTIME); o nanosecond resolution monotonic clock (equivalent of CLOCK_MONOTONIC). In addition to this, this option provides unified interface to get bintime (equivalent of using SO_BINTIME), except it also supported with IPv6 where SO_BINTIME has never been supported. The long term plan is to depreciate SO_BINTIME and move everything to using SO_TS_CLOCK. Idea for this enhancement has been briefly discussed on the Net session during dev summit in Ottawa last June and the general input was positive. This change is believed to benefit network benchmarks/profiling as well as other scenarios where precise time of arrival measurement is necessary. There are two regression test cases as part of this commit: one extends unix domain test code (unix_cmsg) to test new SCM_XXX types and another one implementis totally new test case which exchanges UDP packets between two processes using both conventional methods (i.e. calling clock_gettime(2) before recv(2) and after send(2)), as well as using setsockopt()+recv() in receive path. The resulting delays are checked for sanity for all supported clock types. Reviewed by: adrian, gnn Differential Revision: https://reviews.freebsd.org/D9171
453 lines
11 KiB
C
453 lines
11 KiB
C
/*-
|
|
* Copyright (c) 2005 Andrey Simonenko
|
|
* 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.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/limits.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
|
|
#include <err.h>
|
|
#include <inttypes.h>
|
|
#include <paths.h>
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "uc_common.h"
|
|
#include "t_cmsgcred.h"
|
|
#include "t_bintime.h"
|
|
#include "t_generic.h"
|
|
#include "t_peercred.h"
|
|
#include "t_timeval.h"
|
|
#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
|
|
* functions. Each t_*() function returns 0 if its test passed,
|
|
* -1 if its test failed, -2 if some system error occurred.
|
|
* If a test function returns -2, then a program exits.
|
|
*
|
|
* If a test function forks a client process, then it waits for its
|
|
* termination. If a return code of a client process is not equal
|
|
* to zero, or if a client process was terminated by a signal, then
|
|
* a test function returns -1 or -2 depending on exit status of
|
|
* a client process.
|
|
*
|
|
* Each function which can block, is run under TIMEOUT. If timeout
|
|
* occurs, then a test function returns -2 or a client process exits
|
|
* with a non-zero return code.
|
|
*/
|
|
|
|
struct test_func {
|
|
int (*func)(void);
|
|
const char *desc;
|
|
};
|
|
|
|
static const struct test_func test_stream_tbl[] = {
|
|
{
|
|
.func = NULL,
|
|
.desc = "All tests"
|
|
},
|
|
{
|
|
.func = t_cmsgcred,
|
|
.desc = "Sending, receiving cmsgcred"
|
|
},
|
|
{
|
|
.func = t_sockcred_1,
|
|
.desc = "Receiving sockcred (listening socket)"
|
|
},
|
|
{
|
|
.func = t_sockcred_2,
|
|
.desc = "Receiving sockcred (accepted socket)"
|
|
},
|
|
{
|
|
.func = t_cmsgcred_sockcred,
|
|
.desc = "Sending cmsgcred, receiving sockcred"
|
|
},
|
|
{
|
|
.func = t_timeval,
|
|
.desc = "Sending, receiving timeval"
|
|
},
|
|
{
|
|
.func = t_bintime,
|
|
.desc = "Sending, receiving bintime"
|
|
},
|
|
/*
|
|
* The testcase fails on 64-bit architectures (amd64), but passes on 32-bit
|
|
* architectures (i386); see bug 206543
|
|
*/
|
|
#ifndef __LP64__
|
|
{
|
|
.func = t_cmsg_len,
|
|
.desc = "Check cmsghdr.cmsg_len"
|
|
},
|
|
#endif
|
|
{
|
|
.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 \
|
|
(sizeof(test_stream_tbl) / sizeof(test_stream_tbl[0]))
|
|
|
|
static const struct test_func test_dgram_tbl[] = {
|
|
{
|
|
.func = NULL,
|
|
.desc = "All tests"
|
|
},
|
|
{
|
|
.func = t_cmsgcred,
|
|
.desc = "Sending, receiving cmsgcred"
|
|
},
|
|
{
|
|
.func = t_sockcred_2,
|
|
.desc = "Receiving sockcred"
|
|
},
|
|
{
|
|
.func = t_cmsgcred_sockcred,
|
|
.desc = "Sending cmsgcred, receiving sockcred"
|
|
},
|
|
{
|
|
.func = t_timeval,
|
|
.desc = "Sending, receiving timeval"
|
|
},
|
|
{
|
|
.func = t_bintime,
|
|
.desc = "Sending, receiving bintime"
|
|
},
|
|
#ifndef __LP64__
|
|
{
|
|
.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
|
|
};
|
|
|
|
#define TEST_DGRAM_TBL_SIZE \
|
|
(sizeof(test_dgram_tbl) / sizeof(test_dgram_tbl[0]))
|
|
|
|
static bool failed_flag = false;
|
|
|
|
struct uc_cfg uc_cfg;
|
|
|
|
static char work_dir[] = _PATH_TMP "unix_cmsg.XXXXXXX";
|
|
|
|
#define IPC_MSG_NUM_DEF 5
|
|
#define IPC_MSG_NUM_MAX 10
|
|
#define IPC_MSG_SIZE_DEF 7
|
|
#define IPC_MSG_SIZE_MAX 128
|
|
|
|
static void
|
|
usage(bool verbose)
|
|
{
|
|
u_int i;
|
|
|
|
printf("usage: %s [-dh] [-n num] [-s size] [-t type] "
|
|
"[-z value] [testno]\n", getprogname());
|
|
if (!verbose)
|
|
return;
|
|
printf("\n Options are:\n\
|
|
-d Output debugging information\n\
|
|
-h Output the help message and exit\n\
|
|
-n num Number of messages to send\n\
|
|
-s size Specify size of data for IPC\n\
|
|
-t type Specify socket type (stream, dgram) for tests\n\
|
|
-z value Do not send data in a message (bit 0x1), do not send\n\
|
|
data array associated with a cmsghdr structure (bit 0x2)\n\
|
|
testno Run one test by its number (require the -t option)\n\n");
|
|
printf(" Available tests for stream sockets:\n");
|
|
for (i = 0; i < TEST_STREAM_TBL_SIZE; ++i)
|
|
printf(" %u: %s\n", i, test_stream_tbl[i].desc);
|
|
printf("\n Available tests for datagram sockets:\n");
|
|
for (i = 0; i < TEST_DGRAM_TBL_SIZE; ++i)
|
|
printf(" %u: %s\n", i, test_dgram_tbl[i].desc);
|
|
}
|
|
|
|
static int
|
|
run_tests(int type, u_int testno1)
|
|
{
|
|
const struct test_func *tf;
|
|
u_int i, testno2, failed_num;
|
|
|
|
uc_cfg.sock_type = type;
|
|
if (type == SOCK_STREAM) {
|
|
uc_cfg.sock_type_str = "SOCK_STREAM";
|
|
tf = test_stream_tbl;
|
|
i = TEST_STREAM_TBL_SIZE - 1;
|
|
} else {
|
|
uc_cfg.sock_type_str = "SOCK_DGRAM";
|
|
tf = test_dgram_tbl;
|
|
i = TEST_DGRAM_TBL_SIZE - 1;
|
|
}
|
|
if (testno1 == 0) {
|
|
testno1 = 1;
|
|
testno2 = i;
|
|
} else
|
|
testno2 = testno1;
|
|
|
|
uc_output("Running tests for %s sockets:\n", uc_cfg.sock_type_str);
|
|
failed_num = 0;
|
|
for (i = testno1, tf += testno1; i <= testno2; ++tf, ++i) {
|
|
uc_output(" %u: %s\n", i, tf->desc);
|
|
switch (tf->func()) {
|
|
case -1:
|
|
++failed_num;
|
|
break;
|
|
case -2:
|
|
uc_logmsgx("some system error or timeout occurred");
|
|
return (-1);
|
|
}
|
|
}
|
|
|
|
if (failed_num != 0)
|
|
failed_flag = true;
|
|
|
|
if (testno1 != testno2) {
|
|
if (failed_num == 0)
|
|
uc_output("-- all tests passed!\n");
|
|
else
|
|
uc_output("-- %u test%s failed!\n",
|
|
failed_num, failed_num == 1 ? "" : "s");
|
|
} else {
|
|
if (failed_num == 0)
|
|
uc_output("-- test passed!\n");
|
|
else
|
|
uc_output("-- test failed!\n");
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
init(void)
|
|
{
|
|
struct sigaction sigact;
|
|
size_t idx;
|
|
int rv;
|
|
|
|
uc_cfg.proc_name = "SERVER";
|
|
|
|
sigact.sa_handler = SIG_IGN;
|
|
sigact.sa_flags = 0;
|
|
sigemptyset(&sigact.sa_mask);
|
|
if (sigaction(SIGPIPE, &sigact, (struct sigaction *)NULL) < 0) {
|
|
uc_logmsg("init: sigaction");
|
|
return (-1);
|
|
}
|
|
|
|
if (uc_cfg.ipc_msg.buf_size == 0)
|
|
uc_cfg.ipc_msg.buf_send = uc_cfg.ipc_msg.buf_recv = NULL;
|
|
else {
|
|
uc_cfg.ipc_msg.buf_send = malloc(uc_cfg.ipc_msg.buf_size);
|
|
uc_cfg.ipc_msg.buf_recv = malloc(uc_cfg.ipc_msg.buf_size);
|
|
if (uc_cfg.ipc_msg.buf_send == NULL || uc_cfg.ipc_msg.buf_recv == NULL) {
|
|
uc_logmsg("init: malloc");
|
|
return (-1);
|
|
}
|
|
for (idx = 0; idx < uc_cfg.ipc_msg.buf_size; ++idx)
|
|
uc_cfg.ipc_msg.buf_send[idx] = (char)idx;
|
|
}
|
|
|
|
uc_cfg.proc_cred.uid = getuid();
|
|
uc_cfg.proc_cred.euid = geteuid();
|
|
uc_cfg.proc_cred.gid = getgid();
|
|
uc_cfg.proc_cred.egid = getegid();
|
|
uc_cfg.proc_cred.gid_num = getgroups(0, (gid_t *)NULL);
|
|
if (uc_cfg.proc_cred.gid_num < 0) {
|
|
uc_logmsg("init: getgroups");
|
|
return (-1);
|
|
}
|
|
uc_cfg.proc_cred.gid_arr = malloc(uc_cfg.proc_cred.gid_num *
|
|
sizeof(*uc_cfg.proc_cred.gid_arr));
|
|
if (uc_cfg.proc_cred.gid_arr == NULL) {
|
|
uc_logmsg("init: malloc");
|
|
return (-1);
|
|
}
|
|
if (getgroups(uc_cfg.proc_cred.gid_num, uc_cfg.proc_cred.gid_arr) < 0) {
|
|
uc_logmsg("init: getgroups");
|
|
return (-1);
|
|
}
|
|
|
|
memset(&uc_cfg.serv_addr_sun, 0, sizeof(uc_cfg.serv_addr_sun));
|
|
rv = snprintf(uc_cfg.serv_addr_sun.sun_path, sizeof(uc_cfg.serv_addr_sun.sun_path),
|
|
"%s/%s", work_dir, uc_cfg.proc_name);
|
|
if (rv < 0) {
|
|
uc_logmsg("init: snprintf");
|
|
return (-1);
|
|
}
|
|
if ((size_t)rv >= sizeof(uc_cfg.serv_addr_sun.sun_path)) {
|
|
uc_logmsgx("init: not enough space for socket pathname");
|
|
return (-1);
|
|
}
|
|
uc_cfg.serv_addr_sun.sun_family = PF_LOCAL;
|
|
uc_cfg.serv_addr_sun.sun_len = SUN_LEN(&uc_cfg.serv_addr_sun);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
const char *errstr;
|
|
u_int testno, zvalue;
|
|
int opt, rv;
|
|
bool dgram_flag, stream_flag;
|
|
|
|
memset(&uc_cfg, '\0', sizeof(uc_cfg));
|
|
uc_cfg.debug = false;
|
|
uc_cfg.server_flag = true;
|
|
uc_cfg.send_data_flag = true;
|
|
uc_cfg.send_array_flag = true;
|
|
uc_cfg.ipc_msg.buf_size = IPC_MSG_SIZE_DEF;
|
|
uc_cfg.ipc_msg.msg_num = IPC_MSG_NUM_DEF;
|
|
dgram_flag = stream_flag = false;
|
|
while ((opt = getopt(argc, argv, "dhn:s:t:z:")) != -1)
|
|
switch (opt) {
|
|
case 'd':
|
|
uc_cfg.debug = true;
|
|
break;
|
|
case 'h':
|
|
usage(true);
|
|
return (EXIT_SUCCESS);
|
|
case 'n':
|
|
uc_cfg.ipc_msg.msg_num = strtonum(optarg, 1,
|
|
IPC_MSG_NUM_MAX, &errstr);
|
|
if (errstr != NULL)
|
|
errx(EXIT_FAILURE, "option -n: number is %s",
|
|
errstr);
|
|
break;
|
|
case 's':
|
|
uc_cfg.ipc_msg.buf_size = strtonum(optarg, 0,
|
|
IPC_MSG_SIZE_MAX, &errstr);
|
|
if (errstr != NULL)
|
|
errx(EXIT_FAILURE, "option -s: number is %s",
|
|
errstr);
|
|
break;
|
|
case 't':
|
|
if (strcmp(optarg, "stream") == 0)
|
|
stream_flag = true;
|
|
else if (strcmp(optarg, "dgram") == 0)
|
|
dgram_flag = true;
|
|
else
|
|
errx(EXIT_FAILURE, "option -t: "
|
|
"wrong socket type");
|
|
break;
|
|
case 'z':
|
|
zvalue = strtonum(optarg, 0, 3, &errstr);
|
|
if (errstr != NULL)
|
|
errx(EXIT_FAILURE, "option -z: number is %s",
|
|
errstr);
|
|
if (zvalue & 0x1)
|
|
uc_cfg.send_data_flag = false;
|
|
if (zvalue & 0x2)
|
|
uc_cfg.send_array_flag = false;
|
|
break;
|
|
default:
|
|
usage(false);
|
|
return (EXIT_FAILURE);
|
|
}
|
|
|
|
if (optind < argc) {
|
|
if (optind + 1 != argc)
|
|
errx(EXIT_FAILURE, "too many arguments");
|
|
testno = strtonum(argv[optind], 0, UINT_MAX, &errstr);
|
|
if (errstr != NULL)
|
|
errx(EXIT_FAILURE, "test number is %s", errstr);
|
|
if (stream_flag && testno >= TEST_STREAM_TBL_SIZE)
|
|
errx(EXIT_FAILURE, "given test %u for stream "
|
|
"sockets does not exist", testno);
|
|
if (dgram_flag && testno >= TEST_DGRAM_TBL_SIZE)
|
|
errx(EXIT_FAILURE, "given test %u for datagram "
|
|
"sockets does not exist", testno);
|
|
} else
|
|
testno = 0;
|
|
|
|
if (!dgram_flag && !stream_flag) {
|
|
if (testno != 0)
|
|
errx(EXIT_FAILURE, "particular test number "
|
|
"can be used with the -t option only");
|
|
dgram_flag = stream_flag = true;
|
|
}
|
|
|
|
if (mkdtemp(work_dir) == NULL)
|
|
err(EXIT_FAILURE, "mkdtemp(%s)", work_dir);
|
|
|
|
rv = EXIT_FAILURE;
|
|
if (init() < 0)
|
|
goto done;
|
|
|
|
if (stream_flag)
|
|
if (run_tests(SOCK_STREAM, testno) < 0)
|
|
goto done;
|
|
if (dgram_flag)
|
|
if (run_tests(SOCK_DGRAM, testno) < 0)
|
|
goto done;
|
|
|
|
rv = EXIT_SUCCESS;
|
|
done:
|
|
if (rmdir(work_dir) < 0) {
|
|
uc_logmsg("rmdir(%s)", work_dir);
|
|
rv = EXIT_FAILURE;
|
|
}
|
|
return (failed_flag ? EXIT_FAILURE : rv);
|
|
}
|