(the host this is being reported from is the same host used in testing .. which is isolated)
Given a .conf file containing (literally):
net.ipv6.conf.all.forwarding=1 # not 2345
The two commands behave quite differently and produce very different results**.
I was able to simulate this behaviour using write(2) vs printf(3), I will attach the
code samples.
** systcl(8) sets it to 1, systemd-sysctl(8) sets it to 5
Looking at the code of sysctl(8) , it APPEARS to use printf(3) but the file
pointer was not created with fopen(3) but rather fprocopen() [ uses openat(2) ]
so ultimately it uses write(2).
Other interesting examples to test are:
net.ipv6.conf.all.forwarding=12345
and
net.ipv6.conf.all.forwarding=0 # must be 0 as 1 is a security breach
Which produce surprising results, esp if you use sysctl(8) to test your setting prior to "go live"
(esp. as the effect of setting net.ipv6.conf.all.forwarding to 6 seems to be the same as 0)
# cat /etc/sysctl.d/60-bogus.conf
net.ipv6.conf.all.forwarding=1 # not 2345
# sysctl -w net.ipv6.conf.all.forwarding=0
net.ipv6.conf.all.forwarding = 0
# sysctl net.ipv6.conf.all.forwarding
net.ipv6.conf.all.forwarding = 0
# strace /usr/sbin/sysctl --system
...
readlink("/proc/sys/net/ipv6/conf/all/forwarding", 0x7ffd0d33b0b0, 1023) = -1 EINVAL (Invalid argument)
openat(AT_FDCWD, "/proc/sys/net/ipv6/conf/all/forwarding", O_WRONLY|O_TRUNC) = 3
write(3, "1 # not 2345\n", 14) = 1
close(3) = 0
write(1, "net.ipv6.conf.all.forwarding = 1"..., 45net.ipv6.conf.all.forwarding = 1 # not 2345
) = 45
close(1) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
# sysctl net.ipv6.conf.all.forwarding
net.ipv6.conf.all.forwarding = 1
.....
# sysctl -w net.ipv6.conf.all.forwarding=0
net.ipv6.conf.all.forwarding = 0
# sysctl net.ipv6.conf.all.forwarding
net.ipv6.conf.all.forwarding = 0
# strace /usr/lib/systemd/systemd-sysctl
...
read(3, "4194304\n", 9) = 8
close(3) = 0
openat(AT_FDCWD, "/proc/sys/net/ipv6/conf/all/forwarding", O_RDWR|O_NOCTTY|O_CLOEXEC) = 3
fcntl(3, F_GETFL) = 0x8002 (flags O_RDWR|O_LARGEFILE)
fstat(3, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
read(3, "0\n", 15) = 2
lseek(3, 0, SEEK_SET) = 0
write(3, "1 # not 2345\n", 14) = 1
write(3, " # not 2345\n", 13) = 1
write(3, " # not 2345\n", 12) = 1
write(3, "# not 2345\n", 11) = 1
write(3, " not 2345\n", 10) = 1
write(3, "not 2345\n", 9) = 1
write(3, "ot 2345\n", 8) = 1
write(3, "t 2345\n", 7) = 1
write(3, " 2345\n", 6) = 1
write(3, "2345\n", 5) = 1
write(3, "345\n", 4) = 1
write(3, "45\n", 3) = 1
write(3, "5\n", 2) = 1
write(3, "\n", 1) = 1
close(3) = 0
exit_group(0) = ?
# sysctl net.ipv6.conf.all.forwarding
net.ipv6.conf.all.forwarding = 5
# apt-file search /usr/lib/systemd/systemd-sysctl
systemd: /usr/lib/systemd/systemd-sysctl
# apt show systemd
WARNING: terminal is not fully functional
Press RETURN to continue
Package: systemd
Version: 257.9-1~deb13u1
Priority: important
$ cat /proc/version
Linux version 6.12.85+deb13-amd64 (debian-kernel@lists.debian.org) (x86_64-linux-gnu-gcc-14 (Debian 14.2.0-19) 14.2.0, GNU ld (GNU Binutils for Debian) 2.44) #1 SMP PREEMPT_DYNAMIC Debian 6.12.85-1 (2026-04-30)
$ cat /etc/debian_version
13.4
In case it wasn't obvious the line: (the host this is being reported from is the same host used in testing .. which is isolated) Should read: (the host this is being reported from is NOT the same host used in testing .. which is isolated)
Somebody has changed the title of the bug from Bug#1135862: Acknowledgement (systemd: /usr/lib/systemd/systemd-sysctl behaves quite differently to /usr/sbin/sysctl, setting wrong values) to systemd-sysctl: does not handle same-line comments in conf files This is confusing and inaccurate . The bug does NOT relate to comments , as the example net.ipv6.conf.all.forwarding=12345 Shows . Any reference to inline comments will likely be rejected a RTFM. Please resect the many man hours spent digging into this issue, to raise a accurate bug report.
retitle 1135862 systemd-sysctl behaves differently than sysctl and sets the wrong values thanks My apologies, I misread your examples, as two of them specifically demonstrated a problem with comments. You're right, *that* example shows there are multiple issues here: there's an issue of not handling same-line comments, *and* an issue of not handling some values correctly. I've changed the title of the issue to reflect the more general issue. Thank you.
I suspect there is a single bug here, but the behaviour differs. This
and another IPv6 related new bug held me up for several weeks, so I've
spent a lot of time looking at this.
I initially also thought it was related to treatment of comments .
However after quite a lot of testing and code inspection, it appears the
root cause is
sysctl(8) uses write(2)
systemd variant uses some form of stream IO (e.f prinntf) **
** so when a write(2) of a 10 byte buffer returns "1" (byte sent) ,
systemd keeps calling , until all 10 bytes are consumed. sysctl(8) just
stops at 1 byte
*** that said I've seen write(2) also send many bytes ... so , a bit
more complicated.
The interaction is then how the kernel level code accepts the IO for
this particular /proc variable , it accepts just 1 byte (and seems to
only differentiate "1" or not "1" ..so "6" acts like "0")
for other /proc settings the kernel side obviously behaves differently ,
since for example:
cat /proc/sys/kernel/version
#111-Ubuntu SMP PREEMPT_DYNAMIC Sat Apr 11 23:16:02 UTC 2026
Lots of bytes, including a '#' :-)
(BTW, from a different system, just a for instance)
The line in sysctl.c is:
} else {
if (0 < fprintf(fp, "%s\n", value))
rc = EXIT_SUCCESS;
Which looks like regular printf() but that "fp" comes, not from
fopen(3) but from:
if ((fp = fprocopen(path, "w")) == NULL) {
...
Then, via a long tortuous route , ends up at close() doing: (this is
from memory now, it's a while since I traced it)
if (cookie->final) {
len = write(cookie->fd, cookie->buf, cookie->offset);
There is a whole bunch of special cases "delimiter characters" etc and
a liberal scattering of gotos :-)