networkd: add minimal IP forwarding and masquerading support to .network files

This adds two new settings to networkd's .network files:
IPForwarding=yes and IPMasquerade=yes. The former controls the
"forwarding" sysctl setting of the interface, thus controlling whether
IP forwarding shall be enabled on the specific interface. The latter
controls whether a firewall rule shall be installed that exposes traffic
coming from the interface as coming from the local host to all other
interfaces.

This also enables both options by default for container network
interfaces, thus making "systemd-nspawn --network-veth" have network
connectivity out of the box.
This commit is contained in:
Lennart Poettering 2015-01-13 13:47:08 +01:00
parent 76917807eb
commit 5a8bcb674f
10 changed files with 232 additions and 70 deletions

View File

@ -5403,6 +5403,11 @@ systemd_networkd_SOURCES = \
systemd_networkd_LDADD = \
libsystemd-networkd-core.la
if HAVE_LIBIPTC
systemd_networkd_LDADD += \
libsystemd-fw.la
endif
noinst_LTLIBRARIES += \
libsystemd-networkd-core.la
@ -5493,6 +5498,11 @@ test_network_SOURCES = \
test_network_LDADD = \
libsystemd-networkd-core.la
if HAVE_LIBIPTC
test_network_LDADD += \
libsystemd-fw.la
endif
test_network_tables_SOURCES = \
src/network/test-network-tables.c \
src/shared/test-tables.h

View File

@ -344,6 +344,30 @@
<para>An NTP server address. This option may be specified more than once.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>IPForward=</varname></term>
<listitem><para>Configures IP
forwarding for the network
interface. If enabled incoming
packets on the network
interface will be forwarded to
other interfaces according to
the routing table. Takes a
boolean
argument.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>IPMasquerade=</varname></term>
<listitem><para>Configures IP
masquerading for the network
interface. If enabled packets
forwarded from the network
interface will be appear as
coming from the local
host. Takes a boolean
argument. Implies
<varname>IPForward=yes</varname>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>Bridge=</varname></term>
<listitem>

View File

@ -13,3 +13,5 @@ Driver=veth
Address=0.0.0.0/28
IPv4LL=yes
DHCPServer=yes
IPForward=yes
IPMasquerade=yes

View File

@ -21,13 +21,13 @@
#include <net/if.h>
#include "networkd.h"
#include "networkd-link.h"
#include "utf8.h"
#include "util.h"
#include "conf-parser.h"
#include "fw-util.h"
#include "network-internal.h"
#include "networkd.h"
#include "networkd-link.h"
static void address_init(Address *address) {
assert(address);
@ -103,6 +103,54 @@ void address_free(Address *address) {
free(address);
}
int address_establish(Address *address, Link *link) {
bool masq;
int r;
assert(address);
assert(link);
masq = link->network &&
link->network->ip_masquerade &&
address->family == AF_INET &&
address->scope < RT_SCOPE_LINK;
/* Add firewall entry if this is requested */
if (address->ip_forward_done != masq) {
union in_addr_union masked = address->in_addr;
in_addr_mask(address->family, &masked, address->prefixlen);
r = fw_add_masquerade(masq, AF_INET, 0, &masked, address->prefixlen, NULL, NULL, 0);
if (r < 0)
log_link_warning_errno(link, r, "Could not enable IP masquerading: %m");
address->ip_forward_done = masq;
}
return 0;
}
int address_release(Address *address, Link *link) {
int r;
assert(address);
assert(link);
/* Remove masquerading firewall entry if it was added */
if (address->ip_forward_done) {
union in_addr_union masked = address->in_addr;
in_addr_mask(address->family, &masked, address->prefixlen);
r = fw_add_masquerade(false, AF_INET, 0, &masked, address->prefixlen, NULL, NULL, 0);
if (r < 0)
log_link_warning_errno(link, r, "Failed to disable IP masquerading: %m");
address->ip_forward_done = false;
}
return 0;
}
int address_drop(Address *address, Link *link,
sd_rtnl_message_handler_t callback) {
_cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL;
@ -115,6 +163,8 @@ int address_drop(Address *address, Link *link,
assert(link->manager);
assert(link->manager->rtnl);
address_release(address, link);
r = sd_rtnl_message_new_addr(link->manager->rtnl, &req, RTM_DELADDR,
link->ifindex, address->family);
if (r < 0)
@ -333,6 +383,8 @@ int address_configure(Address *address, Link *link,
link_ref(link);
address_establish(address, link);
return 0;
}
@ -549,8 +601,7 @@ bool address_equal(Address *a1, Address *a2) {
return (b1 >> (32 - a1->prefixlen)) == (b2 >> (32 - a1->prefixlen));
}
case AF_INET6:
{
case AF_INET6: {
uint64_t *b1, *b2;
b1 = (uint64_t*)&a1->in_addr.in6;
@ -558,6 +609,7 @@ bool address_equal(Address *a1, Address *a2) {
return (((b1[0] ^ b2[0]) | (b1[1] ^ b2[1])) == 0UL);
}
default:
assert_not_reached("Invalid address family");
}

View File

@ -23,16 +23,16 @@
#include <linux/if.h>
#include <unistd.h>
#include "networkd-link.h"
#include "networkd-netdev.h"
#include "libudev-private.h"
#include "udev-util.h"
#include "util.h"
#include "virt.h"
#include "fileio.h"
#include "bus-util.h"
#include "udev-util.h"
#include "libudev-private.h"
#include "network-internal.h"
#include "networkd-link.h"
#include "networkd-netdev.h"
#include "conf-parser.h"
#include "dhcp-lease-internal.h"
static bool link_dhcp6_enabled(Link *link) {
@ -82,12 +82,22 @@ static bool link_lldp_enabled(Link *link) {
if (!link->network)
return false;
if(link->network->bridge)
if (link->network->bridge)
return false;
return link->network->lldp;
}
static bool link_ip_forward_enabled(Link *link) {
if (link->flags & IFF_LOOPBACK)
return false;
if (!link->network)
return false;
return link->network->ip_forward;
}
#define FLAG_STRING(string, flag, old, new) \
(((old ^ new) & flag) \
? ((old & flag) ? (" -" string) : (" +" string)) \
@ -653,9 +663,7 @@ static int link_enter_set_addresses(Link *link) {
LIST_FOREACH(addresses, ad, link->network->static_addresses) {
r = address_configure(ad, link, &address_handler);
if (r < 0) {
log_link_warning(link,
"could not set addresses: %s",
strerror(-r));
log_link_warning_errno(link, r, "Could not set addresses: %m");
link_enter_failed(link);
return r;
}
@ -1217,6 +1225,18 @@ static int link_enter_join_netdev(Link *link) {
return 0;
}
static int link_set_ip_forward(Link *link) {
const char *p = NULL;
int r;
p = strappenda("/proc/sys/net/ipv4/conf/", link->ifname, "/forwarding");
r = write_string_file_no_create(p, link_ip_forward_enabled(link) ? "1" : "0");
if (r < 0)
log_link_warning_errno(link, r, "Cannot configure IP forwarding for interface: %m");
return 0;
}
static int link_configure(Link *link) {
int r;
@ -1228,6 +1248,10 @@ static int link_configure(Link *link) {
if (r < 0)
return r;
r = link_set_ip_forward(link);
if (r < 0)
return r;
if (link_ipv4ll_enabled(link)) {
r = ipv4ll_configure(link);
if (r < 0)
@ -1364,16 +1388,27 @@ int link_initialized(Link *link, struct udev_device *device) {
return 0;
}
static Address* link_get_equal_address(Link *link, Address *needle) {
Address *i;
assert(link);
assert(needle);
LIST_FOREACH(addresses, i, link->addresses)
if (address_equal(i, needle))
return i;
return NULL;
}
int link_rtnl_process_address(sd_rtnl *rtnl, sd_rtnl_message *message, void *userdata) {
Manager *m = userdata;
Link *link = NULL;
uint16_t type;
_cleanup_address_free_ Address *address = NULL;
Address *ad;
char buf[INET6_ADDRSTRLEN];
char valid_buf[FORMAT_TIMESPAN_MAX];
Address *existing;
char buf[INET6_ADDRSTRLEN], valid_buf[FORMAT_TIMESPAN_MAX];
const char *valid_str = NULL;
bool address_dropped = false;
int r, ifindex;
assert(rtnl);
@ -1415,50 +1450,42 @@ int link_rtnl_process_address(sd_rtnl *rtnl, sd_rtnl_message *message, void *use
r = sd_rtnl_message_addr_get_family(message, &address->family);
if (r < 0 || !IN_SET(address->family, AF_INET, AF_INET6)) {
log_link_warning(link,
"rtnl: received address with invalid family, ignoring");
log_link_warning(link, "rtnl: received address with invalid family, ignoring");
return 0;
}
r = sd_rtnl_message_addr_get_prefixlen(message, &address->prefixlen);
if (r < 0) {
log_link_warning(link,
"rtnl: received address with invalid prefixlen, ignoring");
log_link_warning(link, "rtnl: received address with invalid prefixlen, ignoring");
return 0;
}
r = sd_rtnl_message_addr_get_scope(message, &address->scope);
if (r < 0) {
log_link_warning(link,
"rtnl: received address with invalid scope, ignoring");
log_link_warning(link, "rtnl: received address with invalid scope, ignoring");
return 0;
}
r = sd_rtnl_message_addr_get_flags(message, &address->flags);
if (r < 0) {
log_link_warning(link,
"rtnl: received address with invalid flags, ignoring");
log_link_warning(link, "rtnl: received address with invalid flags, ignoring");
return 0;
}
switch (address->family) {
case AF_INET:
r = sd_rtnl_message_read_in_addr(message, IFA_LOCAL,
&address->in_addr.in);
r = sd_rtnl_message_read_in_addr(message, IFA_LOCAL, &address->in_addr.in);
if (r < 0) {
log_link_warning(link,
"rtnl: received address without valid address, ignoring");
log_link_warning(link, "rtnl: received address without valid address, ignoring");
return 0;
}
break;
case AF_INET6:
r = sd_rtnl_message_read_in6_addr(message, IFA_ADDRESS,
&address->in_addr.in6);
r = sd_rtnl_message_read_in6_addr(message, IFA_ADDRESS, &address->in_addr.in6);
if (r < 0) {
log_link_warning(link,
"rtnl: received address without valid address, ignoring");
log_link_warning(link, "rtnl: received address without valid address, ignoring");
return 0;
}
@ -1468,14 +1495,12 @@ int link_rtnl_process_address(sd_rtnl *rtnl, sd_rtnl_message *message, void *use
assert_not_reached("invalid address family");
}
if (!inet_ntop(address->family, &address->in_addr, buf,
INET6_ADDRSTRLEN)) {
if (!inet_ntop(address->family, &address->in_addr, buf, INET6_ADDRSTRLEN)) {
log_link_warning(link, "could not print address");
return 0;
}
r = sd_rtnl_message_read_cache_info(message, IFA_CACHEINFO,
&address->cinfo);
r = sd_rtnl_message_read_cache_info(message, IFA_CACHEINFO, &address->cinfo);
if (r >= 0) {
if (address->cinfo.ifa_valid == CACHE_INFO_INFINITY_LIFE_TIME)
valid_str = "ever";
@ -1485,43 +1510,40 @@ int link_rtnl_process_address(sd_rtnl *rtnl, sd_rtnl_message *message, void *use
USEC_PER_SEC);
}
LIST_FOREACH(addresses, ad, link->addresses) {
if (address_equal(ad, address)) {
LIST_REMOVE(addresses, link->addresses, ad);
address_free(ad);
address_dropped = true;
break;
}
}
existing = link_get_equal_address(link, address);
switch (type) {
case RTM_NEWADDR:
if (!address_dropped)
log_link_debug(link, "added address: %s/%u (valid for %s)",
buf, address->prefixlen, valid_str);
else
log_link_debug(link, "updated address: %s/%u (valid for %s)",
buf, address->prefixlen, valid_str);
if (existing) {
log_link_debug(link, "Updating address: %s/%u (valid for %s)", buf, address->prefixlen, valid_str);
LIST_PREPEND(addresses, link->addresses, address);
address = NULL;
link_save(link);
existing->scope = address->scope;
existing->flags = address->flags;
existing->cinfo = address->cinfo;
break;
case RTM_DELADDR:
if (address_dropped) {
log_link_debug(link, "removed address: %s/%u (valid for %s)",
buf, address->prefixlen, valid_str);
} else {
log_link_debug(link, "Adding address: %s/%u (valid for %s)", buf, address->prefixlen, valid_str);
LIST_PREPEND(addresses, link->addresses, address);
address_establish(address, link);
address = NULL;
link_save(link);
}
break;
case RTM_DELADDR:
if (existing) {
log_link_debug(link, "Removing address: %s/%u (valid for %s)", buf, address->prefixlen, valid_str);
address_release(existing, link);
LIST_REMOVE(addresses, link->addresses, existing);
address_free(existing);
} else
log_link_warning(link,
"removing non-existent address: %s/%u (valid for %s)",
buf, address->prefixlen, valid_str);
log_link_warning(link, "Removing non-existent address: %s/%u (valid for %s)", buf, address->prefixlen, valid_str);
break;
default:

View File

@ -44,6 +44,8 @@ Network.Domains, config_parse_domains, 0,
Network.DNS, config_parse_strv, 0, offsetof(Network, dns)
Network.LLMNR, config_parse_llmnr, 0, offsetof(Network, llmnr)
Network.NTP, config_parse_strv, 0, offsetof(Network, ntp)
Network.IPForward, config_parse_bool, 0, offsetof(Network, ip_forward)
Network.IPMasquerade, config_parse_bool, 0, offsetof(Network, ip_masquerade)
Address.Address, config_parse_address, 0, 0
Address.Peer, config_parse_address, 0, 0
Address.Broadcast, config_parse_broadcast, 0, 0

View File

@ -22,14 +22,14 @@
#include <ctype.h>
#include <net/if.h>
#include "networkd.h"
#include "networkd-netdev.h"
#include "networkd-link.h"
#include "network-internal.h"
#include "path-util.h"
#include "conf-files.h"
#include "conf-parser.h"
#include "util.h"
#include "networkd.h"
#include "networkd-netdev.h"
#include "networkd-link.h"
#include "network-internal.h"
static int network_load_one(Manager *manager, const char *filename) {
_cleanup_network_free_ Network *network = NULL;
@ -109,6 +109,10 @@ static int network_load_one(Manager *manager, const char *filename) {
if (r < 0)
return r;
/* IPMasquerade=yes implies IPForward=yes */
if (network->ip_masquerade)
network->ip_forward = true;
LIST_PREPEND(networks, manager->networks, network);
LIST_FOREACH(routes, route, network->static_routes) {

View File

@ -120,6 +120,9 @@ struct Network {
unsigned cost;
bool ip_masquerade;
bool ip_forward;
struct ether_addr *mac;
unsigned mtu;
@ -157,6 +160,8 @@ struct Address {
union in_addr_union in_addr;
union in_addr_union in_addr_peer;
bool ip_forward_done;
LIST_FIELDS(Address, addresses);
};
@ -326,6 +331,8 @@ void address_free(Address *address);
int address_configure(Address *address, Link *link, sd_rtnl_message_handler_t callback);
int address_update(Address *address, Link *link, sd_rtnl_message_handler_t callback);
int address_drop(Address *address, Link *link, sd_rtnl_message_handler_t callback);
int address_establish(Address *address, Link *link);
int address_release(Address *address, Link *link);
bool address_equal(Address *a1, Address *a2);
DEFINE_TRIVIAL_CLEANUP_FUNC(Address*, address_free);

View File

@ -300,3 +300,39 @@ int in_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask
in_addr_prefixlen_to_netmask(mask, prefixlen);
return 0;
}
int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen) {
assert(addr);
if (family == AF_INET) {
struct in_addr mask;
if (!in_addr_prefixlen_to_netmask(&mask, prefixlen))
return -EINVAL;
addr->in.s_addr &= mask.s_addr;
return 0;
}
if (family == AF_INET6) {
unsigned i;
for (i = 0; i < 16; i++) {
uint8_t mask;
if (prefixlen >= 8) {
mask = 0xFF;
prefixlen -= 8;
} else {
mask = 0xFF << (8 - prefixlen);
prefixlen = 0;
}
addr->in6.s6_addr[i] &= mask;
}
return 0;
}
return -EAFNOSUPPORT;
}

View File

@ -43,8 +43,11 @@ unsigned char in_addr_netmask_to_prefixlen(const struct in_addr *addr);
struct in_addr* in_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen);
int in_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen);
int in_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask);
int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen);
static inline size_t FAMILY_ADDRESS_SIZE(int family) {
assert(family == AF_INET || family == AF_INET6);
return family == AF_INET6 ? 16 : 4;
}
#define IN_ADDR_NULL ((union in_addr_union) {})