Merge pull request #12155 from yuwata/network-fix-and-extend-foo-over-udp-support

network: fix and extend Foo over UDP
This commit is contained in:
Yu Watanabe 2019-04-02 06:10:07 +09:00 committed by GitHub
commit 7a24df5ecc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 294 additions and 43 deletions

View file

@ -1008,7 +1008,8 @@
<term><varname>FooOverUDP=</varname></term>
<listitem>
<para>Takes a boolean. Specifies whether <varname>FooOverUDP=</varname> tunnel is to be configured.
Defaults to false. For more detail information see
Defaults to false. This takes effects only for IPIP, SIT, GRE, and GRETAP tunnels.
For more detail information see
<ulink url="https://lwn.net/Articles/614348">Foo over UDP</ulink></para>
</listitem>
</varlistentry>
@ -1016,7 +1017,7 @@
<term><varname>FOUDestinationPort=</varname></term>
<listitem>
<para>This setting specifies the UDP destination port for encapsulation.
This field is mandatory and is not set by default.</para>
This field is mandatory when <varname>FooOverUDP=yes</varname>, and is not set by default.</para>
</listitem>
</varlistentry>
<varlistentry>
@ -1029,7 +1030,7 @@
<varlistentry>
<term><varname>Encapsulation=</varname></term>
<listitem>
<para>Accepts the same key as <literal>[FooOverUDP]</literal></para>
<para>Accepts the same key as in the <literal>[FooOverUDP]</literal> section.</para>
</listitem>
</varlistentry>
<varlistentry>
@ -1074,13 +1075,6 @@
following keys:</para>
<variablelist class='network-directives'>
<varlistentry>
<term><varname>Protocol=</varname></term>
<listitem>
<para>The <varname>Protocol=</varname> specifies the protocol number of the
packets arriving at the UDP port. This field is mandatory and is not set by default. Valid range is 1-255.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>Encapsulation=</varname></term>
<listitem>
@ -1103,7 +1097,17 @@
for delivery to the real destination. This option is mandatory.</para>
</listitem>
</varlistentry>
</variablelist>
<varlistentry>
<term><varname>Protocol=</varname></term>
<listitem>
<para>The <varname>Protocol=</varname> specifies the protocol number of the packets arriving
at the UDP port. When <varname>Encapsulation=FooOverUDP</varname>, this field is mandatory
and is not set by default. Takes an IP protocol name such as <literal>gre</literal> or
<literal>ipip</literal>, or an integer within the range 1-255. When
<varname>Encapsulation=GenericUDPEncapsulation</varname>, this must not be specified.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>[Peer] Section Options</title>

View file

@ -5,8 +5,10 @@
#include <linux/ip.h>
#include "conf-parser.h"
#include "ip-protocol-list.h"
#include "missing.h"
#include "netdev/fou-tunnel.h"
#include "netlink-util.h"
#include "networkd-link.h"
#include "networkd-manager.h"
#include "parse-util.h"
@ -27,6 +29,7 @@ DEFINE_CONFIG_PARSE_ENUM(config_parse_fou_encap_type, fou_encap_type, FooOverUDP
static int netdev_fill_fou_tunnel_message(NetDev *netdev, sd_netlink_message **ret) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
FouTunnel *t;
uint8_t encap_type;
int r;
assert(netdev);
@ -43,7 +46,18 @@ static int netdev_fill_fou_tunnel_message(NetDev *netdev, sd_netlink_message **r
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_PORT attribute: %m");
r = sd_netlink_message_append_u8(m, FOU_ATTR_TYPE, FOU_ENCAP_GUE);
switch (t->fou_encap_type) {
case NETDEV_FOO_OVER_UDP_ENCAP_DIRECT:
encap_type = FOU_ENCAP_DIRECT;
break;
case NETDEV_FOO_OVER_UDP_ENCAP_GUE:
encap_type = FOU_ENCAP_GUE;
break;
default:
assert_not_reached("invalid encap type");
}
r = sd_netlink_message_append_u8(m, FOU_ATTR_TYPE, encap_type);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_TYPE attribute: %m");
@ -55,15 +69,32 @@ static int netdev_fill_fou_tunnel_message(NetDev *netdev, sd_netlink_message **r
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_IPPROTO attribute: %m");
*ret = m;
m = NULL;
*ret = TAKE_PTR(m);
return 0;
}
static int fou_tunnel_create_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) {
int r;
assert(netdev);
assert(netdev->state != _NETDEV_STATE_INVALID);
r = sd_netlink_message_get_errno(m);
if (r == -EEXIST)
log_netdev_info(netdev, "netdev exists, using existing without changing its parameters");
else if (r < 0) {
log_netdev_warning_errno(netdev, r, "netdev could not be created: %m");
netdev_drop(netdev);
return 1;
}
log_netdev_debug(netdev, "FooOverUDP tunnel is created");
return 1;
}
static int netdev_fou_tunnel_create(NetDev *netdev) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
uint32_t serial;
int r;
assert(netdev);
@ -73,10 +104,49 @@ static int netdev_fou_tunnel_create(NetDev *netdev) {
if (r < 0)
return r;
r = sd_netlink_send(netdev->manager->genl, m, &serial);
if (r < 0 && r != -EADDRINUSE)
return log_netdev_error_errno(netdev, r, "Failed to add FooOverUDP tunnel: %m");
r = netlink_call_async(netdev->manager->genl, NULL, m, fou_tunnel_create_handler,
netdev_destroy_callback, netdev);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Failed to create FooOverUDP tunnel: %m");
netdev_ref(netdev);
return 0;
}
int config_parse_ip_protocol(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
uint8_t *protocol = data;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
assert_cc(IPPROTO_MAX-1 <= UINT8_MAX);
r = parse_ip_protocol(rvalue);
if (r < 0) {
r = safe_atou8(rvalue, protocol);
if (r < 0)
log_syntax(unit, LOG_ERR, filename, line, r,
"Failed to parse IP protocol '%s' for Foo over UDP tunnel, "
"ignoring assignment: %m", rvalue);
return 0;
}
*protocol = r;
return 0;
}
@ -90,14 +160,21 @@ static int netdev_fou_tunnel_verify(NetDev *netdev, const char *filename) {
assert(t);
if (t->fou_encap_type == NETDEV_FOO_OVER_UDP_ENCAP_DIRECT && t->fou_protocol <= 0) {
log_netdev_error(netdev, "FooOverUDP protocol not configured in %s. Rejecting configuration.", filename);
return -EINVAL;
}
if (t->fou_encap_type == NETDEV_FOO_OVER_UDP_ENCAP_GUE && t->fou_protocol > 0) {
log_netdev_error(netdev, "FooOverUDP GUE can't be set with protocol configured in %s. Rejecting configuration.", filename);
return -EINVAL;
switch (t->fou_encap_type) {
case NETDEV_FOO_OVER_UDP_ENCAP_DIRECT:
if (t->fou_protocol <= 0)
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
"FooOverUDP protocol not configured in %s. Rejecting configuration.",
filename);
break;
case NETDEV_FOO_OVER_UDP_ENCAP_GUE:
if (t->fou_protocol > 0)
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
"FooOverUDP GUE can't be set with protocol configured in %s. Rejecting configuration.",
filename);
break;
default:
assert_not_reached("Invalid fou encap type");
}
return 0;

View file

@ -34,3 +34,4 @@ const char *fou_encap_type_to_string(FooOverUDPEncapType d) _const_;
FooOverUDPEncapType fou_encap_type_from_string(const char *d) _pure_;
CONFIG_PARSER_PROTOTYPE(config_parse_fou_encap_type);
CONFIG_PARSER_PROTOTYPE(config_parse_ip_protocol);

View file

@ -75,7 +75,7 @@ Tunnel.IPv6RapidDeploymentPrefix, config_parse_6rd_prefix, 0,
Tunnel.ERSPANIndex, config_parse_uint32, 0, offsetof(Tunnel, erspan_index)
Tunnel.SerializeTunneledPackets, config_parse_tristate, 0, offsetof(Tunnel, gre_erspan_sequence)
Tunnel.ISATAP, config_parse_tristate, 0, offsetof(Tunnel, isatap)
FooOverUDP.Protocol, config_parse_uint8, 0, offsetof(FouTunnel, fou_protocol)
FooOverUDP.Protocol, config_parse_ip_protocol, 0, offsetof(FouTunnel, fou_protocol)
FooOverUDP.Encapsulation, config_parse_fou_encap_type, 0, offsetof(FouTunnel, fou_encap_type)
FooOverUDP.Port, config_parse_ip_port, 0, offsetof(FouTunnel, port)
L2TP.TunnelId, config_parse_l2tp_tunnel_id, 0, offsetof(L2tpTunnel, tunnel_id)

View file

@ -72,7 +72,7 @@ static int netdev_ipip_sit_fill_message_create(NetDev *netdev, Link *link, sd_ne
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_PMTUDISC attribute: %m");
if (netdev->kind == NETDEV_KIND_IPIP && t->fou_tunnel) {
if (t->fou_tunnel) {
r = sd_netlink_message_append_u16(m, IFLA_IPTUN_ENCAP_TYPE, t->fou_encap_type);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_ENCAP_TYPE attribute: %m");
@ -214,6 +214,20 @@ static int netdev_gre_erspan_fill_message_create(NetDev *netdev, Link *link, sd_
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_OFLAGS, attribute: %m");
if (t->fou_tunnel) {
r = sd_netlink_message_append_u16(m, IFLA_GRE_ENCAP_TYPE, t->fou_encap_type);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_ENCAP_TYPE attribute: %m");
r = sd_netlink_message_append_u16(m, IFLA_GRE_ENCAP_SPORT, htobe16(t->encap_src_port));
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_ENCAP_SPORT attribute: %m");
r = sd_netlink_message_append_u16(m, IFLA_GRE_ENCAP_DPORT, htobe16(t->fou_destination_port));
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_ENCAP_DPORT attribute: %m");
}
return r;
}
@ -669,23 +683,26 @@ int config_parse_6rd_prefix(const char* unit,
return 0;
}
static void ipip_init(NetDev *n) {
Tunnel *t = IPIP(n);
static void ipip_sit_init(NetDev *n) {
Tunnel *t;
assert(n);
switch (n->kind) {
case NETDEV_KIND_IPIP:
t = IPIP(n);
break;
case NETDEV_KIND_SIT:
t = SIT(n);
break;
default:
assert_not_reached("invalid netdev kind");
}
assert(t);
t->pmtudisc = true;
t->fou_encap_type = FOU_ENCAP_DIRECT;
}
static void sit_init(NetDev *n) {
Tunnel *t = SIT(n);
assert(n);
assert(t);
t->pmtudisc = true;
t->isatap = -1;
}
@ -727,6 +744,7 @@ static void gre_erspan_init(NetDev *n) {
t->pmtudisc = true;
t->gre_erspan_sequence = -1;
t->fou_encap_type = FOU_ENCAP_DIRECT;
}
static void ip6gre_init(NetDev *n) {
@ -759,7 +777,7 @@ static void ip6tnl_init(NetDev *n) {
const NetDevVTable ipip_vtable = {
.object_size = sizeof(Tunnel),
.init = ipip_init,
.init = ipip_sit_init,
.sections = "Match\0NetDev\0Tunnel\0",
.fill_message_create = netdev_ipip_sit_fill_message_create,
.create_type = NETDEV_CREATE_STACKED,
@ -768,7 +786,7 @@ const NetDevVTable ipip_vtable = {
const NetDevVTable sit_vtable = {
.object_size = sizeof(Tunnel),
.init = sit_init,
.init = ipip_sit_init,
.sections = "Match\0NetDev\0Tunnel\0",
.fill_message_create = netdev_ipip_sit_fill_message_create,
.create_type = NETDEV_CREATE_STACKED,

View file

@ -0,0 +1,14 @@
[NetDev]
Name=gretun96
Kind=gre
[Tunnel]
Local=10.65.223.238
Remote=10.65.223.239
Key=1.2.5.103
SerializeTunneledPackets=true
Independent=true
FooOverUDP=yes
FOUDestinationPort=55556
FOUSourcePort=1001

View file

@ -0,0 +1,13 @@
[NetDev]
Name=gretap96
Kind=gretap
[Tunnel]
Local=10.65.223.238
Remote=10.65.223.239
Key=1.2.5.106
SerializeTunneledPackets=true
Independent=true
FooOverUDP=yes
FOUDestinationPort=55556

View file

@ -0,0 +1,12 @@
[NetDev]
Name=ipiptun96
Kind=ipip
MTUBytes=1480
[Tunnel]
Local=192.168.223.238
Remote=192.169.224.239
Independent=true
FooOverUDP=yes
FOUDestinationPort=55555

View file

@ -0,0 +1,8 @@
[NetDev]
Name=fou98
Kind=fou
[FooOverUDP]
Encapsulation=FooOverUDP
Port=55556
Protocol=GRE

View file

@ -0,0 +1,8 @@
[NetDev]
Name=fou99
Kind=fou
[FooOverUDP]
Encapsulation=FooOverUDP
Port=55555
Protocol=ipip

View file

@ -0,0 +1,11 @@
[NetDev]
Name=sittun96
Kind=sit
[Tunnel]
Local=10.65.223.238
Remote=10.65.223.239
Independent=true
FooOverUDP=yes
FOUDestinationPort=55555

View file

@ -5,3 +5,5 @@ Kind=gre
[Tunnel]
Local=any
Remote=10.65.223.239
Key=104
SerializeTunneledPackets=false

View file

@ -5,3 +5,5 @@ Kind=gre
[Tunnel]
Local=10.65.223.238
Remote=any
Key=105
SerializeTunneledPackets=false

View file

@ -5,3 +5,6 @@ Kind=gre
[Tunnel]
Local=10.65.223.238
Remote=10.65.223.239
InputKey=1.2.3.103
OutputKey=1.2.4.103
SerializeTunneledPackets=true

View file

@ -5,3 +5,5 @@ Kind=gretap
[Tunnel]
Local=any
Remote=10.65.223.239
Key=107
SerializeTunneledPackets=true

View file

@ -5,3 +5,5 @@ Kind=gretap
[Tunnel]
Local=10.65.223.238
Remote=10.65.223.239
Key=106
SerializeTunneledPackets=true

View file

@ -201,8 +201,10 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
'erspan98',
'erspan99',
'geneve99',
'gretap96',
'gretap98',
'gretap99',
'gretun96',
'gretun97',
'gretun98',
'gretun99',
@ -214,6 +216,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
'ip6tnl97',
'ip6tnl98',
'ip6tnl99',
'ipiptun96',
'ipiptun97',
'ipiptun98',
'ipiptun99',
@ -221,6 +224,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
'isataptun99',
'macvlan99',
'macvtap99',
'sittun96',
'sittun97',
'sittun98',
'sittun99',
@ -257,6 +261,12 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
'25-bridge.netdev',
'25-erspan-tunnel-local-any.netdev',
'25-erspan-tunnel.netdev',
'25-fou-gretap.netdev',
'25-fou-gre.netdev',
'25-fou-ipip.netdev',
'25-fou-ipproto-gre.netdev',
'25-fou-ipproto-ipip.netdev',
'25-fou-sit.netdev',
'25-geneve.netdev',
'25-gretap-tunnel-local-any.netdev',
'25-gretap-tunnel.netdev',
@ -572,12 +582,24 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
output = subprocess.check_output(['ip', '-d', 'link', 'show', 'gretun99']).rstrip().decode('utf-8')
print(output)
self.assertRegex(output, 'gre remote 10.65.223.239 local 10.65.223.238 dev dummy98')
self.assertRegex(output, 'ikey 1.2.3.103')
self.assertRegex(output, 'okey 1.2.4.103')
self.assertRegex(output, 'iseq')
self.assertRegex(output, 'oseq')
output = subprocess.check_output(['ip', '-d', 'link', 'show', 'gretun98']).rstrip().decode('utf-8')
print(output)
self.assertRegex(output, 'gre remote 10.65.223.239 local any dev dummy98')
self.assertRegex(output, 'ikey 0.0.0.104')
self.assertRegex(output, 'okey 0.0.0.104')
self.assertNotRegex(output, 'iseq')
self.assertNotRegex(output, 'oseq')
output = subprocess.check_output(['ip', '-d', 'link', 'show', 'gretun97']).rstrip().decode('utf-8')
print(output)
self.assertRegex(output, 'gre remote any local 10.65.223.238 dev dummy98')
self.assertRegex(output, 'ikey 0.0.0.105')
self.assertRegex(output, 'okey 0.0.0.105')
self.assertNotRegex(output, 'iseq')
self.assertNotRegex(output, 'oseq')
def test_ip6gre_tunnel(self):
self.copy_unit_to_networkd_unit_path('12-dummy.netdev', '25-ip6gre-tunnel.netdev', 'ip6gretun.network',
@ -611,9 +633,17 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
output = subprocess.check_output(['ip', '-d', 'link', 'show', 'gretap99']).rstrip().decode('utf-8')
print(output)
self.assertRegex(output, 'gretap remote 10.65.223.239 local 10.65.223.238 dev dummy98')
self.assertRegex(output, 'ikey 0.0.0.106')
self.assertRegex(output, 'okey 0.0.0.106')
self.assertRegex(output, 'iseq')
self.assertRegex(output, 'oseq')
output = subprocess.check_output(['ip', '-d', 'link', 'show', 'gretap98']).rstrip().decode('utf-8')
print(output)
self.assertRegex(output, 'gretap remote 10.65.223.239 local any dev dummy98')
self.assertRegex(output, 'ikey 0.0.0.107')
self.assertRegex(output, 'okey 0.0.0.107')
self.assertRegex(output, 'iseq')
self.assertRegex(output, 'oseq')
def test_ip6gretap_tunnel(self):
self.copy_unit_to_networkd_unit_path('12-dummy.netdev', '25-ip6gretap-tunnel.netdev', 'ip6gretap.network',
@ -747,11 +777,18 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
output = subprocess.check_output(['ip', '-d', 'link', 'show', 'erspan99']).rstrip().decode('utf-8')
print(output)
self.assertRegex(output, 'erspan remote 172.16.1.100 local 172.16.1.200')
self.assertRegex(output, '101')
self.assertRegex(output, 'ikey 0.0.0.101')
self.assertRegex(output, 'okey 0.0.0.101')
self.assertRegex(output, 'iseq')
self.assertRegex(output, 'oseq')
output = subprocess.check_output(['ip', '-d', 'link', 'show', 'erspan98']).rstrip().decode('utf-8')
print(output)
self.assertRegex(output, 'erspan remote 172.16.1.100 local any')
self.assertRegex(output, '102')
self.assertRegex(output, 'ikey 0.0.0.102')
self.assertRegex(output, 'okey 0.0.0.102')
self.assertRegex(output, 'iseq')
self.assertRegex(output, 'oseq')
def test_tunnel_independent(self):
self.copy_unit_to_networkd_unit_path('25-ipip-tunnel-independent.netdev')
@ -759,6 +796,43 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
self.assertTrue(self.link_exits('ipiptun99'))
@expectedFailureIfModuleIsNotAvailable('fou')
def test_fou(self):
# The following redundant check is necessary for CentOS CI.
# Maybe, error handling in lookup_id() in sd-netlink/generic-netlink.c needs to be updated.
self.assertTrue(is_module_available('fou'))
self.copy_unit_to_networkd_unit_path('25-fou-ipproto-ipip.netdev', '25-fou-ipproto-gre.netdev',
'25-fou-ipip.netdev', '25-fou-sit.netdev',
'25-fou-gre.netdev', '25-fou-gretap.netdev')
self.start_networkd()
self.assertTrue(self.link_exits('ipiptun96'))
self.assertTrue(self.link_exits('sittun96'))
self.assertTrue(self.link_exits('gretun96'))
self.assertTrue(self.link_exits('gretap96'))
output = subprocess.check_output(['ip', 'fou', 'show']).rstrip().decode('utf-8')
print(output)
self.assertRegex(output, 'port 55555 ipproto 4')
self.assertRegex(output, 'port 55556 ipproto 47')
output = subprocess.check_output(['ip', '-d', 'link', 'show', 'ipiptun96']).rstrip().decode('utf-8')
print(output)
self.assertRegex(output, 'encap fou encap-sport auto encap-dport 55555')
output = subprocess.check_output(['ip', '-d', 'link', 'show', 'sittun96']).rstrip().decode('utf-8')
print(output)
self.assertRegex(output, 'encap fou encap-sport auto encap-dport 55555')
output = subprocess.check_output(['ip', '-d', 'link', 'show', 'gretun96']).rstrip().decode('utf-8')
print(output)
self.assertRegex(output, 'encap fou encap-sport 1001 encap-dport 55556')
output = subprocess.check_output(['ip', '-d', 'link', 'show', 'gretap96']).rstrip().decode('utf-8')
print(output)
self.assertRegex(output, 'encap fou encap-sport auto encap-dport 55556')
subprocess.call(['ip', 'fou', 'del', 'port', '55555'])
subprocess.call(['ip', 'fou', 'del', 'port', '55556'])
def test_vxlan(self):
self.copy_unit_to_networkd_unit_path('25-vxlan.netdev', 'vxlan.network', '11-dummy.netdev')
self.start_networkd()