socket: add support for systemd sockets
Before opening new IPv4/IPv6 server sockets, chronyd will check for matching reusable sockets passed from the service manager (for example, passed via systemd socket activation: https://www.freedesktop.org/software/systemd/man/latest/sd_listen_fds.html) and use those instead. Aside from IPV6_V6ONLY (which cannot be set on already-bound sockets), the daemon sets the same socket options on reusable sockets as it would on sockets it opens itself. Unit tests test the correct parsing of the LISTEN_FDS environment variable. Add 011-systemd system test to test socket activation for DGRAM and STREAM sockets (both IPv4 and IPv6). The tests use the systemd-socket-activate test tool, which has some limitations requiring workarounds discussed in inline comments.
This commit is contained in:
parent
c063b9e78a
commit
e6a0476eb7
7 changed files with 372 additions and 10 deletions
|
@ -206,6 +206,17 @@ With this option *chronyd* will print version number to the terminal and exit.
|
||||||
*-h*, *--help*::
|
*-h*, *--help*::
|
||||||
With this option *chronyd* will print a help message to the terminal and exit.
|
With this option *chronyd* will print a help message to the terminal and exit.
|
||||||
|
|
||||||
|
== ENVIRONMENT VARIABLES
|
||||||
|
|
||||||
|
*LISTEN_FDS*::
|
||||||
|
On Linux systems, the systemd service manager may pass file descriptors for
|
||||||
|
pre-initialised sockets to *chronyd*. The service manager allocates and binds
|
||||||
|
the file descriptors, and passes a copy to each spawned instance of the
|
||||||
|
service. This allows for zero-downtime service restarts as the sockets buffer
|
||||||
|
client requests until the service is able to handle them. The service manager
|
||||||
|
sets the LISTEN_FDS environment variable to the number of passed file
|
||||||
|
descriptors.
|
||||||
|
|
||||||
== FILES
|
== FILES
|
||||||
|
|
||||||
_@SYSCONFDIR@/chrony.conf_
|
_@SYSCONFDIR@/chrony.conf_
|
||||||
|
|
7
main.c
7
main.c
|
@ -368,9 +368,9 @@ go_daemon(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Don't keep stdin/out/err from before. But don't close
|
/* Don't keep stdin/out/err from before. But don't close
|
||||||
the parent pipe yet. */
|
the parent pipe yet, or reusable file descriptors. */
|
||||||
for (fd=0; fd<1024; fd++) {
|
for (fd=0; fd<1024; fd++) {
|
||||||
if (fd != pipefd[1])
|
if (fd != pipefd[1] && !SCK_IsReusable(fd))
|
||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -560,6 +560,9 @@ int main
|
||||||
if (user_check && getuid() != 0)
|
if (user_check && getuid() != 0)
|
||||||
LOG_FATAL("Not superuser");
|
LOG_FATAL("Not superuser");
|
||||||
|
|
||||||
|
/* Initialise reusable file descriptors before fork */
|
||||||
|
SCK_PreInitialise();
|
||||||
|
|
||||||
/* Turn into a daemon */
|
/* Turn into a daemon */
|
||||||
if (!nofork) {
|
if (!nofork) {
|
||||||
go_daemon();
|
go_daemon();
|
||||||
|
|
155
socket.c
155
socket.c
|
@ -89,6 +89,9 @@ struct MessageHeader {
|
||||||
|
|
||||||
static int initialised;
|
static int initialised;
|
||||||
|
|
||||||
|
static int first_reusable_fd;
|
||||||
|
static int reusable_fds;
|
||||||
|
|
||||||
/* Flags indicating in which IP families sockets can be requested */
|
/* Flags indicating in which IP families sockets can be requested */
|
||||||
static int ip4_enabled;
|
static int ip4_enabled;
|
||||||
static int ip6_enabled;
|
static int ip6_enabled;
|
||||||
|
@ -155,6 +158,59 @@ domain_to_string(int domain)
|
||||||
|
|
||||||
/* ================================================== */
|
/* ================================================== */
|
||||||
|
|
||||||
|
static int
|
||||||
|
get_reusable_socket(int type, IPSockAddr *spec)
|
||||||
|
{
|
||||||
|
#ifdef LINUX
|
||||||
|
union sockaddr_all sa;
|
||||||
|
IPSockAddr ip_sa;
|
||||||
|
int sock_fd, opt;
|
||||||
|
socklen_t l;
|
||||||
|
|
||||||
|
/* Abort early if not an IPv4/IPv6 server socket */
|
||||||
|
if (!spec || spec->ip_addr.family == IPADDR_UNSPEC || spec->port == 0)
|
||||||
|
return INVALID_SOCK_FD;
|
||||||
|
|
||||||
|
/* Loop over available reusable sockets */
|
||||||
|
for (sock_fd = first_reusable_fd; sock_fd < first_reusable_fd + reusable_fds; sock_fd++) {
|
||||||
|
|
||||||
|
/* Check that types match */
|
||||||
|
l = sizeof (opt);
|
||||||
|
if (getsockopt(sock_fd, SOL_SOCKET, SO_TYPE, &opt, &l) < 0 ||
|
||||||
|
l != sizeof (opt) || opt != type)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Get sockaddr for reusable socket */
|
||||||
|
l = sizeof (sa);
|
||||||
|
if (getsockname(sock_fd, &sa.sa, &l) < 0 || l < sizeof (sa_family_t))
|
||||||
|
continue;
|
||||||
|
SCK_SockaddrToIPSockAddr(&sa.sa, l, &ip_sa);
|
||||||
|
|
||||||
|
/* Check that reusable socket matches specification */
|
||||||
|
if (ip_sa.port != spec->port || UTI_CompareIPs(&ip_sa.ip_addr, &spec->ip_addr, NULL) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Check that STREAM socket is listening */
|
||||||
|
l = sizeof (opt);
|
||||||
|
if (type == SOCK_STREAM && (getsockopt(sock_fd, SOL_SOCKET, SO_ACCEPTCONN, &opt, &l) < 0 ||
|
||||||
|
l != sizeof (opt) || opt == 0))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
#if defined(FEAT_IPV6) && defined(IPV6_V6ONLY)
|
||||||
|
if (spec->ip_addr.family == IPADDR_INET6 &&
|
||||||
|
(!SCK_GetIntOption(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt) || opt != 1))
|
||||||
|
LOG(LOGS_WARN, "Reusable IPv6 socket missing IPV6_V6ONLY option");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return sock_fd;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return INVALID_SOCK_FD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================== */
|
||||||
|
|
||||||
#if defined(SOCK_CLOEXEC) || defined(SOCK_NONBLOCK)
|
#if defined(SOCK_CLOEXEC) || defined(SOCK_NONBLOCK)
|
||||||
static int
|
static int
|
||||||
check_socket_flag(int sock_flag, int fd_flag, int fs_flag)
|
check_socket_flag(int sock_flag, int fd_flag, int fs_flag)
|
||||||
|
@ -214,7 +270,7 @@ set_socket_flags(int sock_fd, int flags)
|
||||||
/* Close the socket automatically on exec */
|
/* Close the socket automatically on exec */
|
||||||
if (
|
if (
|
||||||
#ifdef SOCK_CLOEXEC
|
#ifdef SOCK_CLOEXEC
|
||||||
(supported_socket_flags & SOCK_CLOEXEC) == 0 &&
|
(SCK_IsReusable(sock_fd) || (supported_socket_flags & SOCK_CLOEXEC) == 0) &&
|
||||||
#endif
|
#endif
|
||||||
!UTI_FdSetCloexec(sock_fd))
|
!UTI_FdSetCloexec(sock_fd))
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -222,7 +278,7 @@ set_socket_flags(int sock_fd, int flags)
|
||||||
/* Enable non-blocking mode */
|
/* Enable non-blocking mode */
|
||||||
if ((flags & SCK_FLAG_BLOCK) == 0 &&
|
if ((flags & SCK_FLAG_BLOCK) == 0 &&
|
||||||
#ifdef SOCK_NONBLOCK
|
#ifdef SOCK_NONBLOCK
|
||||||
(supported_socket_flags & SOCK_NONBLOCK) == 0 &&
|
(SCK_IsReusable(sock_fd) || (supported_socket_flags & SOCK_NONBLOCK) == 0) &&
|
||||||
#endif
|
#endif
|
||||||
!set_socket_nonblock(sock_fd))
|
!set_socket_nonblock(sock_fd))
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -279,6 +335,32 @@ open_socket_pair(int domain, int type, int flags, int *other_fd)
|
||||||
|
|
||||||
/* ================================================== */
|
/* ================================================== */
|
||||||
|
|
||||||
|
static int
|
||||||
|
get_ip_socket(int domain, int type, int flags, IPSockAddr *ip_sa)
|
||||||
|
{
|
||||||
|
int sock_fd;
|
||||||
|
|
||||||
|
/* Check if there is a matching reusable socket */
|
||||||
|
sock_fd = get_reusable_socket(type, ip_sa);
|
||||||
|
|
||||||
|
if (sock_fd < 0) {
|
||||||
|
sock_fd = open_socket(domain, type, flags);
|
||||||
|
|
||||||
|
/* Unexpected, but make sure the new socket is not in the reusable range */
|
||||||
|
if (SCK_IsReusable(sock_fd))
|
||||||
|
LOG_FATAL("Could not open %s socket : file descriptor in reusable range",
|
||||||
|
domain_to_string(domain));
|
||||||
|
} else {
|
||||||
|
/* Set socket flags on reusable socket */
|
||||||
|
if (!set_socket_flags(sock_fd, flags))
|
||||||
|
return INVALID_SOCK_FD;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sock_fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================== */
|
||||||
|
|
||||||
static int
|
static int
|
||||||
set_socket_options(int sock_fd, int flags)
|
set_socket_options(int sock_fd, int flags)
|
||||||
{
|
{
|
||||||
|
@ -295,8 +377,10 @@ static int
|
||||||
set_ip_options(int sock_fd, int family, int flags)
|
set_ip_options(int sock_fd, int family, int flags)
|
||||||
{
|
{
|
||||||
#if defined(FEAT_IPV6) && defined(IPV6_V6ONLY)
|
#if defined(FEAT_IPV6) && defined(IPV6_V6ONLY)
|
||||||
/* Receive only IPv6 packets on an IPv6 socket */
|
/* Receive only IPv6 packets on an IPv6 socket, but do not attempt
|
||||||
if (family == IPADDR_INET6 && !SCK_SetIntOption(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, 1))
|
to set this option on pre-initialised reuseable sockets */
|
||||||
|
if (family == IPADDR_INET6 && !SCK_IsReusable(sock_fd) &&
|
||||||
|
!SCK_SetIntOption(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, 1))
|
||||||
return 0;
|
return 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -385,6 +469,10 @@ bind_ip_address(int sock_fd, IPSockAddr *addr, int flags)
|
||||||
;
|
;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Do not attempt to bind pre-initialised reusable socket */
|
||||||
|
if (SCK_IsReusable(sock_fd))
|
||||||
|
return 1;
|
||||||
|
|
||||||
saddr_len = SCK_IPSockAddrToSockaddr(addr, (struct sockaddr *)&saddr, sizeof (saddr));
|
saddr_len = SCK_IPSockAddrToSockaddr(addr, (struct sockaddr *)&saddr, sizeof (saddr));
|
||||||
if (saddr_len == 0)
|
if (saddr_len == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -457,7 +545,7 @@ open_ip_socket(IPSockAddr *remote_addr, IPSockAddr *local_addr, const char *ifac
|
||||||
return INVALID_SOCK_FD;
|
return INVALID_SOCK_FD;
|
||||||
}
|
}
|
||||||
|
|
||||||
sock_fd = open_socket(domain, type, flags);
|
sock_fd = get_ip_socket(domain, type, flags, local_addr);
|
||||||
if (sock_fd < 0)
|
if (sock_fd < 0)
|
||||||
return INVALID_SOCK_FD;
|
return INVALID_SOCK_FD;
|
||||||
|
|
||||||
|
@ -482,7 +570,8 @@ open_ip_socket(IPSockAddr *remote_addr, IPSockAddr *local_addr, const char *ifac
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
if (remote_addr || local_addr)
|
if (remote_addr || local_addr)
|
||||||
DEBUG_LOG("Opened %s%s socket fd=%d%s%s%s%s",
|
DEBUG_LOG("%s %s%s socket fd=%d%s%s%s%s",
|
||||||
|
SCK_IsReusable(sock_fd) ? "Reusing" : "Opened",
|
||||||
type == SOCK_DGRAM ? "UDP" : type == SOCK_STREAM ? "TCP" : "?",
|
type == SOCK_DGRAM ? "UDP" : type == SOCK_STREAM ? "TCP" : "?",
|
||||||
family == IPADDR_INET4 ? "v4" : "v6",
|
family == IPADDR_INET4 ? "v4" : "v6",
|
||||||
sock_fd,
|
sock_fd,
|
||||||
|
@ -1170,6 +1259,39 @@ send_message(int sock_fd, SCK_Message *message, int flags)
|
||||||
|
|
||||||
/* ================================================== */
|
/* ================================================== */
|
||||||
|
|
||||||
|
void
|
||||||
|
SCK_PreInitialise(void)
|
||||||
|
{
|
||||||
|
#ifdef LINUX
|
||||||
|
char *s, *ptr;
|
||||||
|
|
||||||
|
/* On Linux systems, the systemd service manager may pass file descriptors
|
||||||
|
for pre-initialised sockets to the chronyd daemon. The service manager
|
||||||
|
allocates and binds the file descriptors, and passes a copy to each
|
||||||
|
spawned instance of the service. This allows for zero-downtime service
|
||||||
|
restarts as the sockets buffer client requests until the service is able
|
||||||
|
to handle them. The service manager sets the LISTEN_FDS environment
|
||||||
|
variable to the number of passed file descriptors, and the integer file
|
||||||
|
descriptors start at 3 (see SD_LISTEN_FDS_START in
|
||||||
|
https://www.freedesktop.org/software/systemd/man/latest/sd_listen_fds.html). */
|
||||||
|
first_reusable_fd = 3;
|
||||||
|
reusable_fds = 0;
|
||||||
|
|
||||||
|
s = getenv("LISTEN_FDS");
|
||||||
|
if (s) {
|
||||||
|
errno = 0;
|
||||||
|
reusable_fds = strtol(s, &ptr, 10);
|
||||||
|
if (errno != 0 || *ptr != '\0' || reusable_fds < 0)
|
||||||
|
reusable_fds = 0;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
first_reusable_fd = 0;
|
||||||
|
reusable_fds = 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================== */
|
||||||
|
|
||||||
void
|
void
|
||||||
SCK_Initialise(int family)
|
SCK_Initialise(int family)
|
||||||
{
|
{
|
||||||
|
@ -1209,10 +1331,17 @@ SCK_Initialise(int family)
|
||||||
void
|
void
|
||||||
SCK_Finalise(void)
|
SCK_Finalise(void)
|
||||||
{
|
{
|
||||||
|
int fd;
|
||||||
|
|
||||||
ARR_DestroyInstance(recv_sck_messages);
|
ARR_DestroyInstance(recv_sck_messages);
|
||||||
ARR_DestroyInstance(recv_headers);
|
ARR_DestroyInstance(recv_headers);
|
||||||
ARR_DestroyInstance(recv_messages);
|
ARR_DestroyInstance(recv_messages);
|
||||||
|
|
||||||
|
for (fd = first_reusable_fd; fd < first_reusable_fd + reusable_fds; fd++)
|
||||||
|
close(fd);
|
||||||
|
reusable_fds = 0;
|
||||||
|
first_reusable_fd = 0;
|
||||||
|
|
||||||
initialised = 0;
|
initialised = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1353,6 +1482,14 @@ SCK_OpenUnixSocketPair(int flags, int *other_fd)
|
||||||
|
|
||||||
/* ================================================== */
|
/* ================================================== */
|
||||||
|
|
||||||
|
int
|
||||||
|
SCK_IsReusable(int fd)
|
||||||
|
{
|
||||||
|
return fd >= first_reusable_fd && fd < first_reusable_fd + reusable_fds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================== */
|
||||||
|
|
||||||
int
|
int
|
||||||
SCK_SetIntOption(int sock_fd, int level, int name, int value)
|
SCK_SetIntOption(int sock_fd, int level, int name, int value)
|
||||||
{
|
{
|
||||||
|
@ -1410,7 +1547,7 @@ SCK_EnableKernelRxTimestamping(int sock_fd)
|
||||||
int
|
int
|
||||||
SCK_ListenOnSocket(int sock_fd, int backlog)
|
SCK_ListenOnSocket(int sock_fd, int backlog)
|
||||||
{
|
{
|
||||||
if (listen(sock_fd, backlog) < 0) {
|
if (!SCK_IsReusable(sock_fd) && listen(sock_fd, backlog) < 0) {
|
||||||
DEBUG_LOG("listen() failed : %s", strerror(errno));
|
DEBUG_LOG("listen() failed : %s", strerror(errno));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1573,6 +1710,10 @@ SCK_RemoveSocket(int sock_fd)
|
||||||
void
|
void
|
||||||
SCK_CloseSocket(int sock_fd)
|
SCK_CloseSocket(int sock_fd)
|
||||||
{
|
{
|
||||||
|
/* Reusable sockets are closed in finalisation */
|
||||||
|
if (SCK_IsReusable(sock_fd))
|
||||||
|
return;
|
||||||
|
|
||||||
close(sock_fd);
|
close(sock_fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
6
socket.h
6
socket.h
|
@ -73,6 +73,9 @@ typedef struct {
|
||||||
int descriptor;
|
int descriptor;
|
||||||
} SCK_Message;
|
} SCK_Message;
|
||||||
|
|
||||||
|
/* Pre-initialisation function */
|
||||||
|
extern void SCK_PreInitialise(void);
|
||||||
|
|
||||||
/* Initialisation function (the specified IP family is enabled,
|
/* Initialisation function (the specified IP family is enabled,
|
||||||
or all if IPADDR_UNSPEC) */
|
or all if IPADDR_UNSPEC) */
|
||||||
extern void SCK_Initialise(int family);
|
extern void SCK_Initialise(int family);
|
||||||
|
@ -106,6 +109,9 @@ extern int SCK_OpenUnixStreamSocket(const char *remote_addr, const char *local_a
|
||||||
int flags);
|
int flags);
|
||||||
extern int SCK_OpenUnixSocketPair(int flags, int *other_fd);
|
extern int SCK_OpenUnixSocketPair(int flags, int *other_fd);
|
||||||
|
|
||||||
|
/* Check if a file descriptor was passed from the service manager */
|
||||||
|
extern int SCK_IsReusable(int sock_fd);
|
||||||
|
|
||||||
/* Set and get a socket option of int size */
|
/* Set and get a socket option of int size */
|
||||||
extern int SCK_SetIntOption(int sock_fd, int level, int name, int value);
|
extern int SCK_SetIntOption(int sock_fd, int level, int name, int value);
|
||||||
extern int SCK_GetIntOption(int sock_fd, int level, int name, int *value);
|
extern int SCK_GetIntOption(int sock_fd, int level, int name, int *value);
|
||||||
|
|
140
test/system/011-systemd
Executable file
140
test/system/011-systemd
Executable file
|
@ -0,0 +1,140 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
. ./test.common
|
||||||
|
|
||||||
|
check_chronyd_features NTS || test_skip "NTS support disabled"
|
||||||
|
certtool --help &> /dev/null || test_skip "certtool missing"
|
||||||
|
check_chronyd_features DEBUG || test_skip "DEBUG support disabled"
|
||||||
|
systemd-socket-activate -h &> /dev/null || test_skip "systemd-socket-activate missing"
|
||||||
|
has_ipv6=$(check_chronyd_features IPV6 && ping6 -c 1 ::1 > /dev/null 2>&1 && echo 1 || echo 0)
|
||||||
|
|
||||||
|
test_start "systemd socket activation"
|
||||||
|
|
||||||
|
cat > $TEST_DIR/cert.cfg <<EOF
|
||||||
|
cn = "chrony-nts-test"
|
||||||
|
dns_name = "chrony-nts-test"
|
||||||
|
ip_address = "$server"
|
||||||
|
$([ "$has_ipv6" = "1" ] && echo 'ip_address = "::1"')
|
||||||
|
serial = 001
|
||||||
|
activation_date = "$[$(date '+%Y') - 1]-01-01 00:00:00 UTC"
|
||||||
|
expiration_date = "$[$(date '+%Y') + 2]-01-01 00:00:00 UTC"
|
||||||
|
signing_key
|
||||||
|
encryption_key
|
||||||
|
EOF
|
||||||
|
|
||||||
|
certtool --generate-privkey --key-type=ed25519 --outfile $TEST_DIR/server.key \
|
||||||
|
&> $TEST_DIR/certtool.log
|
||||||
|
certtool --generate-self-signed --load-privkey $TEST_DIR/server.key \
|
||||||
|
--template $TEST_DIR/cert.cfg --outfile $TEST_DIR/server.crt &>> $TEST_DIR/certtool.log
|
||||||
|
chown $user $TEST_DIR/server.*
|
||||||
|
|
||||||
|
ntpport=$(get_free_port)
|
||||||
|
ntsport=$(get_free_port)
|
||||||
|
|
||||||
|
server_options="port $ntpport nts ntsport $ntsport"
|
||||||
|
extra_chronyd_directives="
|
||||||
|
port $ntpport
|
||||||
|
ntsport $ntsport
|
||||||
|
ntsserverkey $TEST_DIR/server.key
|
||||||
|
ntsservercert $TEST_DIR/server.crt
|
||||||
|
ntstrustedcerts $TEST_DIR/server.crt
|
||||||
|
ntsdumpdir $TEST_LIBDIR
|
||||||
|
ntsprocesses 3"
|
||||||
|
|
||||||
|
if [ "$has_ipv6" = "1" ]; then
|
||||||
|
extra_chronyd_directives="$extra_chronyd_directives
|
||||||
|
bindaddress ::1
|
||||||
|
server ::1 minpoll -6 maxpoll -6 $server_options"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# enable debug logging
|
||||||
|
extra_chronyd_options="-L -1"
|
||||||
|
# Hack to trigger systemd-socket-activate to activate the service. Normally,
|
||||||
|
# chronyd.service would be configured with the WantedBy= directive so it starts
|
||||||
|
# without waiting for socket activation.
|
||||||
|
# (https://0pointer.de/blog/projects/socket-activation.html).
|
||||||
|
for i in $(seq 10); do
|
||||||
|
sleep 1
|
||||||
|
(echo "wake up" > /dev/udp/127.0.0.1/$ntpport) 2>/dev/null
|
||||||
|
(echo "wake up" > /dev/tcp/127.0.0.1/$ntsport) 2>/dev/null
|
||||||
|
done &
|
||||||
|
|
||||||
|
# Test with UDP sockets (unfortunately systemd-socket-activate doesn't support
|
||||||
|
# both datagram and stream sockets in the same invocation:
|
||||||
|
# https://github.com/systemd/systemd/issues/9983).
|
||||||
|
CHRONYD_WRAPPER="systemd-socket-activate \
|
||||||
|
--datagram \
|
||||||
|
--listen 127.0.0.1:$ntpport \
|
||||||
|
--listen 127.0.0.1:$ntsport"
|
||||||
|
if [ "$has_ipv6" = "1" ]; then
|
||||||
|
CHRONYD_WRAPPER="$CHRONYD_WRAPPER \
|
||||||
|
--listen [::1]:$ntpport \
|
||||||
|
--listen [::1]:$ntsport"
|
||||||
|
fi
|
||||||
|
|
||||||
|
start_chronyd || test_fail
|
||||||
|
wait_for_sync || test_fail
|
||||||
|
|
||||||
|
if [ "$has_ipv6" = "1" ]; then
|
||||||
|
run_chronyc "ntpdata ::1" || test_fail
|
||||||
|
check_chronyc_output "Total RX +: [1-9]" || test_fail
|
||||||
|
fi
|
||||||
|
run_chronyc "authdata" || test_fail
|
||||||
|
check_chronyc_output "^Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen
|
||||||
|
=========================================================================\
|
||||||
|
$([ "$has_ipv6" = "1" ] && printf "\n%s\n" '::1 NTS 1 (30|15) (128|256) [0-9] 0 0 [78] ( 64|100)')
|
||||||
|
127\.0\.0\.1 NTS 1 (30|15) (128|256) [0-9] 0 0 [78] ( 64|100)$" || test_fail
|
||||||
|
|
||||||
|
stop_chronyd || test_fail
|
||||||
|
# DGRAM ntpport socket should be used
|
||||||
|
check_chronyd_message_count "Reusing UDPv4 socket fd=3 local=127.0.0.1:$ntpport" 1 1 || test_fail
|
||||||
|
# DGRAM ntsport socket should be ignored
|
||||||
|
check_chronyd_message_count "Reusing TCPv4 socket fd=4 local=127.0.0.1:$ntsport" 0 0 || test_fail
|
||||||
|
if [ "$has_ipv6" = "1" ]; then
|
||||||
|
# DGRAM ntpport socket should be used
|
||||||
|
check_chronyd_message_count "Reusing UDPv6 socket fd=5 local=\[::1\]:$ntpport" 1 1 || test_fail
|
||||||
|
# DGRAM ntsport socket should be ignored
|
||||||
|
check_chronyd_message_count "Reusing TCPv6 socket fd=6 local=\[::1\]:$ntsport" 0 0 || test_fail
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_chronyd_messages || test_fail
|
||||||
|
check_chronyd_files || test_fail
|
||||||
|
|
||||||
|
# Test with TCP sockets
|
||||||
|
CHRONYD_WRAPPER="systemd-socket-activate \
|
||||||
|
--listen 127.0.0.1:$ntpport \
|
||||||
|
--listen 127.0.0.1:$ntsport"
|
||||||
|
if [ "$has_ipv6" = "1" ]; then
|
||||||
|
CHRONYD_WRAPPER="$CHRONYD_WRAPPER \
|
||||||
|
--listen [::1]:$ntpport \
|
||||||
|
--listen [::1]:$ntsport"
|
||||||
|
fi
|
||||||
|
|
||||||
|
start_chronyd || test_fail
|
||||||
|
wait_for_sync || test_fail
|
||||||
|
|
||||||
|
if [ "$has_ipv6" = "1" ]; then
|
||||||
|
run_chronyc "ntpdata ::1" || test_fail
|
||||||
|
check_chronyc_output "Total RX +: [1-9]" || test_fail
|
||||||
|
fi
|
||||||
|
run_chronyc "authdata" || test_fail
|
||||||
|
check_chronyc_output "^Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen
|
||||||
|
=========================================================================\
|
||||||
|
$([ "$has_ipv6" = "1" ] && printf "\n%s\n" '::1 NTS 1 (30|15) (128|256) [0-9] 0 0 [78] ( 64|100)')
|
||||||
|
127\.0\.0\.1 NTS 1 (30|15) (128|256) [0-9] 0 0 [78] ( 64|100)$" || test_fail
|
||||||
|
|
||||||
|
stop_chronyd || test_fail
|
||||||
|
# STREAM ntpport should be ignored
|
||||||
|
check_chronyd_message_count "Reusing TCPv4 socket fd=3 local=127.0.0.1:$ntpport" 0 0 || test_fail
|
||||||
|
# STREAM ntsport should be used
|
||||||
|
check_chronyd_message_count "Reusing TCPv4 socket fd=4 local=127.0.0.1:$ntsport" 1 1 || test_fail
|
||||||
|
if [ "$has_ipv6" = "1" ]; then
|
||||||
|
# STREAM ntpport should be ignored
|
||||||
|
check_chronyd_message_count "Reusing TCPv6 socket fd=5 local=\[::1\]:$ntpport" 0 0 || test_fail
|
||||||
|
# STREAM ntsport should be used
|
||||||
|
check_chronyd_message_count "Reusing TCPv6 socket fd=6 local=\[::1\]:$ntsport" 1 1 || test_fail
|
||||||
|
fi
|
||||||
|
check_chronyd_messages || test_fail
|
||||||
|
check_chronyd_files || test_fail
|
||||||
|
|
||||||
|
test_pass
|
|
@ -324,7 +324,7 @@ check_chronyd_messages() {
|
||||||
([ "$clock_control" -ne 0 ] || grep -q 'Disabled control of system clock' "$logfile") && \
|
([ "$clock_control" -ne 0 ] || grep -q 'Disabled control of system clock' "$logfile") && \
|
||||||
([ "$minimal_config" -ne 0 ] || grep -q 'Frequency .* read from' "$logfile") && \
|
([ "$minimal_config" -ne 0 ] || grep -q 'Frequency .* read from' "$logfile") && \
|
||||||
grep -q 'chronyd exiting' "$logfile" && \
|
grep -q 'chronyd exiting' "$logfile" && \
|
||||||
! grep -q 'Could not' "$logfile" && \
|
! (grep -v '^.\{19\}Z D:' "$logfile" | grep -q 'Could not') && \
|
||||||
! grep -q 'Disabled command socket' "$logfile" && \
|
! grep -q 'Disabled command socket' "$logfile" && \
|
||||||
test_ok || test_bad
|
test_ok || test_bad
|
||||||
}
|
}
|
||||||
|
|
61
test/unit/socket.c
Normal file
61
test/unit/socket.c
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
**********************************************************************
|
||||||
|
* Copyright (C) Luke Valenta 2023
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of version 2 of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
**********************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <socket.c>
|
||||||
|
#include "test.h"
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_preinitialise(void)
|
||||||
|
{
|
||||||
|
#ifdef LINUX
|
||||||
|
/* Test LISTEN_FDS environment variable parsing */
|
||||||
|
|
||||||
|
/* normal */
|
||||||
|
putenv("LISTEN_FDS=2");
|
||||||
|
SCK_PreInitialise();
|
||||||
|
TEST_CHECK(reusable_fds == 2);
|
||||||
|
|
||||||
|
/* negative */
|
||||||
|
putenv("LISTEN_FDS=-2");
|
||||||
|
SCK_PreInitialise();
|
||||||
|
TEST_CHECK(reusable_fds == 0);
|
||||||
|
|
||||||
|
/* trailing characters */
|
||||||
|
putenv("LISTEN_FDS=2a");
|
||||||
|
SCK_PreInitialise();
|
||||||
|
TEST_CHECK(reusable_fds == 0);
|
||||||
|
|
||||||
|
/* non-integer */
|
||||||
|
putenv("LISTEN_FDS=a2");
|
||||||
|
SCK_PreInitialise();
|
||||||
|
TEST_CHECK(reusable_fds == 0);
|
||||||
|
|
||||||
|
/* not set */
|
||||||
|
unsetenv("LISTEN_FDS");
|
||||||
|
SCK_PreInitialise();
|
||||||
|
TEST_CHECK(reusable_fds == 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
test_unit(void)
|
||||||
|
{
|
||||||
|
test_preinitialise();
|
||||||
|
}
|
Loading…
Reference in a new issue