Skip to content

Commit 765afd5

Browse files
committed
networkd: stop managing per-interface IP forwarding settings
As it turns out the kernel does not support per-interface IPv6 packet forwarding controls (unlike as it does for IPv4), but only supports a global option (#1597). Also, the current per-interface management of the setting isn't really useful, as you want it to propagate to at least one more interface than the one you configure it on. This created much grief (#1411, #1808). Hence, let's roll this logic back and simplify this again, so that we can expose the same behaviour on IPv4 and IPv6 and things start to work automatically again for most folks: if a network with this setting set is set up we propagate the setting into the global setting, but this is strictly one-way: we never reset it again, and we do nothing for network interfaces where this setting is not enabled. Fixes: #1808, #1597.
1 parent d68e2e5 commit 765afd5

File tree

3 files changed

+57
-42
lines changed

3 files changed

+57
-42
lines changed

man/systemd.network.xml

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -363,29 +363,28 @@
363363
</varlistentry>
364364
<varlistentry>
365365
<term><varname>IPForward=</varname></term>
366-
<listitem><para>Configures IP forwarding for the network
367-
interface. If enabled, incoming packets on the network
368-
interface will be forwarded to other interfaces according to
369-
the routing table. Takes either a boolean argument, or the
370-
values <literal>ipv4</literal> or <literal>ipv6</literal>,
371-
which only enables IP forwarding for the specified address
372-
family, or <literal>kernel</literal>, which preserves existing sysctl settings.
373-
This controls the
374-
<filename>net.ipv4.conf.&lt;interface&gt;.forwarding</filename>
375-
and
376-
<filename>net.ipv6.conf.&lt;interface&gt;.forwarding</filename>
377-
sysctl options of the network interface (see <ulink
366+
<listitem><para>Configures IP packet forwarding for the
367+
system. If enabled, incoming packets on any network
368+
interface will be forwarded to any other interfaces
369+
according to the routing table. Takes either a boolean
370+
argument, or the values <literal>ipv4</literal> or
371+
<literal>ipv6</literal>, which only enable IP packet
372+
forwarding for the specified address family. This controls
373+
the <filename>net.ipv4.ip_forward</filename> and
374+
<filename>net.ipv6.conf.all.forwarding</filename> sysctl
375+
options of the network interface (see <ulink
378376
url="https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt">ip-sysctl.txt</ulink>
379377
for details about sysctl options). Defaults to
380378
<literal>no</literal>.</para>
381379

382-
<para>Note: unless this option is turned on, or set to <literal>kernel</literal>,
383-
no IP forwarding is done on this interface, even if this is
384-
globally turned on in the kernel, with the
385-
<filename>net.ipv4.ip_forward</filename>,
386-
<filename>net.ipv4.conf.all.forwarding</filename>, and
387-
<filename>net.ipv6.conf.all.forwarding</filename> sysctl
388-
options.</para>
380+
<para>Note: this setting controls a global kernel option,
381+
and does so one way only: if a network that has this setting
382+
enabled is set up the global setting is turned on. However,
383+
it is never turned off again, even after all networks with
384+
this setting enabled are shut down again.</para>
385+
386+
<para>To allow IP packet forwarding only between specific
387+
network interfaces use a firewall.</para>
389388
</listitem>
390389
</varlistentry>
391390
<varlistentry>

src/network/networkd-link.c

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -111,16 +111,26 @@ static bool link_ipv4_forward_enabled(Link *link) {
111111
if (!link->network)
112112
return false;
113113

114+
if (link->network->ip_forward == _ADDRESS_FAMILY_BOOLEAN_INVALID)
115+
return false;
116+
114117
return link->network->ip_forward & ADDRESS_FAMILY_IPV4;
115118
}
116119

117120
static bool link_ipv6_forward_enabled(Link *link) {
121+
122+
if (!socket_ipv6_is_supported())
123+
return false;
124+
118125
if (link->flags & IFF_LOOPBACK)
119126
return false;
120127

121128
if (!link->network)
122129
return false;
123130

131+
if (link->network->ip_forward == _ADDRESS_FAMILY_BOOLEAN_INVALID)
132+
return false;
133+
124134
return link->network->ip_forward & ADDRESS_FAMILY_IPV6;
125135
}
126136

@@ -1851,45 +1861,43 @@ static int link_enter_join_netdev(Link *link) {
18511861
}
18521862

18531863
static int link_set_ipv4_forward(Link *link) {
1854-
const char *p = NULL, *v;
18551864
int r;
18561865

1857-
if (link->flags & IFF_LOOPBACK)
1858-
return 0;
1859-
1860-
if (link->network->ip_forward == _ADDRESS_FAMILY_BOOLEAN_INVALID)
1866+
if (!link_ipv4_forward_enabled(link))
18611867
return 0;
18621868

1863-
p = strjoina("/proc/sys/net/ipv4/conf/", link->ifname, "/forwarding");
1864-
v = one_zero(link_ipv4_forward_enabled(link));
1869+
/* We propagate the forwarding flag from one interface to the
1870+
* global setting one way. This means: as long as at least one
1871+
* interface was configured at any time that had IP forwarding
1872+
* enabled the setting will stay on for good. We do this
1873+
* primarily to keep IPv4 and IPv6 packet forwarding behaviour
1874+
* somewhat in sync (see below). */
18651875

1866-
r = write_string_file(p, v, WRITE_STRING_FILE_VERIFY_ON_FAILURE);
1876+
r = write_string_file("/proc/sys/net/ipv4/ip_forward", "1", WRITE_STRING_FILE_VERIFY_ON_FAILURE);
18671877
if (r < 0)
1868-
log_link_warning_errno(link, r, "Cannot configure IPv4 forwarding for interface %s: %m", link->ifname);
1878+
log_link_warning_errno(link, r, "Cannot turn on IPv4 packet forwarding, ignoring: %m");
18691879

18701880
return 0;
18711881
}
18721882

18731883
static int link_set_ipv6_forward(Link *link) {
1874-
const char *p = NULL, *v = NULL;
18751884
int r;
18761885

1877-
/* Make this a NOP if IPv6 is not available */
1878-
if (!socket_ipv6_is_supported())
1879-
return 0;
1880-
1881-
if (link->flags & IFF_LOOPBACK)
1882-
return 0;
1883-
1884-
if (link->network->ip_forward == _ADDRESS_FAMILY_BOOLEAN_INVALID)
1886+
if (!link_ipv6_forward_enabled(link))
18851887
return 0;
18861888

1887-
p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/forwarding");
1888-
v = one_zero(link_ipv6_forward_enabled(link));
1889+
/* On Linux, the IPv6 stack does not not know a per-interface
1890+
* packet forwarding setting: either packet forwarding is on
1891+
* for all, or off for all. We hence don't bother with a
1892+
* per-interface setting, but simply propagate the interface
1893+
* flag, if it is set, to the global flag, one-way. Note that
1894+
* while IPv4 would allow a per-interface flag, we expose the
1895+
* same behaviour there and also propagate the setting from
1896+
* one to all, to keep things simple (see above). */
18891897

1890-
r = write_string_file(p, v, WRITE_STRING_FILE_VERIFY_ON_FAILURE);
1898+
r = write_string_file("/proc/sys/net/ipv6/conf/all/forwarding", "1", WRITE_STRING_FILE_VERIFY_ON_FAILURE);
18911899
if (r < 0)
1892-
log_link_warning_errno(link, r, "Cannot configure IPv6 forwarding for interface: %m");
1900+
log_link_warning_errno(link, r, "Cannot configure IPv6 packet forwarding, ignoring: %m");
18931901

18941902
return 0;
18951903
}

src/network/networkd-util.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,18 @@ int config_parse_address_family_boolean_with_kernel(
7979
assert(rvalue);
8080
assert(data);
8181

82+
/* This function is mostly obsolete now. It simply redirects
83+
* "kernel" to "no". In older networkd versions we used to
84+
* distuingish IPForward=off from IPForward=kernel, where the
85+
* former would explicitly turn off forwarding while the
86+
* latter would simply not touch the setting. But that logic
87+
* is gone, hence silently accept the old setting, but turn it
88+
* to "no". */
89+
8290
s = address_family_boolean_from_string(rvalue);
8391
if (s < 0) {
8492
if (streq(rvalue, "kernel"))
85-
s = _ADDRESS_FAMILY_BOOLEAN_INVALID;
93+
s = ADDRESS_FAMILY_NO;
8694
else {
8795
log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse IPForward= option, ignoring: %s", rvalue);
8896
return 0;

0 commit comments

Comments
 (0)