From nobody Tue Jun 30 20:54:46 2026 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4gqb5k3pTDz6jx91 for ; Tue, 30 Jun 2026 20:55:54 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR1" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4gqb4Q2QzWz3Sj0 for ; Tue, 30 Jun 2026 20:54:46 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1782852886; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=NrEcSvVIfdgsNFNFtLMxmaZntJLEdi7iQdH0nYu2akc=; b=U4lgt/N0X6p2SfVGs4tRCeIfgCp0qGcoEL8tZrhQV2OoKaiwI/1p2wnWS67cv6a6J9ogg/ Ir/UL+++QZTsUxgQJ7wtQsVQNYq5Y5D90G/ObhlOWxLtOkyPl3Sg/fgIrSWIEVScTSgYMu iKtBmNjik6PaJdlkfIU+2J+U+8CI+FFj+xMu7ZcIvWbPHGEqRoSuhzIqM2/VQKYAFHoHpx p9reF/aYJqZvhvVNMiqdKE3mSe+e+9JGmLysMCggUeFQBbRJKPzjjkirHTxvpFDPqj2c1k 9guGzzpG584PR1PWfeL4TScy6aSvBrXd5TDyf+pBpSDGWEg/fAyolF87vEBIPg== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1782852886; a=rsa-sha256; cv=none; b=bl31fq5LJNlmQh2sk5t/2dkjGUGha/9KDB/2JfjgqA6k5Mpky0h2FsyKEXVbfjjTnkNT5p GHdNQtZvlcAGQWlXOK32xoFuYKPlErW4iZzp2O7uuXpmZDqF2r7wbhseXNroubg9pOtLhn cUvbHsgnjie6k0d2XZgaKeAN32HEq2f9w1BupcG8AxIW2Vig3pIZTXy2eMjGwJWWcq6BSx P9XFVCghKTXaLTN+hXmEnDP0A32cOLuBl2Pi52sCP75VenjuE9D1DoSRHskbxql8pc6lia dZzOWWC6mgkgM+g0uCe4YllrpVBOMHHO9UNUaLglrtpaxZ4nNpooZla8Xr74jg== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1782852886; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=NrEcSvVIfdgsNFNFtLMxmaZntJLEdi7iQdH0nYu2akc=; b=DNdafJoPpWGoXvvb4oUh7p3i//up0ZN5ZYlKsrQ23DdwuDqZYDtV0rwevcZbzcDHIA9KXp XbJ+TU/CzsBDXvDZLiUsQS4/ktyXM7t/YnizK5iIkmLxnTNvMUvqx5COVaDY0icjzhb8jT 9/9C0WPDlXiBqVxKBYY0xc28NCB7k2IWbZ+f7IyXfovW1olLL/dEz2ezbMvCAEKdIsVV07 Vc8N12s7eWlhf5bUT7ClrAuZ4OsDZ+EVoGIXMvrEfU4B/UNdxkebn4umHEOFeLfiAMGlbW rYFrxMqWW3FGWFbmF4Wu6vAYnpAAUuMGSGbRPHXQzLg8kmbmqz7o+cZYACNytA== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4gqb4Q1LGbz117g for ; Tue, 30 Jun 2026 20:54:46 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 34894 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Tue, 30 Jun 2026 20:54:46 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Mark Johnston Subject: git: 8a62a5f77b10 - main - syslogd: Handle connection errors when setting up forwarding sockets List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: markj X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 8a62a5f77b10ddfa593c4a3d7751cf9bac3bb1b3 Auto-Submitted: auto-generated Date: Tue, 30 Jun 2026 20:54:46 +0000 Message-Id: <6a442d16.34894.132c8ccb@gitrepo.freebsd.org> The branch main has been updated by markj: URL: https://cgit.FreeBSD.org/src/commit/?id=8a62a5f77b10ddfa593c4a3d7751cf9bac3bb1b3 commit 8a62a5f77b10ddfa593c4a3d7751cf9bac3bb1b3 Author: Mark Johnston AuthorDate: 2026-06-30 19:19:50 +0000 Commit: Mark Johnston CommitDate: 2026-06-30 20:15:17 +0000 syslogd: Handle connection errors when setting up forwarding sockets Since syslogd was converted to run in a Capsicum sandbox, it needs to explicitly connect() its forwarding sockets rather than using sendmsg(). At the time syslogd starts during boot, some of its forwarding destinations may not be routable, in which case connect() fails. Fix this by making connect() failures non-fatal, and use cap_net to lazily connect sockets once something actually tries logging to the destination. Add a regression test. Reported by: ae Reviewed by: ae Discussed with: jfree Fixes: 4ecbee2760f7 ("syslogd: Open forwarding socket descriptors") MFC after: 2 weeks Differential Revision: https://reviews.freebsd.org/D57394 --- usr.sbin/syslogd/syslogd.c | 134 +++++++++++++++++++++++++++------ usr.sbin/syslogd/syslogd_cap_config.c | 7 ++ usr.sbin/syslogd/tests/Makefile | 3 +- usr.sbin/syslogd/tests/syslogd_test.sh | 69 +++++++++++++++++ 4 files changed, 188 insertions(+), 25 deletions(-) diff --git a/usr.sbin/syslogd/syslogd.c b/usr.sbin/syslogd/syslogd.c index 70cb705c0cb7..19190124c8b4 100644 --- a/usr.sbin/syslogd/syslogd.c +++ b/usr.sbin/syslogd/syslogd.c @@ -1752,6 +1752,24 @@ iovlist_truncate(struct iovlist *il, size_t size) } #endif +static int +find_forw_fd(const struct sockaddr_storage *rss, + const struct sockaddr_storage *lss, struct filed *skip) +{ + struct filed *f; + + STAILQ_FOREACH(f, &fhead, next) { + if (f->f_type != F_FORW || f == skip) + continue; + for (size_t i = 0; i < f->f_num_addr_fds; ++i) { + if (memcmp(&f->f_addrs[i].raddr, rss, rss->ss_len) == 0 && + memcmp(&f->f_addrs[i].laddr, lss, lss->ss_len) == 0) + return (f->f_addr_fds[i]); + } + } + return (-1); +} + static void fprintlog_write(struct filed *f, struct iovlist *il, int flags) { @@ -1760,13 +1778,14 @@ fprintlog_write(struct filed *f, struct iovlist *il, int flags) ssize_t lsent; if (Debug) { - int domain, sockfd = f->f_addr_fds[0]; + int domain, port, sockfd = f->f_addr_fds[0]; socklen_t len = sizeof(domain); if (getsockopt(sockfd, SOL_SOCKET, SO_DOMAIN, &domain, &len) < 0) err(1, "getsockopt"); + port = -1; printf(" %s", f->f_hname); switch (domain) { #ifdef INET @@ -1776,8 +1795,10 @@ fprintlog_write(struct filed *f, struct iovlist *il, int flags) len = sizeof(sin); if (getpeername(sockfd, (struct sockaddr *)&sin, &len) < 0) - err(1, "getpeername"); - printf(":%d\n", ntohs(sin.sin_port)); + warn("getpeername"); + else + port = ntohs(sin.sin_port); + printf(":%d\n", port); break; } #endif @@ -1788,8 +1809,10 @@ fprintlog_write(struct filed *f, struct iovlist *il, int flags) len = sizeof(sin6); if (getpeername(sockfd, (struct sockaddr *)&sin6, &len) < 0) - err(1, "getpeername"); - printf(":%d\n", ntohs(sin6.sin6_port)); + warn("getpeername"); + else + port = ntohs(sin6.sin6_port); + printf(":%d\n", port); break; } #endif @@ -1803,23 +1826,76 @@ fprintlog_write(struct filed *f, struct iovlist *il, int flags) iovlist_truncate(il, MaxForwardLen); #endif + /* + * We have some constraints on message forwarding: + * - we want to send messages from an address to which syslogd + * is bound, + * - syslogd might start before the system's routes are + * configured, in which case connect() will fail, + * - there may be multiple logging rules which forward a message + * to a given address, i.e., sockets from different fileds + * may have the same tuple, + * - we don't want to use casper to forward messages for us, as + * that's a lot of overhead to add to each message. + * + * These constraints plus Capsicum's restrictions make this + * rather complicated. We handle the first constraint by + * calling bind() when setting up forwarding sockets. The + * second constraint is handled by allowing connect() to fail + * during setup; if the sendmsg() call below fails for that + * reason, we then use cap_connect() to connect it lazily. + * Finally, that connect() call may fail due to the third + * constraint, in which case we look for another matching socket + * and use that one instead. + */ lsent = 0; for (size_t i = 0; i < f->f_num_addr_fds; ++i) { struct msghdr msg = { .msg_iov = il->iov, .msg_iovlen = il->iovcnt, }; + int fd; - lsent = sendmsg(f->f_addr_fds[i], &msg, 0); + fd = f->f_addr_fds[i]; + lsent = sendmsg(fd, &msg, 0); + if (lsent == -1 && + (errno == ENOTCONN || errno == EDESTADDRREQ)) { + int error; + + error = cap_connect(cap_net, fd, + (struct sockaddr *)&f->f_addrs[i].raddr, + f->f_addrs[i].raddr.ss_len); + if (error != 0 && errno == EADDRINUSE) { + fd = find_forw_fd(&f->f_addrs[i].raddr, + &f->f_addrs[i].laddr, f); + if (fd != -1) { + (void)dup2(fd, + f->f_addr_fds[i]); + fd = f->f_addr_fds[i]; + } else { + /* + * Something is preventing us + * from connecting, we don't + * have much recourse. Keep + * fd==-1 to trigger an error + * from sendmsg() below. + */ + dprintf( + "failed to connect to %s", + f->f_hname); + } + } + lsent = sendmsg(fd, &msg, 0); + } if (lsent == (ssize_t)il->totalsize && !send_to_all) break; } dprintf("lsent/totalsize: %zd/%zu\n", lsent, il->totalsize); if (lsent != (ssize_t)il->totalsize) { int e = errno; + logerror("sendto"); - errno = e; - switch (errno) { + switch (e) { case ENOBUFS: case ENETDOWN: case ENETUNREACH: @@ -1828,6 +1904,9 @@ fprintlog_write(struct filed *f, struct iovlist *il, int flags) case EADDRNOTAVAIL: case EAGAIN: case ECONNREFUSED: + case ENOTCONN: + case EDESTADDRREQ: + case EADDRINUSE: break; /* case EBADF: */ /* case EACCES: */ @@ -1836,7 +1915,7 @@ fprintlog_write(struct filed *f, struct iovlist *il, int flags) /* case EMSGSIZE: */ default: dprintf("removing entry: errno=%d\n", e); - f->f_type = F_UNUSED; + close_filed(f); break; } } @@ -2533,7 +2612,7 @@ syslogd_cap_enter(void) err(1, "Failed to open the system.net libcasper service"); cap_close(cap_casper); limit = cap_net_limit_init(cap_net, - CAPNET_ADDR2NAME | CAPNET_NAME2ADDR); + CAPNET_ADDR2NAME | CAPNET_NAME2ADDR | CAPNET_CONNECT); if (limit == NULL) err(1, "Failed to create system.net limits"); if (cap_net_limit(limit) == -1) @@ -2641,15 +2720,18 @@ init(bool reload) &domain, &len) < 0) err(1, "getsockopt"); + port = -1; switch (domain) { #ifdef INET case AF_INET: { struct sockaddr_in sin; len = sizeof(sin); - if (getpeername(sockfd, (struct sockaddr *)&sin, &len) < 0) - err(1, "getpeername"); - port = ntohs(sin.sin_port); + if (getpeername(sockfd, + (struct sockaddr *)&sin, &len) < 0) + warn("getpeername"); + else + port = ntohs(sin.sin_port); break; } #endif @@ -2658,20 +2740,18 @@ init(bool reload) struct sockaddr_in6 sin6; len = sizeof(sin6); - if (getpeername(sockfd, (struct sockaddr *)&sin6, &len) < 0) - err(1, "getpeername"); - port = ntohs(sin6.sin6_port); + if (getpeername(sockfd, + (struct sockaddr *)&sin6, &len) < 0) + warn("getpeername"); + else + port = ntohs(sin6.sin6_port); break; } #endif default: port = 0; } - if (port != 514) { - printf("%s:%d", f->f_hname, port); - } else { - printf("%s", f->f_hname); - } + printf("%s:%d", f->f_hname, port); break; } @@ -3014,13 +3094,20 @@ make_forw_socket(const nvlist_t *nvl, struct addrinfo *ai, struct addrinfo *lai) errc(1, EADDRINUSE, "connect"); (void)close(s); s = s1; + } else if (errno == ENETUNREACH || errno == EHOSTUNREACH) { + /* + * We can't connect right now, the system is probably + * not fully configured. We will try again, using + * cap_net, once something actually tries to log. + */ + ; } else { err(1, "connect"); } } /* Make it a write-only socket. */ if (shutdown(s, SHUT_RD) < 0) - err(1, "shutdown"); + warn("shutdown"); return (s); } @@ -3144,7 +3231,8 @@ parse_action(const nvlist_t *nvl, const char *p, struct filed *f) }; error = getaddrinfo(f->f_hname, p ? p : "syslog", &hints, &res); if (error) { - dprintf("%s\n", gai_strerror(error)); + dprintf("getaddrinfo(%s): %s\n", f->f_hname, + gai_strerror(error)); break; } make_forw_socket_array(nvl, f, res); diff --git a/usr.sbin/syslogd/syslogd_cap_config.c b/usr.sbin/syslogd/syslogd_cap_config.c index 0cf6fb67491d..5b333d5f6037 100644 --- a/usr.sbin/syslogd/syslogd_cap_config.c +++ b/usr.sbin/syslogd/syslogd_cap_config.c @@ -226,6 +226,13 @@ nvlist_to_filed(const nvlist_t *nvl_filed) if (filed->f_addr_fds[i] < 0) err(1, "dup"); } + filed->f_addrs = calloc(filed->f_num_addr_fds, + sizeof(*filed->f_addrs)); + if (filed->f_addrs == NULL) + err(1, "calloc"); + memcpy(filed->f_addrs, + nvlist_get_binary(nvl_filed, "f_addrs", NULL), + filed->f_num_addr_fds * sizeof(*filed->f_addrs)); } else if (filed->f_type == F_PIPE) { (void)strlcpy(filed->f_pname, nvlist_get_string(nvl_filed, "f_pname"), sizeof(filed->f_pname)); diff --git a/usr.sbin/syslogd/tests/Makefile b/usr.sbin/syslogd/tests/Makefile index fcc0679f3274..f362f40c6653 100644 --- a/usr.sbin/syslogd/tests/Makefile +++ b/usr.sbin/syslogd/tests/Makefile @@ -7,8 +7,7 @@ ATF_TESTS_SH= syslogd_basic_format_test \ # Run in jail with new vnet so we don't need to worry about address conflicts TEST_METADATA= execenv="jail" \ - execenv_jail_params="vnet=new" \ - timeout=20 + execenv_jail_params="vnet=new" ${PACKAGE}FILES=syslogd_format_test_common.sh \ syslogd_test_common.sh diff --git a/usr.sbin/syslogd/tests/syslogd_test.sh b/usr.sbin/syslogd/tests/syslogd_test.sh index 253a26258959..d17152f69bfc 100644 --- a/usr.sbin/syslogd/tests/syslogd_test.sh +++ b/usr.sbin/syslogd/tests/syslogd_test.sh @@ -588,6 +588,74 @@ forward_cleanup() syslogd_cleanup } +atf_test_case "forward_reload" "cleanup" +forward_reload_head() +{ + atf_set descr "syslogd might start before routes are configured" + atf_set require.user root +} +forward_reload_body() +{ + local epair server client + + server=syslogd_server$$ + client=syslogd_client$$ + + syslogd_check_req epair + + atf_check -o save:epair ifconfig epair create + epair=$(cat epair) + epair=${epair%%a} + + syslogd_mkjail $server vnet + atf_check ifconfig ${epair}a vnet $server + atf_check jexec $server ifconfig ${epair}a inet6 fd00::2/64 + + syslogd_mkjail $client vnet + atf_check ifconfig ${epair}b vnet $client + + cat <<__EOF__ > ./server_config +user.debug ${SYSLOGD_LOGFILE} +ftp.debug ${SYSLOGD_LOGFILE} +__EOF__ + + syslogd_start -j $server -f ${PWD}/server_config -b fd00::2 + + cat <<__EOF__ > ./client_config +user.debug @[fd00::2] +ftp.debug @[fd00::2] +__EOF__ + + syslogd_start -j $client -f ${PWD}/client_config \ + -p ${PWD}/client -P ${SYSLOGD_PIDFILE}.2 + + # Make sure the client can't reach the server with the current + # network configuration. + atf_check -s not-exit:0 -e match:"No route to host" \ + jexec $client ping6 -c 1 fd00::2 + + syslogd_log_jail $client -p user.debug -t test1 "hello there" + syslogd_log_jail $client -p ftp.debug -t test2 "hi there" + + atf_check jexec $client ifconfig ${epair}b inet6 fd00::1/64 + atf_check -o ignore jexec $client ping6 -c 1 fd00::2 + + syslogd_check_log_nomatch "test1: hello there" + syslogd_check_log_nomatch "test2: hi there" + + syslogd_log_jail $client \ + -p user.debug -t test1 -h ${PWD}/client "how about now" + syslogd_check_log "test1: how about now" + + syslogd_log_jail $client \ + -p ftp.debug -t test2 -h ${PWD}/client "bing bong" + syslogd_check_log "test2: bing bong" +} +forward_reload_cleanup() +{ + syslogd_cleanup +} + atf_init_test_cases() { atf_add_test_case "unix" @@ -605,4 +673,5 @@ atf_init_test_cases() atf_add_test_case "allowed_peer_forwarding" atf_add_test_case "allowed_peer_wildcard" atf_add_test_case "forward" + atf_add_test_case "forward_reload" }