[PATCH] Add support for systemd sockets

[ Thread Index | Date Index | More chrony.tuxfamily.org/chrony-dev Archives ]


Check for file descriptors passed by the service manager as part of
systemd socket activation. If any of the following file descriptor names
are provided then chronyd will attempt to use the corresponding socket
instead of creating a new one.

ntp4: IPv4 NTP server (DGRAM socket bound to IPv4 *bindaddress* and *port*)
ntp6: IPv6 NTP server (DGRAM socket bound to IPv6 *bindaddress* and *port*)
nts4: IPv4 NTS-KE server (STREAM socket bound to IPv4 *bindaddress* and
*ntsport*)
nts6: IPv6 NTS-KE server (STREAM socket bound to IPv6 *bindaddress* and
*ntsport*)

Aside from IPV6_V6ONLY (which cannot be set on already-bound sockets),
all socket options are set in the daemon, overwriting any options that
may be configured in the systemd socket unit.

Unit tests test the correct parsing of the LISTEN_FDS and LISTEN_FDNAMES
environment variables.

System tests test socket activation functionality using
the test tool systemd-socket-activate (which has some limitations
requiring workarounds, discussed in test/system/011-systemd).
---
 doc/chrony.conf.adoc    |  43 ++++++++++
 doc/faq.adoc            |  11 +++
 main.c                  |   5 +-
 ntp_io.c                |  37 +++++----
 nts_ke_server.c         |  35 ++++----
 socket.c                | 180 ++++++++++++++++++++++++++++++++++++++--
 socket.h                |  11 +++
 test/system/011-systemd | 129 ++++++++++++++++++++++++++++
 test/system/test.common |  16 ++++
 test/unit/socket.c      |  60 ++++++++++++++
 10 files changed, 487 insertions(+), 40 deletions(-)
 create mode 100755 test/system/011-systemd
 create mode 100644 test/unit/socket.c

diff --git a/doc/chrony.conf.adoc b/doc/chrony.conf.adoc
index 4af870d..e47332a 100644
--- a/doc/chrony.conf.adoc
+++ b/doc/chrony.conf.adoc
@@ -1534,6 +1534,49 @@ bindaddress 192.168.1.1
 Currently, for each of the IPv4 and IPv6 protocols, only one *bindaddress*
 directive can be specified. Therefore, it is not useful on computers which
 should serve NTP on multiple network interfaces.
++
+Before creating new server sockets, the chronyd daemon will check for file
+descriptors passed by the service manager as part of
+
https://www.freedesktop.org/software/systemd/man/latest/sd_listen_fds.html[systemd
socket activation].
+File descriptors to sockets that do not match corresponding directives in
+_chrony.conf_ will be ignored.  Currently, the following four named file
+descriptors are supported:
++
+----
+ntp4: IPv4 NTP server (DGRAM socket bound to IPv4 *bindaddress* and *port*)
+ntp6: IPv6 NTP server (DGRAM socket bound to IPv6 *bindaddress* and *port*)
+nts4: IPv4 NTS-KE server (STREAM socket bound to IPv4 *bindaddress* and
*ntsport*)
+nts6: IPv6 NTS-KE server (STREAM socket bound to IPv6 *bindaddress* and
*ntsport*)
+----
++
+An example systemd socket unit for the IPv4 NTP server is below, where
+*bindaddress* and *port* correspond to directives in _chrony.conf_.
++
+----
+[Unit]
+chronyd socket for IPv4 NTP server
+[Socket]
+FileDescriptorName=ntp4
+Service=chronyd.service
+ListenDatagram=*bindaddress*:*port*
+[Install]
+WantedBy=sockets.target
+----
++
+An example systemd socket unit for the IPv6 NTS-KE server is below, where
+*bindaddress* and *ntsport* correspond to directives in _chrony.conf_.
++
+----
+[Unit]
+chronyd socket for IPv6 NTS-KE server
+[Socket]
+FileDescriptorName=nts6
+Service=chronyd.service
+ListenStream=*bindaddress*:*port*
+BindIPv6Only=ipv6-only
+[Install]
+WantedBy=sockets.target
+----

 [[binddevice]]*binddevice* _interface_::
 The *binddevice* directive binds the NTP and NTS-KE server sockets to a
network
diff --git a/doc/faq.adoc b/doc/faq.adoc
index 57347c3..4873901 100644
--- a/doc/faq.adoc
+++ b/doc/faq.adoc
@@ -497,6 +497,17 @@ pidfile /var/run/chronyd-server1.pid
 driftfile /var/lib/chrony/drift-server1
 ----

+=== Does `chronyd` support systemd socket activation?
+
+Yes, for the NTP and NTS-KE server sockets.
+
+With socket activation, the service manager (systemd) creates sockets and
+passes file descriptors to them to the process instead of requiring the
process
+to create new sockets on startup. This allows for zero-downtime service
+upgrades, more parallelization, and simplified dependency logic at boot.
+
+See the *bindaddress* documention in _chrony.conf_ for more details.
+
 === Should be a leap smear enabled on NTP server?

 With the `smoothtime` and `leapsecmode` directives it is possible to
enable a
diff --git a/main.c b/main.c
index 3233707..c71c9dc 100644
--- a/main.c
+++ b/main.c
@@ -361,6 +361,7 @@ go_daemon(void)
       exit(0);
     } else {
       /* In the child we want to leave running as the daemon */
+      int n = SCK_GetSystemdListenFdCount();

       /* Change current directory to / */
       if (chdir("/") < 0) {
@@ -368,9 +369,9 @@ go_daemon(void)
       }

       /* Don't keep stdin/out/err from before. But don't close
-         the parent pipe yet. */
+         the parent pipe yet, or file descriptors from systemd. */
       for (fd=0; fd<1024; fd++) {
-        if (fd != pipefd[1])
+        if (fd != pipefd[1] && !(n > 0 && fd >= SD_LISTEN_FDS_START && fd
< SD_LISTEN_FDS_START + n))
           close(fd);
       }

diff --git a/ntp_io.c b/ntp_io.c
index fce7b17..ab4a4a8 100644
--- a/ntp_io.c
+++ b/ntp_io.c
@@ -93,7 +93,7 @@ static void read_from_socket(int sock_fd, int event, void
*anything);
 /* ================================================== */

 static int
-open_socket(int family, int local_port, int client_only, IPSockAddr
*remote_addr)
+open_socket(int family, int local_port, int client_only, IPSockAddr
*remote_addr, char *name)
 {
   int sock_fd, sock_flags, dscp, events = SCH_FILE_INPUT;
   IPSockAddr local_addr;
@@ -116,11 +116,18 @@ open_socket(int family, int local_port, int
client_only, IPSockAddr *remote_addr
   if (!client_only)
     sock_flags |= SCK_FLAG_BROADCAST;

-  sock_fd = SCK_OpenUdpSocket(remote_addr, &local_addr, iface, sock_flags);
-  if (sock_fd < 0) {
-    if (!client_only)
-      LOG(LOGS_ERR, "Could not open NTP socket on %s",
UTI_IPSockAddrToString(&local_addr));
-    return INVALID_SOCK_FD;
+  /* Attempt to retrieve socket file descriptor from service manager */
+  sock_fd = SCK_GetSystemdSocket(name, SOCK_DGRAM, &local_addr, iface,
sock_flags);
+
+  if (sock_fd > 0) {
+    LOG(LOGS_INFO, "Using NTP socket from systemd : fd=%d, name=%s",
sock_fd, name);
+  } else {
+    sock_fd = SCK_OpenUdpSocket(remote_addr, &local_addr, iface,
sock_flags);
+    if (sock_fd < 0) {
+      if (!client_only)
+        LOG(LOGS_ERR, "Could not open NTP socket on %s",
UTI_IPSockAddrToString(&local_addr));
+      return INVALID_SOCK_FD;
+    }
   }

   dscp = CNF_GetNtpDscp();
@@ -158,7 +165,7 @@ open_socket(int family, int local_port, int
client_only, IPSockAddr *remote_addr
 static int
 open_separate_client_socket(IPSockAddr *remote_addr)
 {
-  return open_socket(remote_addr->ip_addr.family, 0, 1, remote_addr);
+  return open_socket(remote_addr->ip_addr.family, 0, 1, remote_addr, NULL);
 }

 /* ================================================== */
@@ -216,14 +223,14 @@ NIO_Initialise(void)
   server_sock_ref6 = 0;

   if (permanent_server_sockets && server_port) {
-    server_sock_fd4 = open_socket(IPADDR_INET4, server_port, 0, NULL);
-    server_sock_fd6 = open_socket(IPADDR_INET6, server_port, 0, NULL);
+    server_sock_fd4 = open_socket(IPADDR_INET4, server_port, 0, NULL,
"ntp4");
+    server_sock_fd6 = open_socket(IPADDR_INET6, server_port, 0, NULL,
"ntp6");
   }

   if (!separate_client_sockets) {
     if (client_port != server_port || !server_port) {
-      client_sock_fd4 = open_socket(IPADDR_INET4, client_port, 1, NULL);
-      client_sock_fd6 = open_socket(IPADDR_INET6, client_port, 1, NULL);
+      client_sock_fd4 = open_socket(IPADDR_INET4, client_port, 1, NULL,
NULL);
+      client_sock_fd6 = open_socket(IPADDR_INET6, client_port, 1, NULL,
NULL);
     } else {
       client_sock_fd4 = server_sock_fd4;
       client_sock_fd6 = server_sock_fd6;
@@ -243,8 +250,8 @@ NIO_Initialise(void)
   ptp_message = NULL;

   if (ptp_port > 0) {
-    ptp_sock_fd4 = open_socket(IPADDR_INET4, ptp_port, 0, NULL);
-    ptp_sock_fd6 = open_socket(IPADDR_INET6, ptp_port, 0, NULL);
+    ptp_sock_fd4 = open_socket(IPADDR_INET4, ptp_port, 0, NULL, NULL);
+    ptp_sock_fd6 = open_socket(IPADDR_INET6, ptp_port, 0, NULL, NULL);
     ptp_message = MallocNew(PTP_NtpMessage);
   }
 }
@@ -323,7 +330,7 @@ NIO_OpenServerSocket(NTP_Remote_Address *remote_addr)
       if (permanent_server_sockets)
         return server_sock_fd4;
       if (server_sock_fd4 == INVALID_SOCK_FD)
-        server_sock_fd4 = open_socket(IPADDR_INET4, CNF_GetNTPPort(), 0,
NULL);
+        server_sock_fd4 = open_socket(IPADDR_INET4, CNF_GetNTPPort(), 0,
NULL, "ntp4");
       if (server_sock_fd4 != INVALID_SOCK_FD)
         server_sock_ref4++;
       return server_sock_fd4;
@@ -333,7 +340,7 @@ NIO_OpenServerSocket(NTP_Remote_Address *remote_addr)
       if (permanent_server_sockets)
         return server_sock_fd6;
       if (server_sock_fd6 == INVALID_SOCK_FD)
-        server_sock_fd6 = open_socket(IPADDR_INET6, CNF_GetNTPPort(), 0,
NULL);
+        server_sock_fd6 = open_socket(IPADDR_INET6, CNF_GetNTPPort(), 0,
NULL, "ntp6");
       if (server_sock_fd6 != INVALID_SOCK_FD)
         server_sock_ref6++;
       return server_sock_fd6;
diff --git a/nts_ke_server.c b/nts_ke_server.c
index 5e25c50..fcfceb5 100644
--- a/nts_ke_server.c
+++ b/nts_ke_server.c
@@ -293,7 +293,7 @@ accept_connection(int listening_fd, int event, void
*arg)
 /* ================================================== */

 static int
-open_socket(int family)
+open_socket(int family, char *name)
 {
   IPSockAddr local_addr;
   int backlog, sock_fd;
@@ -306,19 +306,26 @@ open_socket(int family)
   local_addr.port = CNF_GetNtsServerPort();
   iface = CNF_GetBindNtpInterface();

-  sock_fd = SCK_OpenTcpSocket(NULL, &local_addr, iface, 0);
-  if (sock_fd < 0) {
-    LOG(LOGS_ERR, "Could not open NTS-KE socket on %s",
UTI_IPSockAddrToString(&local_addr));
-    return INVALID_SOCK_FD;
-  }
+  /* Attempt to retrieve socket file descriptor from service manager */
+  sock_fd = SCK_GetSystemdSocket(name, SOCK_STREAM, &local_addr, iface, 0);
+
+  if (sock_fd > 0) {
+    LOG(LOGS_INFO, "Using NTS-KE socket from systemd : fd=%d, name=%s",
sock_fd, name);
+  } else {
+    sock_fd = SCK_OpenTcpSocket(NULL, &local_addr, iface, 0);
+    if (sock_fd < 0) {
+      LOG(LOGS_ERR, "Could not open NTS-KE socket on %s",
UTI_IPSockAddrToString(&local_addr));
+      return INVALID_SOCK_FD;
+    }

-  /* Set the maximum number of waiting connections on the socket to the
maximum
-     number of concurrent sessions */
-  backlog = MAX(CNF_GetNtsServerProcesses(), 1) *
CNF_GetNtsServerConnections();
+    /* Set the maximum number of waiting connections on the socket to the
maximum
+       number of concurrent sessions */
+    backlog = MAX(CNF_GetNtsServerProcesses(), 1) *
CNF_GetNtsServerConnections();

-  if (!SCK_ListenOnSocket(sock_fd, backlog)) {
-    SCK_CloseSocket(sock_fd);
-    return INVALID_SOCK_FD;
+    if (!SCK_ListenOnSocket(sock_fd, backlog)) {
+      SCK_CloseSocket(sock_fd);
+      return INVALID_SOCK_FD;
+    }
   }

   SCH_AddFileHandler(sock_fd, SCH_FILE_INPUT, accept_connection, NULL);
@@ -811,8 +818,8 @@ NKS_Initialise(void)
   current_server_key = MAX_SERVER_KEYS - 1;

   if (!is_helper) {
-    server_sock_fd4 = open_socket(IPADDR_INET4);
-    server_sock_fd6 = open_socket(IPADDR_INET6);
+    server_sock_fd4 = open_socket(IPADDR_INET4, "nts4");
+    server_sock_fd6 = open_socket(IPADDR_INET6, "nts6");

     key_rotation_interval = MAX(CNF_GetNtsRotate(), 0);

diff --git a/socket.c b/socket.c
index aa060a8..ebfe596 100644
--- a/socket.c
+++ b/socket.c
@@ -214,7 +214,7 @@ set_socket_flags(int sock_fd, int flags)
   /* Close the socket automatically on exec */
   if (
 #ifdef SOCK_CLOEXEC
-      (supported_socket_flags & SOCK_CLOEXEC) == 0 &&
+      (flags & SCK_FLAG_SYSTEMD || (supported_socket_flags & SOCK_CLOEXEC)
== 0) &&
 #endif
       !UTI_FdSetCloexec(sock_fd))
     return 0;
@@ -222,7 +222,7 @@ set_socket_flags(int sock_fd, int flags)
   /* Enable non-blocking mode */
   if ((flags & SCK_FLAG_BLOCK) == 0 &&
 #ifdef SOCK_NONBLOCK
-      (supported_socket_flags & SOCK_NONBLOCK) == 0 &&
+      (flags & SCK_FLAG_SYSTEMD || (supported_socket_flags &
SOCK_NONBLOCK) == 0) &&
 #endif
       !set_socket_nonblock(sock_fd))
     return 0;
@@ -296,8 +296,15 @@ set_ip_options(int sock_fd, int family, int flags)
 {
 #if defined(FEAT_IPV6) && defined(IPV6_V6ONLY)
   /* Receive only IPv6 packets on an IPv6 socket */
-  if (family == IPADDR_INET6 && !SCK_SetIntOption(sock_fd, IPPROTO_IPV6,
IPV6_V6ONLY, 1))
-    return 0;
+  if (family == IPADDR_INET6) {
+    /* Only check if the option is set for systemd sockets as we cannot set
+     * IPV6_V6ONLY when the socket is already bound. */
+    int opt;
+    if (flags & SCK_FLAG_SYSTEMD && (!SCK_GetIntOption(sock_fd,
IPPROTO_IPV6, IPV6_V6ONLY, &opt) || opt != 1))
+      return 0;
+    else if (!SCK_SetIntOption(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, 1))
+      return 0;
+  }
 #endif

   /* Provide destination address of received packets if requested */
@@ -361,12 +368,8 @@ bind_device(int sock_fd, const char *iface)
 /* ================================================== */

 static int
-bind_ip_address(int sock_fd, IPSockAddr *addr, int flags)
+set_bind_options(int sock_fd, IPSockAddr *addr)
 {
-  union sockaddr_all saddr;
-  socklen_t saddr_len;
-  int s;
-
   /* Make the socket capable of re-using an old address if binding to a
specific port */
   if (addr->port > 0 && !SCK_SetIntOption(sock_fd, SOL_SOCKET,
SO_REUSEADDR, 1))
     ;
@@ -385,6 +388,71 @@ bind_ip_address(int sock_fd, IPSockAddr *addr, int
flags)
     ;
 #endif

+  return 1;
+}
+
+/* ================================================== */
+
+static int
+check_ip_bind_address(int fd, int type, IPSockAddr *addr)
+{
+  int opt;
+  socklen_t l;
+  union sockaddr_all saddr;
+  IPSockAddr ip_sa;
+
+  /* Check that chrony configured address is valid */
+  if (!addr || addr->port == 0 ||
+          (addr->ip_addr.family != IPADDR_INET4 && addr->ip_addr.family !=
IPADDR_INET6) ||
+          (type != SOCK_STREAM && type != SOCK_DGRAM)) {
+      DEBUG_LOG("invalid bind address");
+      return 0;
+  }
+
+  /* Check type */
+  l = sizeof(opt);
+  if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &opt, &l) < 0 || l !=
sizeof(opt) || opt != type) {
+    DEBUG_LOG("type mismatch : fd=%d %d != %d", fd, type, opt);
+    return 0;
+  }
+
+  if (type == SOCK_STREAM) {
+    /* Check if STREAM socket is listening */
+    l = sizeof(opt);
+    if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &opt, &l) < 0 || l !=
sizeof(opt) || opt == 0) {
+      DEBUG_LOG("STREAM socket not listening");
+      return 0;
+    }
+  }
+
+  l = sizeof(saddr);
+  if (getsockname(fd, &saddr.sa, &l) < 0 || l < sizeof(sa_family_t)) {
+    DEBUG_LOG("failed to get socket address from fd");
+    return 0;
+  }
+  SCK_SockaddrToIPSockAddr(&saddr.sa, l, &ip_sa);
+
+  if (UTI_CompareIPs(&ip_sa.ip_addr, &addr->ip_addr, NULL) != 0 ||
ip_sa.port != addr->port) {
+    DEBUG_LOG("address mismatch : %s != %s",
UTI_IPSockAddrToString(&ip_sa), UTI_IPSockAddrToString(addr));
+    return 0;
+  }
+
+  return 1;
+}
+
+
+/* ================================================== */
+
+static int
+bind_ip_address(int sock_fd, IPSockAddr *addr, int flags)
+{
+  union sockaddr_all saddr;
+  socklen_t saddr_len;
+  int s;
+
+  if (!set_bind_options(sock_fd, addr))
+    return 0;
+
   saddr_len = SCK_IPSockAddrToSockaddr(addr, (struct sockaddr *)&saddr,
sizeof (saddr));
   if (saddr_len == 0)
     return 0;
@@ -426,6 +494,41 @@ connect_ip_address(int sock_fd, IPSockAddr *addr)

 /* ================================================== */

+static int
+get_systemd_socket(const char *name)
+{
+  char *s;
+  int i, l, n;
+
+  if (!name)
+    return INVALID_SOCK_FD;
+
+  n = SCK_GetSystemdListenFdCount();
+  s = getenv("LISTEN_FDNAMES");
+
+  if (n > 0 && s) {
+    for (i = 0; i < n; i++) {
+      /* Find length of token */
+      l = 0;
+      while (s[l] != ':' && s[l] != '\0')
+        l++;
+
+      /* Compare (not always null-terminated) token to name */
+      if (strlen(name) == l && !strncmp(name, s, l))
+        return SD_LISTEN_FDS_START + i;
+
+      if (s[l] == '\0')
+        break;
+
+      /* Move to next token */
+      s+=l+1;
+    }
+  }
+  return INVALID_SOCK_FD;
+}
+
+/* ================================================== */
+
 static int
 open_ip_socket(IPSockAddr *remote_addr, IPSockAddr *local_addr, const char
*iface,
                int type, int flags)
@@ -1353,6 +1456,65 @@ SCK_OpenUnixSocketPair(int flags, int *other_fd)

 /* ================================================== */

+int SCK_GetSystemdListenFdCount(void)
+{
+  int n;
+  char *s, *ptr;
+
+  s = getenv("LISTEN_FDS");
+  if (!s)
+      return 0;
+  errno = 0;
+  n = strtol(s, &ptr, 10);
+  if (errno != 0 || *ptr != '\0')
+      return 0;
+
+  return n;
+}
+
+/* ================================================== */
+
+int
+SCK_GetSystemdSocket(const char *name, int type, IPSockAddr *addr, const
char *iface, int flags)
+{
+  int sock_fd;
+  flags |= SCK_FLAG_SYSTEMD;
+
+  sock_fd = get_systemd_socket(name);
+  if (sock_fd < 0)
+    return INVALID_SOCK_FD;
+  DEBUG_LOG("Found systemd socket : fd=%d, name=%s", sock_fd, name);
+
+  if (!set_socket_flags(sock_fd, flags))
+    goto error;
+
+  if (!set_socket_options(sock_fd, flags))
+    goto error;
+
+  if (!set_ip_options(sock_fd, addr->ip_addr.family, flags))
+    goto error;
+
+  /* Bind device if interface provided */
+  if (iface && !bind_device(sock_fd, iface))
+    goto error;
+
+  if (!set_bind_options(sock_fd, addr))
+    goto error;
+
+  /* Check that the socket is bound to the correct address */
+  if (!check_ip_bind_address(sock_fd, type, addr))
+    goto error;
+
+  return sock_fd;
+
+error:
+  SCK_CloseSocket(sock_fd);
+  LOG(LOGS_ERR, "Failed to use systemd socket : fd=%d, name=%s", sock_fd,
name);
+  return INVALID_SOCK_FD;
+}
+
+/* ================================================== */
+
 int
 SCK_SetIntOption(int sock_fd, int level, int name, int value)
 {
diff --git a/socket.h b/socket.h
index cdbae2d..5e350bd 100644
--- a/socket.h
+++ b/socket.h
@@ -36,11 +36,15 @@
 #define SCK_FLAG_RX_DEST_ADDR 4
 #define SCK_FLAG_ALL_PERMISSIONS 8
 #define SCK_FLAG_PRIV_BIND 16
+#define SCK_FLAG_SYSTEMD 32

 /* Flags for receiving and sending messages */
 #define SCK_FLAG_MSG_ERRQUEUE 1
 #define SCK_FLAG_MSG_DESCRIPTOR 2

+/* From sd-daemon.h */
+#define SD_LISTEN_FDS_START 3
+
 typedef enum {
   SCK_ADDR_UNSPEC = 0,
   SCK_ADDR_IP,
@@ -106,6 +110,13 @@ extern int SCK_OpenUnixStreamSocket(const char
*remote_addr, const char *local_a
                                     int flags);
 extern int SCK_OpenUnixSocketPair(int flags, int *other_fd);

+/* Get number of socket file descriptors from service manager */
+extern int SCK_GetSystemdListenFdCount(void);
+
+/* Get and configure socket file descriptor from service manager by name */
+extern int SCK_GetSystemdSocket(const char *name, int type, IPSockAddr
*local_addr,
+                             const char *iface, int flags);
+
 /* 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_GetIntOption(int sock_fd, int level, int name, int *value);
diff --git a/test/system/011-systemd b/test/system/011-systemd
new file mode 100755
index 0000000..9a79163
--- /dev/null
+++ b/test/system/011-systemd
@@ -0,0 +1,129 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+check_chronyd_features NTS || test_skip "NTS support disabled"
+has_ipv6=$(check_chronyd_features IPV6 && ping6 -c 1 ::1 > /dev/null 2>&1
&& echo 1 || echo 0)
+certtool --help &> /dev/null || test_skip "certtool missing"
+systemd-socket-activate -h &> /dev/null || test_skip
"systemd-socket-activate missing"
+
+test_start "systemd socket activation (ipv6=$has_ipv6)"
+
+cat > $TEST_DIR/cert.cfg <<EOF
+cn = "chrony-nts-test"
+dns_name = "chrony-nts-test"
+ip_address = "$server"
+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"
+
+# Test UDP sockets for NTP (unfortunately systemd-socket-activate doesn't
+# support both datagram and stream sockets in the same invocation).
+if [ "$has_ipv6" = "1" ]; then
+CHRONYD_WRAPPER="systemd-socket-activate \
+ --datagram \
+ --listen 127.0.0.1:$ntpport --fdname=ntp4 \
+ --listen [::1]:$ntpport --fdname=ntp6"
+else
+CHRONYD_WRAPPER="systemd-socket-activate \
+ --datagram \
+ --listen 127.0.0.1:$ntpport --fdname=ntp4"
+fi
+
+# 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).
+(sleep 1 && echo "wake up" > /dev/udp/127.0.0.1/$ntpport) &
+start_chronyd || test_fail
+wait_for_sync || test_fail
+
+run_chronyd_client "server 127.0.0.1 port $ntpport iburst maxsamples 1" ||
test_fail
+check_chronyd_client_output || test_fail
+
+if [ "$has_ipv6" = "1" ]; then
+run_chronyd_client "server ::1 port $ntpport iburst maxsamples 1" ||
test_fail
+check_chronyd_client_output || test_fail
+fi
+
+stop_chronyd || test_fail
+check_chronyd_message_count "Using NTP socket from systemd : fd=3,
name=ntp4" 1 1 || test_fail
+if [ "$has_ipv6" = "1" ]; then
+check_chronyd_message_count "Using NTP socket from systemd : fd=4,
name=ntp6" 1 1 || test_fail
+fi
+check_chronyd_messages || test_fail
+check_chronyd_files || test_fail
+
+# Test TCP sockets for NTS-KE
+if [ "$has_ipv6" = "1" ]; then
+CHRONYD_WRAPPER="systemd-socket-activate \
+ --listen 127.0.0.1:$ntsport --fdname=nts4 \
+ --listen [::1]:$ntsport --fdname=nts6"
+else
+CHRONYD_WRAPPER="systemd-socket-activate \
+ --listen 127.0.0.1:$ntsport --fdname=nts4"
+fi
+# Same hack as above to trigger socket activation.
+(sleep 1 && echo "wake up" > /dev/tcp/127.0.0.1/$ntsport) &
+start_chronyd || test_fail
+wait_for_sync || test_fail
+
+run_chronyd_client "server 127.0.0.1 port $ntpport nts ntsport $ntsport
iburst maxsamples 1" "ntstrustedcerts $TEST_DIR/server.crt" || test_fail
+check_chronyd_client_output || test_fail
+
+if [ "$has_ipv6" = "1" ]; then
+run_chronyd_client "server ::1 port $ntpport iburst maxsamples 1" ||
test_fail
+check_chronyd_client_output || test_fail
+fi
+
+stop_chronyd || test_fail
+check_chronyd_message_count 'Using NTS-KE socket from systemd : fd=3,
name=nts4' 1 1 || test_fail
+if [ "$has_ipv6" = "1" ]; then
+check_chronyd_message_count 'Using NTS-KE socket from systemd : fd=4,
name=nts6' 1 1 || test_fail
+fi
+check_chronyd_messages || test_fail
+check_chronyd_files || test_fail
+
+# Test invalid socket configs
+# - nts4 as DGRAM socket should be ignored
+# - foo should be ignored
+CHRONYD_WRAPPER="systemd-socket-activate \
+ --datagram \
+ --listen 127.0.0.1:$ntpport --fdname=foo \
+ --listen 127.0.0.1:$ntsport --fdname=nts4"
+# Same hack as above to trigger socket activation.
+(sleep 1 && echo "wake up" > /dev/udp/127.0.0.1/$ntsport) &
+start_chronyd || test_fail
+wait_for_sync || test_fail
+
+run_chronyd_client "server 127.0.0.1 port $ntpport nts ntsport $ntsport
iburst maxsamples 1" "ntstrustedcerts $TEST_DIR/server.crt" || test_fail
+check_chronyd_client_output || test_fail
+
+stop_chronyd || test_fail
+check_chronyd_message_count 'Failed to use systemd socket : fd=4,
name=nts4' 1 1 || test_fail
+check_chronyd_messages || test_fail
+check_chronyd_files || test_fail
+
+test_pass
diff --git a/test/system/test.common b/test/system/test.common
index aa48ac6..381eb3c 100644
--- a/test/system/test.common
+++ b/test/system/test.common
@@ -349,6 +349,22 @@ check_chronyd_files() {
  test_ok || test_bad
 }

+# Run chronyd in client mode to retrieve and print out time
+run_chronyd_client() {
+ local args=( "$@" )
+ test_message 1 0 "running chronyd client"
+
+ "$chronyd" -Q "${args[@]}" > "$TEST_DIR/chronyd-client.out" 2>&1 &&
test_ok || test_error
+}
+
+check_chronyd_client_output() {
+ local pattern=$1
+
+ test_message 1 0 "checking chronyd client output"
+ grep -q "System clock wrong by .* seconds (ignored)"
"$TEST_DIR/chronyd-client.out" && \
+        test_ok || test_error
+}
+
 # Run a chronyc command
 run_chronyc() {
  local host=$chronyc_host options="-n -m"
diff --git a/test/unit/socket.c b/test/unit/socket.c
new file mode 100644
index 0000000..007984b
--- /dev/null
+++ b/test/unit/socket.c
@@ -0,0 +1,60 @@
+/*
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar  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"
+
+void
+test_unit(void)
+{
+  /* Test systemd environment variable parsing */
+
+  /* Normal case */
+  putenv("LISTEN_FDS=2");
+  putenv("LISTEN_FDNAMES=foo:bar");
+  TEST_CHECK(get_systemd_socket("foo") == SD_LISTEN_FDS_START);
+  TEST_CHECK(get_systemd_socket("bar") == SD_LISTEN_FDS_START + 1);
+  TEST_CHECK(get_systemd_socket("baz") < 0);
+
+  /* Only attempt to parse up to LISTEN_FDS entries */
+  putenv("LISTEN_FDS=1");
+  putenv("LISTEN_FDNAMES=foo:bar");
+  TEST_CHECK(get_systemd_socket("bar") < 0);
+
+  /* OK if a file descriptor name is empty */
+  putenv("LISTEN_FDS=2");
+  putenv("LISTEN_FDNAMES=:bar");
+  TEST_CHECK(get_systemd_socket("") == SD_LISTEN_FDS_START);
+
+  /* OK if LISTEN_FDS doesn't match number of entries in LISTEN_FDNAMES */
+  putenv("LISTEN_FDS=8");
+  putenv("LISTEN_FDNAMES=foo");
+  TEST_CHECK(get_systemd_socket("foo") == SD_LISTEN_FDS_START);
+
+  /* Make sure there are no trailing characters */
+  putenv("LISTEN_FDS=1a");
+  putenv("LISTEN_FDNAMES=foo");
+  TEST_CHECK(get_systemd_socket("foo") < 0);
+
+  /* Handle invalid LISTEN_FDS */
+  putenv("LISTEN_FDS=a1");
+  putenv("LISTEN_FDNAMES=foo");
+  TEST_CHECK(get_systemd_socket("foo") < 0);
+}
-- 
2.39.3 (Apple Git-145)


-- 
Luke Valenta
Systems Engineer - Research

--00000000000091fca90608293f0d
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

<div dir=3D"ltr"><div>Hi folks,</div><div><br></div><div>Please consider th=
is patch to add support for using NTP and NTS-KE server sockets passed to c=
hrony from the systemd service manager.</div><div><br></div><div>For contex=
t, we&#39;re currently testing out this patch as part of moving chrony to o=
ur tubular system for routing client traffic to services (<a href=3D"https:=
//blog.cloudflare.com/tubular-fixing-the-socket-api-with-ebpf/">https://blo=
g.cloudflare.com/tubular-fixing-the-socket-api-with-ebpf/</a>).</div><div><=
br></div><div>Aside from our specific use case, socket activation is quite =
common in services (see the list at <a href=3D"https://www.redhat.com/sysad=
min/socket-activation-podman">https://www.redhat.com/sysadmin/socket-activa=
tion-podman</a>) and we hope that others may benefit from the patch as well=
.. More info on socket activation is available from the links at the bottom =
of <a href=3D"https://www.freedesktop.org/software/systemd/man/latest/syste=
md.socket.html">https://www.freedesktop.org/software/systemd/man/latest/sys=
temd.socket.html</a>.</div><div><br></div><div>The patch only adds support =
for socket activation for the IPv4 and IPv6 NTP and NTS-KE server sockets s=
ince those are relevant for our use case. However, adding socket activation=
 for the command socket is possible as well.<br></div><div><br></div><div>T=
he patch is also available for review convenience in this PR:=C2=A0<a href=
=3D"https://github.com/lukevalenta/chrony/pull/1";>https://github.com/lukeva=
lenta/chrony/pull/1</a>. Any feedback on the patch is welcome and appreciat=
ed!</div><div><br></div><div>Thanks,</div><div>Luke</div><div><br></div><di=
v>---</div><div>From 499ebf2e438393e4c95912c8eb277d615219e222 Mon Sep 17 00=
:00:00 2001<br>From: Luke Valenta &lt;<a href=3D"mailto:lvalenta@cloudflare=
..com">lvalenta@xxxxxxxxxxxxxx</a>&gt;<br>Date: Wed, 11 Oct 2023 13:51:39 -0=
400<br>Subject: [PATCH] Add support for systemd sockets<br><br>Check for fi=
le descriptors passed by the service manager as part of<br>systemd socket a=
ctivation. If any of the following file descriptor names<br>are provided th=
en chronyd will attempt to use the corresponding socket<br>instead of creat=
ing a new one.<br><br>ntp4: IPv4 NTP server (DGRAM socket bound to IPv4 *bi=
ndaddress* and *port*)<br>ntp6: IPv6 NTP server (DGRAM socket bound to IPv6=
 *bindaddress* and *port*)<br>nts4: IPv4 NTS-KE server (STREAM socket bound=
 to IPv4 *bindaddress* and *ntsport*)<br>nts6: IPv6 NTS-KE server (STREAM s=
ocket bound to IPv6 *bindaddress* and *ntsport*)<br><br>Aside from IPV6_V6O=
NLY (which cannot be set on already-bound sockets),<br>all socket options a=
re set in the daemon, overwriting any options that<br>may be configured in =
the systemd socket unit.<br><br>Unit tests test the correct parsing of the =
LISTEN_FDS and LISTEN_FDNAMES<br>environment variables.<br><br>System tests=
 test socket activation functionality using<br>the test tool systemd-socket=
-activate (which has some limitations<br>requiring workarounds, discussed i=
n test/system/011-systemd).<br>---<br>=C2=A0doc/chrony.conf.adoc =C2=A0 =C2=
=A0| =C2=A043 ++++++++++<br>=C2=A0doc/faq.adoc =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 =C2=A0| =C2=A011 +++<br>=C2=A0main.c =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=
=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0| =C2=A0 5 +-<br>=C2=A0ntp_io.c =C2=A0 =C2=
=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0| =C2=A037 +++++----<br>=C2=A0=
nts_ke_server.c =C2=A0 =C2=A0 =C2=A0 =C2=A0 | =C2=A035 ++++----<br>=C2=A0so=
cket.c =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0| 180 +++++++=
+++++++++++++++++++++++++++++++--<br>=C2=A0socket.h =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0| =C2=A011 +++<br>=C2=A0test/system/011-s=
ystemd | 129 ++++++++++++++++++++++++++++<br>=C2=A0test/system/test.common =
| =C2=A016 ++++<br>=C2=A0test/unit/socket.c =C2=A0 =C2=A0 =C2=A0| =C2=A060 =
++++++++++++++<br>=C2=A010 files changed, 487 insertions(+), 40 deletions(-=
)<br>=C2=A0create mode 100755 test/system/011-systemd<br>=C2=A0create mode =
100644 test/unit/socket.c<br><br>diff --git a/doc/chrony.conf.adoc b/doc/ch=
rony.conf.adoc<br>index 4af870d..e47332a 100644<br>--- a/doc/chrony.conf.ad=
oc<br>+++ b/doc/chrony.conf.adoc<br>@@ -1534,6 +1534,49 @@ bindaddress 192.=
168.1.1<br>=C2=A0Currently, for each of the IPv4 and IPv6 protocols, only o=
ne *bindaddress*<br>=C2=A0directive can be specified. Therefore, it is not =
useful on computers which<br>=C2=A0should serve NTP on multiple network int=
erfaces.<br>++<br>+Before creating new server sockets, the chronyd daemon w=
ill check for file<br>+descriptors passed by the service manager as part of=
<br>+<a href=3D"https://www.freedesktop.org/software/systemd/man/latest/sd_=
listen_fds.html[systemd">https://www.freedesktop.org/software/systemd/man/l=
atest/sd_listen_fds.html[systemd</a> socket activation].<br>+File descripto=
rs to sockets that do not match corresponding directives in<br>+_chrony.con=
f_ will be ignored.=C2=A0 Currently, the following four named file<br>+desc=
riptors are supported:<br>++<br>+----<br>+ntp4: IPv4 NTP server (DGRAM sock=
et bound to IPv4 *bindaddress* and *port*)<br>+ntp6: IPv6 NTP server (DGRAM=
 socket bound to IPv6 *bindaddress* and *port*)<br>+nts4: IPv4 NTS-KE serve=
r (STREAM socket bound to IPv4 *bindaddress* and *ntsport*)<br>+nts6: IPv6 =
NTS-KE server (STREAM socket bound to IPv6 *bindaddress* and *ntsport*)<br>=
+----<br>++<br>+An example systemd socket unit for the IPv4 NTP server is b=
elow, where<br>+*bindaddress* and *port* correspond to directives in _chron=
y.conf_.<br>++<br>+----<br>+[Unit]<br>+chronyd socket for IPv4 NTP server<b=
r>+[Socket]<br>+FileDescriptorName=3Dntp4<br>+Service=3Dchronyd.service<br>=
+ListenDatagram=3D*bindaddress*:*port*<br>+[Install]<br>+WantedBy=3Dsockets=
..target<br>+----<br>++<br>+An example systemd socket unit for the IPv6 NTS-=
KE server is below, where<br>+*bindaddress* and *ntsport* correspond to dir=
ectives in _chrony.conf_.<br>++<br>+----<br>+[Unit]<br>+chronyd socket for =
IPv6 NTS-KE server<br>+[Socket]<br>+FileDescriptorName=3Dnts6<br>+Service=
=3Dchronyd.service<br>+ListenStream=3D*bindaddress*:*port*<br>+BindIPv6Only=
=3Dipv6-only<br>+[Install]<br>+WantedBy=3Dsockets.target<br>+----<br>=C2=A0=
<br>=C2=A0[[binddevice]]*binddevice* _interface_::<br>=C2=A0The *binddevice=
* directive binds the NTP and NTS-KE server sockets to a network<br>diff --=
git a/doc/faq.adoc b/doc/faq.adoc<br>index 57347c3..4873901 100644<br>--- a=
/doc/faq.adoc<br>+++ b/doc/faq.adoc<br>@@ -497,6 +497,17 @@ pidfile /var/ru=
n/chronyd-server1.pid<br>=C2=A0driftfile /var/lib/chrony/drift-server1<br>=
=C2=A0----<br>=C2=A0<br>+=3D=3D=3D Does `chronyd` support systemd socket ac=
tivation?<br>+<br>+Yes, for the NTP and NTS-KE server sockets.<br>+<br>+Wit=
h socket activation, the service manager (systemd) creates sockets and<br>+=
passes file descriptors to them to the process instead of requiring the pro=
cess<br>+to create new sockets on startup. This allows for zero-downtime se=
rvice<br>+upgrades, more parallelization, and simplified dependency logic a=
t boot.<br>+<br>+See the *bindaddress* documention in _chrony.conf_ for mor=
e details.<br>+<br>=C2=A0=3D=3D=3D Should be a leap smear enabled on NTP se=
rver?<br>=C2=A0<br>=C2=A0With the `smoothtime` and `leapsecmode` directives=
 it is possible to enable a<br>diff --git a/main.c b/main.c<br>index 323370=
7..c71c9dc 100644<br>--- a/main.c<br>+++ b/main.c<br>@@ -361,6 +361,7 @@ go=
_daemon(void)<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0exit(0);<br>=C2=A0 =C2=A0 =C2=
=A0} else {<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0/* In the child we want to leave =
running as the daemon */<br>+ =C2=A0 =C2=A0 =C2=A0int n =3D SCK_GetSystemdL=
istenFdCount();<br>=C2=A0<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0/* Change current d=
irectory to / */<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0if (chdir(&quot;/&quot;) &lt=
; 0) {<br>@@ -368,9 +369,9 @@ go_daemon(void)<br>=C2=A0 =C2=A0 =C2=A0 =C2=
=A0}<br>=C2=A0<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0/* Don&#39;t keep stdin/out/er=
r from before. But don&#39;t close<br>- =C2=A0 =C2=A0 =C2=A0 =C2=A0 the par=
ent pipe yet. */<br>+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 the parent pipe yet, or f=
ile descriptors from systemd. */<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0for (fd=3D0;=
 fd&lt;1024; fd++) {<br>- =C2=A0 =C2=A0 =C2=A0 =C2=A0if (fd !=3D pipefd[1])=
<br>+ =C2=A0 =C2=A0 =C2=A0 =C2=A0if (fd !=3D pipefd[1] &amp;&amp; !(n &gt; =
0 &amp;&amp; fd &gt;=3D SD_LISTEN_FDS_START &amp;&amp; fd &lt; SD_LISTEN_FD=
S_START + n))<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0close(fd);<br>=C2=
=A0 =C2=A0 =C2=A0 =C2=A0}<br>=C2=A0<br>diff --git a/ntp_io.c b/ntp_io.c<br>=
index fce7b17..ab4a4a8 100644<br>--- a/ntp_io.c<br>+++ b/ntp_io.c<br>@@ -93=
,7 +93,7 @@ static void read_from_socket(int sock_fd, int event, void *anyt=
hing);<br>=C2=A0/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D */<br>=C2=A0<br>=C2=A0static int<br>-open_socket(int =
family, int local_port, int client_only, IPSockAddr *remote_addr)<br>+open_=
socket(int family, int local_port, int client_only, IPSockAddr *remote_addr=
, char *name)<br>=C2=A0{<br>=C2=A0 =C2=A0int sock_fd, sock_flags, dscp, eve=
nts =3D SCH_FILE_INPUT;<br>=C2=A0 =C2=A0IPSockAddr local_addr;<br>@@ -116,1=
1 +116,18 @@ open_socket(int family, int local_port, int client_only, IPSoc=
kAddr *remote_addr<br>=C2=A0 =C2=A0if (!client_only)<br>=C2=A0 =C2=A0 =C2=
=A0sock_flags |=3D SCK_FLAG_BROADCAST;<br>=C2=A0<br>- =C2=A0sock_fd =3D SCK=
_OpenUdpSocket(remote_addr, &amp;local_addr, iface, sock_flags);<br>- =C2=
=A0if (sock_fd &lt; 0) {<br>- =C2=A0 =C2=A0if (!client_only)<br>- =C2=A0 =
=C2=A0 =C2=A0LOG(LOGS_ERR, &quot;Could not open NTP socket on %s&quot;, UTI=
_IPSockAddrToString(&amp;local_addr));<br>- =C2=A0 =C2=A0return INVALID_SOC=
K_FD;<br>+ =C2=A0/* Attempt to retrieve socket file descriptor from service=
 manager */<br>+ =C2=A0sock_fd =3D SCK_GetSystemdSocket(name, SOCK_DGRAM, &=
amp;local_addr, iface, sock_flags);<br>+<br>+ =C2=A0if (sock_fd &gt; 0) {<b=
r>+ =C2=A0 =C2=A0LOG(LOGS_INFO, &quot;Using NTP socket from systemd : fd=3D=
%d, name=3D%s&quot;, sock_fd, name);<br>+ =C2=A0} else {<br>+ =C2=A0 =C2=A0=
sock_fd =3D SCK_OpenUdpSocket(remote_addr, &amp;local_addr, iface, sock_fla=
gs);<br>+ =C2=A0 =C2=A0if (sock_fd &lt; 0) {<br>+ =C2=A0 =C2=A0 =C2=A0if (!=
client_only)<br>+ =C2=A0 =C2=A0 =C2=A0 =C2=A0LOG(LOGS_ERR, &quot;Could not =
open NTP socket on %s&quot;, UTI_IPSockAddrToString(&amp;local_addr));<br>+=
 =C2=A0 =C2=A0 =C2=A0return INVALID_SOCK_FD;<br>+ =C2=A0 =C2=A0}<br>=C2=A0 =
=C2=A0}<br>=C2=A0<br>=C2=A0 =C2=A0dscp =3D CNF_GetNtpDscp();<br>@@ -158,7 +=
165,7 @@ open_socket(int family, int local_port, int client_only, IPSockAdd=
r *remote_addr<br>=C2=A0static int<br>=C2=A0open_separate_client_socket(IPS=
ockAddr *remote_addr)<br>=C2=A0{<br>- =C2=A0return open_socket(remote_addr-=
&gt;ip_addr.family, 0, 1, remote_addr);<br>+ =C2=A0return open_socket(remot=
e_addr-&gt;ip_addr.family, 0, 1, remote_addr, NULL);<br>=C2=A0}<br>=C2=A0<b=
r>=C2=A0/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D */<br>@@ -216,14 +223,14 @@ NIO_Initialise(void)<br>=C2=A0 =C2=
=A0server_sock_ref6 =3D 0;<br>=C2=A0<br>=C2=A0 =C2=A0if (permanent_server_s=
ockets &amp;&amp; server_port) {<br>- =C2=A0 =C2=A0server_sock_fd4 =3D open=
_socket(IPADDR_INET4, server_port, 0, NULL);<br>- =C2=A0 =C2=A0server_sock_=
fd6 =3D open_socket(IPADDR_INET6, server_port, 0, NULL);<br>+ =C2=A0 =C2=A0=
server_sock_fd4 =3D open_socket(IPADDR_INET4, server_port, 0, NULL, &quot;n=
tp4&quot;);<br>+ =C2=A0 =C2=A0server_sock_fd6 =3D open_socket(IPADDR_INET6,=
 server_port, 0, NULL, &quot;ntp6&quot;);<br>=C2=A0 =C2=A0}<br>=C2=A0<br>=
=C2=A0 =C2=A0if (!separate_client_sockets) {<br>=C2=A0 =C2=A0 =C2=A0if (cli=
ent_port !=3D server_port || !server_port) {<br>- =C2=A0 =C2=A0 =C2=A0clien=
t_sock_fd4 =3D open_socket(IPADDR_INET4, client_port, 1, NULL);<br>- =C2=A0=
 =C2=A0 =C2=A0client_sock_fd6 =3D open_socket(IPADDR_INET6, client_port, 1,=
 NULL);<br>+ =C2=A0 =C2=A0 =C2=A0client_sock_fd4 =3D open_socket(IPADDR_INE=
T4, client_port, 1, NULL, NULL);<br>+ =C2=A0 =C2=A0 =C2=A0client_sock_fd6 =
=3D open_socket(IPADDR_INET6, client_port, 1, NULL, NULL);<br>=C2=A0 =C2=A0=
 =C2=A0} else {<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0client_sock_fd4 =3D server_so=
ck_fd4;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0client_sock_fd6 =3D server_sock_fd6;<=
br>@@ -243,8 +250,8 @@ NIO_Initialise(void)<br>=C2=A0 =C2=A0ptp_message =3D=
 NULL;<br>=C2=A0<br>=C2=A0 =C2=A0if (ptp_port &gt; 0) {<br>- =C2=A0 =C2=A0p=
tp_sock_fd4 =3D open_socket(IPADDR_INET4, ptp_port, 0, NULL);<br>- =C2=A0 =
=C2=A0ptp_sock_fd6 =3D open_socket(IPADDR_INET6, ptp_port, 0, NULL);<br>+ =
=C2=A0 =C2=A0ptp_sock_fd4 =3D open_socket(IPADDR_INET4, ptp_port, 0, NULL, =
NULL);<br>+ =C2=A0 =C2=A0ptp_sock_fd6 =3D open_socket(IPADDR_INET6, ptp_por=
t, 0, NULL, NULL);<br>=C2=A0 =C2=A0 =C2=A0ptp_message =3D MallocNew(PTP_Ntp=
Message);<br>=C2=A0 =C2=A0}<br>=C2=A0}<br>@@ -323,7 +330,7 @@ NIO_OpenServe=
rSocket(NTP_Remote_Address *remote_addr)<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0if (=
permanent_server_sockets)<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return serve=
r_sock_fd4;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0if (server_sock_fd4 =3D=3D INVALI=
D_SOCK_FD)<br>- =C2=A0 =C2=A0 =C2=A0 =C2=A0server_sock_fd4 =3D open_socket(=
IPADDR_INET4, CNF_GetNTPPort(), 0, NULL);<br>+ =C2=A0 =C2=A0 =C2=A0 =C2=A0s=
erver_sock_fd4 =3D open_socket(IPADDR_INET4, CNF_GetNTPPort(), 0, NULL, &qu=
ot;ntp4&quot;);<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0if (server_sock_fd4 !=3D INVA=
LID_SOCK_FD)<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0server_sock_ref4++;<br>=
=C2=A0 =C2=A0 =C2=A0 =C2=A0return server_sock_fd4;<br>@@ -333,7 +340,7 @@ N=
IO_OpenServerSocket(NTP_Remote_Address *remote_addr)<br>=C2=A0 =C2=A0 =C2=
=A0 =C2=A0if (permanent_server_sockets)<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=
=A0return server_sock_fd6;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0if (server_sock_fd=
6 =3D=3D INVALID_SOCK_FD)<br>- =C2=A0 =C2=A0 =C2=A0 =C2=A0server_sock_fd6 =
=3D open_socket(IPADDR_INET6, CNF_GetNTPPort(), 0, NULL);<br>+ =C2=A0 =C2=
=A0 =C2=A0 =C2=A0server_sock_fd6 =3D open_socket(IPADDR_INET6, CNF_GetNTPPo=
rt(), 0, NULL, &quot;ntp6&quot;);<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0if (server_=
sock_fd6 !=3D INVALID_SOCK_FD)<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0server_=
sock_ref6++;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0return server_sock_fd6;<br>diff =
--git a/nts_ke_server.c b/nts_ke_server.c<br>index 5e25c50..fcfceb5 100644<=
br>--- a/nts_ke_server.c<br>+++ b/nts_ke_server.c<br>@@ -293,7 +293,7 @@ ac=
cept_connection(int listening_fd, int event, void *arg)<br>=C2=A0/* =3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D */<br=
>=C2=A0<br>=C2=A0static int<br>-open_socket(int family)<br>+open_socket(int=
 family, char *name)<br>=C2=A0{<br>=C2=A0 =C2=A0IPSockAddr local_addr;<br>=
=C2=A0 =C2=A0int backlog, sock_fd;<br>@@ -306,19 +306,26 @@ open_socket(int=
 family)<br>=C2=A0 =C2=A0local_addr.port =3D CNF_GetNtsServerPort();<br>=C2=
=A0 =C2=A0iface =3D CNF_GetBindNtpInterface();<br>=C2=A0<br>- =C2=A0sock_fd=
 =3D SCK_OpenTcpSocket(NULL, &amp;local_addr, iface, 0);<br>- =C2=A0if (soc=
k_fd &lt; 0) {<br>- =C2=A0 =C2=A0LOG(LOGS_ERR, &quot;Could not open NTS-KE =
socket on %s&quot;, UTI_IPSockAddrToString(&amp;local_addr));<br>- =C2=A0 =
=C2=A0return INVALID_SOCK_FD;<br>- =C2=A0}<br>+ =C2=A0/* Attempt to retriev=
e socket file descriptor from service manager */<br>+ =C2=A0sock_fd =3D SCK=
_GetSystemdSocket(name, SOCK_STREAM, &amp;local_addr, iface, 0);<br>+<br>+ =
=C2=A0if (sock_fd &gt; 0) {<br>+ =C2=A0 =C2=A0LOG(LOGS_INFO, &quot;Using NT=
S-KE socket from systemd : fd=3D%d, name=3D%s&quot;, sock_fd, name);<br>+ =
=C2=A0} else {<br>+ =C2=A0 =C2=A0sock_fd =3D SCK_OpenTcpSocket(NULL, &amp;l=
ocal_addr, iface, 0);<br>+ =C2=A0 =C2=A0if (sock_fd &lt; 0) {<br>+ =C2=A0 =
=C2=A0 =C2=A0LOG(LOGS_ERR, &quot;Could not open NTS-KE socket on %s&quot;, =
UTI_IPSockAddrToString(&amp;local_addr));<br>+ =C2=A0 =C2=A0 =C2=A0return I=
NVALID_SOCK_FD;<br>+ =C2=A0 =C2=A0}<br>=C2=A0<br>- =C2=A0/* Set the maximum=
 number of waiting connections on the socket to the maximum<br>- =C2=A0 =C2=
=A0 number of concurrent sessions */<br>- =C2=A0backlog =3D MAX(CNF_GetNtsS=
erverProcesses(), 1) * CNF_GetNtsServerConnections();<br>+ =C2=A0 =C2=A0/* =
Set the maximum number of waiting connections on the socket to the maximum<=
br>+ =C2=A0 =C2=A0 =C2=A0 number of concurrent sessions */<br>+ =C2=A0 =C2=
=A0backlog =3D MAX(CNF_GetNtsServerProcesses(), 1) * CNF_GetNtsServerConnec=
tions();<br>=C2=A0<br>- =C2=A0if (!SCK_ListenOnSocket(sock_fd, backlog)) {<=
br>- =C2=A0 =C2=A0SCK_CloseSocket(sock_fd);<br>- =C2=A0 =C2=A0return INVALI=
D_SOCK_FD;<br>+ =C2=A0 =C2=A0if (!SCK_ListenOnSocket(sock_fd, backlog)) {<b=
r>+ =C2=A0 =C2=A0 =C2=A0SCK_CloseSocket(sock_fd);<br>+ =C2=A0 =C2=A0 =C2=A0=
return INVALID_SOCK_FD;<br>+ =C2=A0 =C2=A0}<br>=C2=A0 =C2=A0}<br>=C2=A0<br>=
=C2=A0 =C2=A0SCH_AddFileHandler(sock_fd, SCH_FILE_INPUT, accept_connection,=
 NULL);<br>@@ -811,8 +818,8 @@ NKS_Initialise(void)<br>=C2=A0 =C2=A0current=
_server_key =3D MAX_SERVER_KEYS - 1;<br>=C2=A0<br>=C2=A0 =C2=A0if (!is_help=
er) {<br>- =C2=A0 =C2=A0server_sock_fd4 =3D open_socket(IPADDR_INET4);<br>-=
 =C2=A0 =C2=A0server_sock_fd6 =3D open_socket(IPADDR_INET6);<br>+ =C2=A0 =
=C2=A0server_sock_fd4 =3D open_socket(IPADDR_INET4, &quot;nts4&quot;);<br>+=
 =C2=A0 =C2=A0server_sock_fd6 =3D open_socket(IPADDR_INET6, &quot;nts6&quot=
;);<br>=C2=A0<br>=C2=A0 =C2=A0 =C2=A0key_rotation_interval =3D MAX(CNF_GetN=
tsRotate(), 0);<br>=C2=A0<br>diff --git a/socket.c b/socket.c<br>index aa06=
0a8..ebfe596 100644<br>--- a/socket.c<br>+++ b/socket.c<br>@@ -214,7 +214,7=
 @@ set_socket_flags(int sock_fd, int flags)<br>=C2=A0 =C2=A0/* Close the s=
ocket automatically on exec */<br>=C2=A0 =C2=A0if (<br>=C2=A0#ifdef SOCK_CL=
OEXEC<br>- =C2=A0 =C2=A0 =C2=A0(supported_socket_flags &amp; SOCK_CLOEXEC) =
=3D=3D 0 &amp;&amp;<br>+ =C2=A0 =C2=A0 =C2=A0(flags &amp; SCK_FLAG_SYSTEMD =
|| (supported_socket_flags &amp; SOCK_CLOEXEC) =3D=3D 0) &amp;&amp;<br>=C2=
=A0#endif<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0!UTI_FdSetCloexec(sock_fd))<br>=C2=
=A0 =C2=A0 =C2=A0return 0;<br>@@ -222,7 +222,7 @@ set_socket_flags(int sock=
_fd, int flags)<br>=C2=A0 =C2=A0/* Enable non-blocking mode */<br>=C2=A0 =
=C2=A0if ((flags &amp; SCK_FLAG_BLOCK) =3D=3D 0 &amp;&amp;<br>=C2=A0#ifdef =
SOCK_NONBLOCK<br>- =C2=A0 =C2=A0 =C2=A0(supported_socket_flags &amp; SOCK_N=
ONBLOCK) =3D=3D 0 &amp;&amp;<br>+ =C2=A0 =C2=A0 =C2=A0(flags &amp; SCK_FLAG=
_SYSTEMD || (supported_socket_flags &amp; SOCK_NONBLOCK) =3D=3D 0) &amp;&am=
p;<br>=C2=A0#endif<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0!set_socket_nonblock(sock_=
fd))<br>=C2=A0 =C2=A0 =C2=A0return 0;<br>@@ -296,8 +296,15 @@ set_ip_option=
s(int sock_fd, int family, int flags)<br>=C2=A0{<br>=C2=A0#if defined(FEAT_=
IPV6) &amp;&amp; defined(IPV6_V6ONLY)<br>=C2=A0 =C2=A0/* Receive only IPv6 =
packets on an IPv6 socket */<br>- =C2=A0if (family =3D=3D IPADDR_INET6 &amp=
;&amp; !SCK_SetIntOption(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, 1))<br>- =C2=
=A0 =C2=A0return 0;<br>+ =C2=A0if (family =3D=3D IPADDR_INET6) {<br>+ =C2=
=A0 =C2=A0/* Only check if the option is set for systemd sockets as we cann=
ot set<br>+ =C2=A0 =C2=A0 * IPV6_V6ONLY when the socket is already bound. *=
/<br>+ =C2=A0 =C2=A0int opt;<br>+ =C2=A0 =C2=A0if (flags &amp; SCK_FLAG_SYS=
TEMD &amp;&amp; (!SCK_GetIntOption(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, &amp=
;opt) || opt !=3D 1))<br>+ =C2=A0 =C2=A0 =C2=A0return 0;<br>+ =C2=A0 =C2=A0=
else if (!SCK_SetIntOption(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, 1))<br>+ =C2=
=A0 =C2=A0 =C2=A0return 0;<br>+ =C2=A0}<br>=C2=A0#endif<br>=C2=A0<br>=C2=A0=
 =C2=A0/* Provide destination address of received packets if requested */<b=
r>@@ -361,12 +368,8 @@ bind_device(int sock_fd, const char *iface)<br>=C2=
=A0/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D */<br>=C2=A0<br>=C2=A0static int<br>-bind_ip_address(int sock_fd, IP=
SockAddr *addr, int flags)<br>+set_bind_options(int sock_fd, IPSockAddr *ad=
dr)<br>=C2=A0{<br>- =C2=A0union sockaddr_all saddr;<br>- =C2=A0socklen_t sa=
ddr_len;<br>- =C2=A0int s;<br>-<br>=C2=A0 =C2=A0/* Make the socket capable =
of re-using an old address if binding to a specific port */<br>=C2=A0 =C2=
=A0if (addr-&gt;port &gt; 0 &amp;&amp; !SCK_SetIntOption(sock_fd, SOL_SOCKE=
T, SO_REUSEADDR, 1))<br>=C2=A0 =C2=A0 =C2=A0;<br>@@ -385,6 +388,71 @@ bind_=
ip_address(int sock_fd, IPSockAddr *addr, int flags)<br>=C2=A0 =C2=A0 =C2=
=A0;<br>=C2=A0#endif<br>=C2=A0<br>+ =C2=A0return 1;<br>+}<br>+<br>+/* =3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D */=
<br>+<br>+static int<br>+check_ip_bind_address(int fd, int type, IPSockAddr=
 *addr)<br>+{<br>+ =C2=A0int opt;<br>+ =C2=A0socklen_t l;<br>+ =C2=A0union =
sockaddr_all saddr;<br>+ =C2=A0IPSockAddr ip_sa;<br>+<br>+ =C2=A0/* Check t=
hat chrony configured address is valid */<br>+ =C2=A0if (!addr || addr-&gt;=
port =3D=3D 0 ||<br>+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(addr-&gt;ip_addr.f=
amily !=3D IPADDR_INET4 &amp;&amp; addr-&gt;ip_addr.family !=3D IPADDR_INET=
6) ||<br>+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(type !=3D SOCK_STREAM &amp;&a=
mp; type !=3D SOCK_DGRAM)) {<br>+ =C2=A0 =C2=A0 =C2=A0DEBUG_LOG(&quot;inval=
id bind address&quot;);<br>+ =C2=A0 =C2=A0 =C2=A0return 0;<br>+ =C2=A0}<br>=
+<br>+ =C2=A0/* Check type */<br>+ =C2=A0l =3D sizeof(opt);<br>+ =C2=A0if (=
getsockopt(fd, SOL_SOCKET, SO_TYPE, &amp;opt, &amp;l) &lt; 0 || l !=3D size=
of(opt) || opt !=3D type) {<br>+ =C2=A0 =C2=A0DEBUG_LOG(&quot;type mismatch=
 : fd=3D%d %d !=3D %d&quot;, fd, type, opt);<br>+ =C2=A0 =C2=A0return 0;<br=
>+ =C2=A0}<br>+<br>+ =C2=A0if (type =3D=3D SOCK_STREAM) {<br>+ =C2=A0 =C2=
=A0/* Check if STREAM socket is listening */<br>+ =C2=A0 =C2=A0l =3D sizeof=
(opt);<br>+ =C2=A0 =C2=A0if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &amp=
;opt, &amp;l) &lt; 0 || l !=3D sizeof(opt) || opt =3D=3D 0) {<br>+ =C2=A0 =
=C2=A0 =C2=A0DEBUG_LOG(&quot;STREAM socket not listening&quot;);<br>+ =C2=
=A0 =C2=A0 =C2=A0return 0;<br>+ =C2=A0 =C2=A0}<br>+ =C2=A0}<br>+<br>+ =C2=
=A0l =3D sizeof(saddr);<br>+ =C2=A0if (getsockname(fd, &amp;<a href=3D"http=
://saddr.sa">saddr.sa</a>, &amp;l) &lt; 0 || l &lt; sizeof(sa_family_t)) {<=
br>+ =C2=A0 =C2=A0DEBUG_LOG(&quot;failed to get socket address from fd&quot=
;);<br>+ =C2=A0 =C2=A0return 0;<br>+ =C2=A0}<br>+ =C2=A0SCK_SockaddrToIPSoc=
kAddr(&amp;<a href=3D"http://saddr.sa";>saddr.sa</a>, l, &amp;ip_sa);<br>+<b=
r>+ =C2=A0if (UTI_CompareIPs(&amp;ip_sa.ip_addr, &amp;addr-&gt;ip_addr, NUL=
L) !=3D 0 || ip_sa.port !=3D addr-&gt;port) {<br>+ =C2=A0 =C2=A0DEBUG_LOG(&=
quot;address mismatch : %s !=3D %s&quot;, UTI_IPSockAddrToString(&amp;ip_sa=
), UTI_IPSockAddrToString(addr));<br>+ =C2=A0 =C2=A0return 0;<br>+ =C2=A0}<=
br>+<br>+ =C2=A0return 1;<br>+}<br>+<br>+<br>+/* =3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D */<br>+<br>+static int<=
br>+bind_ip_address(int sock_fd, IPSockAddr *addr, int flags)<br>+{<br>+ =
=C2=A0union sockaddr_all saddr;<br>+ =C2=A0socklen_t saddr_len;<br>+ =C2=A0=
int s;<br>+<br>+ =C2=A0if (!set_bind_options(sock_fd, addr))<br>+ =C2=A0 =
=C2=A0return 0;<br>+<br>=C2=A0 =C2=A0saddr_len =3D SCK_IPSockAddrToSockaddr=
(addr, (struct sockaddr *)&amp;saddr, sizeof (saddr));<br>=C2=A0 =C2=A0if (=
saddr_len =3D=3D 0)<br>=C2=A0 =C2=A0 =C2=A0return 0;<br>@@ -426,6 +494,41 @=
@ connect_ip_address(int sock_fd, IPSockAddr *addr)<br>=C2=A0<br>=C2=A0/* =
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
 */<br>=C2=A0<br>+static int<br>+get_systemd_socket(const char *name)<br>+{=
<br>+ =C2=A0char *s;<br>+ =C2=A0int i, l, n;<br>+<br>+ =C2=A0if (!name)<br>=
+ =C2=A0 =C2=A0return INVALID_SOCK_FD;<br>+<br>+ =C2=A0n =3D SCK_GetSystemd=
ListenFdCount();<br>+ =C2=A0s =3D getenv(&quot;LISTEN_FDNAMES&quot;);<br>+<=
br>+ =C2=A0if (n &gt; 0 &amp;&amp; s) {<br>+ =C2=A0 =C2=A0for (i =3D 0; i &=
lt; n; i++) {<br>+ =C2=A0 =C2=A0 =C2=A0/* Find length of token */<br>+ =C2=
=A0 =C2=A0 =C2=A0l =3D 0;<br>+ =C2=A0 =C2=A0 =C2=A0while (s[l] !=3D &#39;:&=
#39; &amp;&amp; s[l] !=3D &#39;\0&#39;)<br>+ =C2=A0 =C2=A0 =C2=A0 =C2=A0l++=
;<br>+<br>+ =C2=A0 =C2=A0 =C2=A0/* Compare (not always null-terminated) tok=
en to name */<br>+ =C2=A0 =C2=A0 =C2=A0if (strlen(name) =3D=3D l &amp;&amp;=
 !strncmp(name, s, l))<br>+ =C2=A0 =C2=A0 =C2=A0 =C2=A0return SD_LISTEN_FDS=
_START + i;<br>+<br>+ =C2=A0 =C2=A0 =C2=A0if (s[l] =3D=3D &#39;\0&#39;)<br>=
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0break;<br>+<br>+ =C2=A0 =C2=A0 =C2=A0/* Move t=
o next token */<br>+ =C2=A0 =C2=A0 =C2=A0s+=3Dl+1;<br>+ =C2=A0 =C2=A0}<br>+=
 =C2=A0}<br>+ =C2=A0return INVALID_SOCK_FD;<br>+}<br>+<br>+/* =3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D */<br>+<br>=
=C2=A0static int<br>=C2=A0open_ip_socket(IPSockAddr *remote_addr, IPSockAdd=
r *local_addr, const char *iface,<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=
=A0 =C2=A0 =C2=A0 int type, int flags)<br>@@ -1353,6 +1456,65 @@ SCK_OpenUn=
ixSocketPair(int flags, int *other_fd)<br>=C2=A0<br>=C2=A0/* =3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D */<br>=C2=
=A0<br>+int SCK_GetSystemdListenFdCount(void)<br>+{<br>+ =C2=A0int n;<br>+ =
=C2=A0char *s, *ptr;<br>+<br>+ =C2=A0s =3D getenv(&quot;LISTEN_FDS&quot;);<=
br>+ =C2=A0if (!s)<br>+ =C2=A0 =C2=A0 =C2=A0return 0;<br>+ =C2=A0errno =3D =
0;<br>+ =C2=A0n =3D strtol(s, &amp;ptr, 10);<br>+ =C2=A0if (errno !=3D 0 ||=
 *ptr !=3D &#39;\0&#39;)<br>+ =C2=A0 =C2=A0 =C2=A0return 0;<br>+<br>+ =C2=
=A0return n;<br>+}<br>+<br>+/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D */<br>+<br>+int<br>+SCK_GetSystemdSocket(=
const char *name, int type, IPSockAddr *addr, const char *iface, int flags)=
<br>+{<br>+ =C2=A0int sock_fd;<br>+ =C2=A0flags |=3D SCK_FLAG_SYSTEMD;<br>+=
<br>+ =C2=A0sock_fd =3D get_systemd_socket(name);<br>+ =C2=A0if (sock_fd &l=
t; 0)<br>+ =C2=A0 =C2=A0return INVALID_SOCK_FD;<br>+ =C2=A0DEBUG_LOG(&quot;=
Found systemd socket : fd=3D%d, name=3D%s&quot;, sock_fd, name);<br>+<br>+ =
=C2=A0if (!set_socket_flags(sock_fd, flags))<br>+ =C2=A0 =C2=A0goto error;<=
br>+<br>+ =C2=A0if (!set_socket_options(sock_fd, flags))<br>+ =C2=A0 =C2=A0=
goto error;<br>+<br>+ =C2=A0if (!set_ip_options(sock_fd, addr-&gt;ip_addr.f=
amily, flags))<br>+ =C2=A0 =C2=A0goto error;<br>+<br>+ =C2=A0/* Bind device=
 if interface provided */<br>+ =C2=A0if (iface &amp;&amp; !bind_device(sock=
_fd, iface))<br>+ =C2=A0 =C2=A0goto error;<br>+<br>+ =C2=A0if (!set_bind_op=
tions(sock_fd, addr))<br>+ =C2=A0 =C2=A0goto error;<br>+<br>+ =C2=A0/* Chec=
k that the socket is bound to the correct address */<br>+ =C2=A0if (!check_=
ip_bind_address(sock_fd, type, addr))<br>+ =C2=A0 =C2=A0goto error;<br>+<br=
>+ =C2=A0return sock_fd;<br>+<br>+error:<br>+ =C2=A0SCK_CloseSocket(sock_fd=
);<br>+ =C2=A0LOG(LOGS_ERR, &quot;Failed to use systemd socket : fd=3D%d, n=
ame=3D%s&quot;, sock_fd, name);<br>+ =C2=A0return INVALID_SOCK_FD;<br>+}<br=
>+<br>+/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D */<br>+<br>=C2=A0int<br>=C2=A0SCK_SetIntOption(int sock_fd, in=
t level, int name, int value)<br>=C2=A0{<br>diff --git a/socket.h b/socket.=
h<br>index cdbae2d..5e350bd 100644<br>--- a/socket.h<br>+++ b/socket.h<br>@=
@ -36,11 +36,15 @@<br>=C2=A0#define SCK_FLAG_RX_DEST_ADDR 4<br>=C2=A0#defin=
e SCK_FLAG_ALL_PERMISSIONS 8<br>=C2=A0#define SCK_FLAG_PRIV_BIND 16<br>+#de=
fine SCK_FLAG_SYSTEMD 32<br>=C2=A0<br>=C2=A0/* Flags for receiving and send=
ing messages */<br>=C2=A0#define SCK_FLAG_MSG_ERRQUEUE 1<br>=C2=A0#define S=
CK_FLAG_MSG_DESCRIPTOR 2<br>=C2=A0<br>+/* From sd-daemon.h */<br>+#define S=
D_LISTEN_FDS_START 3<br>+<br>=C2=A0typedef enum {<br>=C2=A0 =C2=A0SCK_ADDR_=
UNSPEC =3D 0,<br>=C2=A0 =C2=A0SCK_ADDR_IP,<br>@@ -106,6 +110,13 @@ extern i=
nt SCK_OpenUnixStreamSocket(const char *remote_addr, const char *local_a<br=
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=
=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0int flags);<br>=
=C2=A0extern int SCK_OpenUnixSocketPair(int flags, int *other_fd);<br>=C2=
=A0<br>+/* Get number of socket file descriptors from service manager */<br=
>+extern int SCK_GetSystemdListenFdCount(void);<br>+<br>+/* Get and configu=
re socket file descriptor from service manager by name */<br>+extern int SC=
K_GetSystemdSocket(const char *name, int type, IPSockAddr *local_addr,<br>+=
 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=
=A0 =C2=A0 =C2=A0 =C2=A0 const char *iface, int flags);<br>+<br>=C2=A0/* Se=
t and get a socket option of int size */<br>=C2=A0extern int SCK_SetIntOpti=
on(int sock_fd, int level, int name, int value);<br>=C2=A0extern int SCK_Ge=
tIntOption(int sock_fd, int level, int name, int *value);<br>diff --git a/t=
est/system/011-systemd b/test/system/011-systemd<br>new file mode 100755<br=
>index 0000000..9a79163<br>--- /dev/null<br>+++ b/test/system/011-systemd<b=
r>@@ -0,0 +1,129 @@<br>+#!/usr/bin/env bash<br>+<br>+. ./test.common<br>+<b=
r>+check_chronyd_features NTS || test_skip &quot;NTS support disabled&quot;=
<br>+has_ipv6=3D$(check_chronyd_features IPV6 &amp;&amp; ping6 -c 1 ::1 &gt=
; /dev/null 2&gt;&amp;1 &amp;&amp; echo 1 || echo 0)<br>+certtool --help &a=
mp;&gt; /dev/null || test_skip &quot;certtool missing&quot;<br>+systemd-soc=
ket-activate -h &amp;&gt; /dev/null || test_skip &quot;systemd-socket-activ=
ate missing&quot;<br>+<br>+test_start &quot;systemd socket activation (ipv6=
=3D$has_ipv6)&quot;<br>+<br>+cat &gt; $TEST_DIR/cert.cfg &lt;&lt;EOF<br>+cn=
 =3D &quot;chrony-nts-test&quot;<br>+dns_name =3D &quot;chrony-nts-test&quo=
t;<br>+ip_address =3D &quot;$server&quot;<br>+serial =3D 001<br>+activation=
_date =3D &quot;$[$(date &#39;+%Y&#39;) - 1]-01-01 00:00:00 UTC&quot;<br>+e=
xpiration_date =3D &quot;$[$(date &#39;+%Y&#39;) + 2]-01-01 00:00:00 UTC&qu=
ot;<br>+signing_key<br>+encryption_key<br>+EOF<br>+<br>+certtool --generate=
-privkey --key-type=3Ded25519 --outfile $TEST_DIR/server.key \<br>+	&amp;&g=
t; $TEST_DIR/certtool.log<br>+certtool --generate-self-signed --load-privke=
y $TEST_DIR/server.key \<br>+	--template $TEST_DIR/cert.cfg --outfile $TEST=
_DIR/server.crt &amp;&gt;&gt; $TEST_DIR/certtool.log<br>+chown $user $TEST_=
DIR/server.*<br>+<br>+ntpport=3D$(get_free_port)<br>+ntsport=3D$(get_free_p=
ort)<br>+<br>+server_options=3D&quot;port $ntpport nts ntsport $ntsport&quo=
t;<br>+extra_chronyd_directives=3D&quot;<br>+port $ntpport<br>+ntsport $nts=
port<br>+ntsserverkey $TEST_DIR/server.key<br>+ntsservercert $TEST_DIR/serv=
er.crt<br>+ntstrustedcerts $TEST_DIR/server.crt<br>+ntsdumpdir $TEST_LIBDIR=
<br>+ntsprocesses 3&quot;<br>+<br>+# Test UDP sockets for NTP (unfortunatel=
y systemd-socket-activate doesn&#39;t<br>+# support both datagram and strea=
m sockets in the same invocation).<br>+if [ &quot;$has_ipv6&quot; =3D &quot=
;1&quot; ]; then<br>+CHRONYD_WRAPPER=3D&quot;systemd-socket-activate \<br>+=
	--datagram \<br>+	--listen 127.0.0.1:$ntpport --fdname=3Dntp4 \<br>+	--lis=
ten [::1]:$ntpport --fdname=3Dntp6&quot;<br>+else<br>+CHRONYD_WRAPPER=3D&qu=
ot;systemd-socket-activate \<br>+	--datagram \<br>+	--listen 127.0.0.1:$ntp=
port --fdname=3Dntp4&quot;<br>+fi<br>+<br>+# Hack to trigger systemd-socket=
-activate to activate the service.=C2=A0 Normally,<br>+# chronyd.service wo=
uld be configured with the WantedBy=3D directive so it starts<br>+# without=
 waiting for socket activation.<br>+# (<a href=3D"https://0pointer.de/blog/=
projects/socket-activation.html">https://0pointer.de/blog/projects/socket-a=
ctivation.html</a>).<br>+(sleep 1 &amp;&amp; echo &quot;wake up&quot; &gt; =
/dev/udp/<a href=3D"http://127.0.0.1/$ntpport";>127.0.0.1/$ntpport</a>) &amp=
;<br>+start_chronyd || test_fail<br>+wait_for_sync || test_fail<br>+<br>+ru=
n_chronyd_client &quot;server 127.0.0.1 port $ntpport iburst maxsamples 1&q=
uot; || test_fail<br>+check_chronyd_client_output || test_fail<br>+<br>+if =
[ &quot;$has_ipv6&quot; =3D &quot;1&quot; ]; then<br>+run_chronyd_client &q=
uot;server ::1 port $ntpport iburst maxsamples 1&quot; || test_fail<br>+che=
ck_chronyd_client_output || test_fail<br>+fi<br>+<br>+stop_chronyd || test_=
fail<br>+check_chronyd_message_count &quot;Using NTP socket from systemd : =
fd=3D3, name=3Dntp4&quot; 1 1 || test_fail<br>+if [ &quot;$has_ipv6&quot; =
=3D &quot;1&quot; ]; then<br>+check_chronyd_message_count &quot;Using NTP s=
ocket from systemd : fd=3D4, name=3Dntp6&quot; 1 1 || test_fail<br>+fi<br>+=
check_chronyd_messages || test_fail<br>+check_chronyd_files || test_fail<br=
>+<br>+# Test TCP sockets for NTS-KE<br>+if [ &quot;$has_ipv6&quot; =3D &qu=
ot;1&quot; ]; then<br>+CHRONYD_WRAPPER=3D&quot;systemd-socket-activate \<br=
>+	--listen 127.0.0.1:$ntsport --fdname=3Dnts4 \<br>+	--listen [::1]:$ntspo=
rt --fdname=3Dnts6&quot;<br>+else<br>+CHRONYD_WRAPPER=3D&quot;systemd-socke=
t-activate \<br>+	--listen 127.0.0.1:$ntsport --fdname=3Dnts4&quot;<br>+fi<=
br>+# Same hack as above to trigger socket activation.<br>+(sleep 1 &amp;&a=
mp; echo &quot;wake up&quot; &gt; /dev/tcp/<a href=3D"http://127.0.0.1/$nts=
port">127.0.0.1/$ntsport</a>) &amp;<br>+start_chronyd || test_fail<br>+wait=
_for_sync || test_fail<br>+<br>+run_chronyd_client &quot;server 127.0.0.1 p=
ort $ntpport nts ntsport $ntsport iburst maxsamples 1&quot; &quot;ntstruste=
dcerts $TEST_DIR/server.crt&quot; || test_fail<br>+check_chronyd_client_out=
put || test_fail<br>+<br>+if [ &quot;$has_ipv6&quot; =3D &quot;1&quot; ]; t=
hen<br>+run_chronyd_client &quot;server ::1 port $ntpport iburst maxsamples=
 1&quot; || test_fail<br>+check_chronyd_client_output || test_fail<br>+fi<b=
r>+<br>+stop_chronyd || test_fail<br>+check_chronyd_message_count &#39;Usin=
g NTS-KE socket from systemd : fd=3D3, name=3Dnts4&#39; 1 1 || test_fail<br=
>+if [ &quot;$has_ipv6&quot; =3D &quot;1&quot; ]; then<br>+check_chronyd_me=
ssage_count &#39;Using NTS-KE socket from systemd : fd=3D4, name=3Dnts6&#39=
; 1 1 || test_fail<br>+fi<br>+check_chronyd_messages || test_fail<br>+check=
_chronyd_files || test_fail<br>+<br>+# Test invalid socket configs<br>+# - =
nts4 as DGRAM socket should be ignored<br>+# - foo should be ignored<br>+CH=
RONYD_WRAPPER=3D&quot;systemd-socket-activate \<br>+	--datagram \<br>+	--li=
sten 127.0.0.1:$ntpport --fdname=3Dfoo \<br>+	--listen 127.0.0.1:$ntsport -=
-fdname=3Dnts4&quot;<br>+# Same hack as above to trigger socket activation.=
<br>+(sleep 1 &amp;&amp; echo &quot;wake up&quot; &gt; /dev/udp/<a href=3D"=
http://127.0.0.1/$ntsport";>127.0.0.1/$ntsport</a>) &amp;<br>+start_chronyd =
|| test_fail<br>+wait_for_sync || test_fail<br>+<br>+run_chronyd_client &qu=
ot;server 127.0.0.1 port $ntpport nts ntsport $ntsport iburst maxsamples 1&=
quot; &quot;ntstrustedcerts $TEST_DIR/server.crt&quot; || test_fail<br>+che=
ck_chronyd_client_output || test_fail<br>+<br>+stop_chronyd || test_fail<br=
>+check_chronyd_message_count &#39;Failed to use systemd socket : fd=3D4, n=
ame=3Dnts4&#39; 1 1 || test_fail<br>+check_chronyd_messages || test_fail<br=
>+check_chronyd_files || test_fail<br>+<br>+test_pass<br>diff --git a/test/=
system/test.common b/test/system/test.common<br>index aa48ac6..381eb3c 1006=
44<br>--- a/test/system/test.common<br>+++ b/test/system/test.common<br>@@ =
-349,6 +349,22 @@ check_chronyd_files() {<br>=C2=A0		test_ok || test_bad<br=
>=C2=A0}<br>=C2=A0<br>+# Run chronyd in client mode to retrieve and print o=
ut time<br>+run_chronyd_client() {<br>+	local args=3D( &quot;$@&quot; )<br>=
+	test_message 1 0 &quot;running chronyd client&quot;<br>+<br>+	&quot;$chro=
nyd&quot; -Q &quot;${args[@]}&quot; &gt; &quot;$TEST_DIR/chronyd-client.out=
&quot; 2&gt;&amp;1 &amp;&amp; test_ok || test_error<br>+}<br>+<br>+check_ch=
ronyd_client_output() {<br>+	local pattern=3D$1<br>+<br>+	test_message 1 0 =
&quot;checking chronyd client output&quot;<br>+	grep -q &quot;System clock =
wrong by .* seconds (ignored)&quot; &quot;$TEST_DIR/chronyd-client.out&quot=
; &amp;&amp; \<br>+	 =C2=A0 =C2=A0 =C2=A0 =C2=A0test_ok || test_error<br>+}=
<br>+<br>=C2=A0# Run a chronyc command<br>=C2=A0run_chronyc() {<br>=C2=A0	l=
ocal host=3D$chronyc_host options=3D&quot;-n -m&quot;<br>diff --git a/test/=
unit/socket.c b/test/unit/socket.c<br>new file mode 100644<br>index 0000000=
...007984b<br>--- /dev/null<br>+++ b/test/unit/socket.c<br>@@ -0,0 +1,60 @@<=
br>+/*<br>+ ***************************************************************=
*******<br>+ * Copyright (C) Miroslav Lichvar =C2=A02023<br>+ * <br>+ * Thi=
s program is free software; you can redistribute it and/or modify<br>+ * it=
 under the terms of version 2 of the GNU General Public License as<br>+ * p=
ublished by the Free Software Foundation.<br>+ * <br>+ * This program is di=
stributed in the hope that it will be useful, but<br>+ * WITHOUT ANY WARRAN=
TY; without even the implied warranty of<br>+ * MERCHANTABILITY or FITNESS =
FOR A PARTICULAR PURPOSE.=C2=A0 See the GNU<br>+ * General Public License f=
or more details.<br>+ * <br>+ * You should have received a copy of the GNU =
General Public License along<br>+ * with this program; if not, write to the=
 Free Software Foundation, Inc.,<br>+ * 51 Franklin Street, Fifth Floor, Bo=
ston, MA =C2=A002110-1301, USA.<br>+ * <br>+ ******************************=
****************************************<br>+ */<br>+<br>+#include &lt;sock=
et.c&gt;<br>+#include &quot;test.h&quot;<br>+<br>+void<br>+test_unit(void)<=
br>+{<br>+ =C2=A0/* Test systemd environment variable parsing */<br>+<br>+ =
=C2=A0/* Normal case */<br>+ =C2=A0putenv(&quot;LISTEN_FDS=3D2&quot;);<br>+=
 =C2=A0putenv(&quot;LISTEN_FDNAMES=3Dfoo:bar&quot;);<br>+ =C2=A0TEST_CHECK(=
get_systemd_socket(&quot;foo&quot;) =3D=3D SD_LISTEN_FDS_START);<br>+ =C2=
=A0TEST_CHECK(get_systemd_socket(&quot;bar&quot;) =3D=3D SD_LISTEN_FDS_STAR=
T + 1);<br>+ =C2=A0TEST_CHECK(get_systemd_socket(&quot;baz&quot;) &lt; 0);<=
br>+<br>+ =C2=A0/* Only attempt to parse up to LISTEN_FDS entries */<br>+ =
=C2=A0putenv(&quot;LISTEN_FDS=3D1&quot;);<br>+ =C2=A0putenv(&quot;LISTEN_FD=
NAMES=3Dfoo:bar&quot;);<br>+ =C2=A0TEST_CHECK(get_systemd_socket(&quot;bar&=
quot;) &lt; 0);<br>+<br>+ =C2=A0/* OK if a file descriptor name is empty */=
<br>+ =C2=A0putenv(&quot;LISTEN_FDS=3D2&quot;);<br>+ =C2=A0putenv(&quot;LIS=
TEN_FDNAMES=3D:bar&quot;);<br>+ =C2=A0TEST_CHECK(get_systemd_socket(&quot;&=
quot;) =3D=3D SD_LISTEN_FDS_START);<br>+<br>+ =C2=A0/* OK if LISTEN_FDS doe=
sn&#39;t match number of entries in LISTEN_FDNAMES */<br>+ =C2=A0putenv(&qu=
ot;LISTEN_FDS=3D8&quot;);<br>+ =C2=A0putenv(&quot;LISTEN_FDNAMES=3Dfoo&quot=
;);<br>+ =C2=A0TEST_CHECK(get_systemd_socket(&quot;foo&quot;) =3D=3D SD_LIS=
TEN_FDS_START);<br>+<br>+ =C2=A0/* Make sure there are no trailing characte=
rs */<br>+ =C2=A0putenv(&quot;LISTEN_FDS=3D1a&quot;);<br>+ =C2=A0putenv(&qu=
ot;LISTEN_FDNAMES=3Dfoo&quot;);<br>+ =C2=A0TEST_CHECK(get_systemd_socket(&q=
uot;foo&quot;) &lt; 0);<br>+<br>+ =C2=A0/* Handle invalid LISTEN_FDS */<br>=
+ =C2=A0putenv(&quot;LISTEN_FDS=3Da1&quot;);<br>+ =C2=A0putenv(&quot;LISTEN=
_FDNAMES=3Dfoo&quot;);<br>+ =C2=A0TEST_CHECK(get_systemd_socket(&quot;foo&q=
uot;) &lt; 0);<br>+}<br>-- <br>2.39.3 (Apple Git-145)<br></div><br clear=3D=
"all"><div><br></div><span class=3D"gmail_signature_prefix">-- </span><br><=
div dir=3D"ltr" class=3D"gmail_signature" data-smartmail=3D"gmail_signature=
"><div dir=3D"ltr"><div><div dir=3D"ltr"><div><div dir=3D"ltr"><div><div di=
r=3D"ltr"><div><span style=3D"font-size:12.8px">Luke Valenta</span></div><d=
iv><span style=3D"font-size:12.8px">Systems Engineer - Research</span></div=
></div></div></div></div></div></div></div></div></div>

--00000000000091fca90608293f0d--

-- 
To unsubscribe email chrony-dev-request@xxxxxxxxxxxxxxxxxxxx with "unsubscribe" in the subject.
For help email chrony-dev-request@xxxxxxxxxxxxxxxxxxxx with "help" in the subject.
Trouble?  Email listmaster@xxxxxxxxxxxxxxxxxxxx.


Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/