diff --git a/doc/netplan.md b/doc/netplan.md index c46e052ea..bb45a9bfd 100644 --- a/doc/netplan.md +++ b/doc/netplan.md @@ -395,10 +395,10 @@ client processes as specified in the netplan YAML. ``NetworkManager`` backends. ``use-domains`` (scalar) - : Takes a boolean, or the special value "route". When true, the domain + : Takes a boolean, or the special value "route". When true, the domain name received from the DHCP server will be used as DNS search domain over this link, similar to the effect of the Domains= setting. If set - to "route", the domain name received from the DHCP server will be + to "route", the domain name received from the DHCP server will be used for routing DNS queries only, but not for searching, similar to the effect of the Domains= setting when the argument is prefixed with "~". @@ -823,7 +823,8 @@ more general information about tunnels. : Defines the tunnel mode. Valid options are ``sit``, ``gre``, ``ip6gre``, ``ipip``, ``ipip6``, ``ip6ip6``, ``vti``, and ``vti6``. Additionally, - the ``networkd`` backend also supports ``gretap`` and ``ip6gretap`` modes. + the ``networkd`` backend also supports ``gretap``, ``ip6gretap`` + and ``l2tp`` modes. In addition, the ``NetworkManager`` backend supports ``isatap`` tunnels. ``local`` (scalar) @@ -874,6 +875,51 @@ Examples: : Alternate name for the ``key`` field. See above. +L2TP-specific keys: + ``local`` (scalar) + : The IP address of the local interface. Takes an IP address, or the special + values "auto", "static", or "dynamic". When an address is set, then the local + interface must have the address. If "auto", then one of the addresses on the local + interface is used. Similarly, if "static" or "dynamic" is set, then one of the static + or dynamic addresses on the local interface is used. Defaults to "auto". + + ``peer-tunnel-id`` (scalar) + : Peer tunnel id, required. + + ``local-tunnel-id`` (scalar) + : Local tunnel id, required. + + ``encapsulation-type`` (scalar) + : The encapsulation type of the tunnel. "udp" or "ip". + + ``udp-source-port`` (scalar) + : The UDP source port. Required for udp encapsulation type. + + ``udp-destination-port`` (scalar) + : The UDP destination port. Required for udp encapsulation type. + + ``udp-checksum`` (scalar) + : When true, specifies if UDP checksum is calculated for transmitted packets over IPv4. + + ``udp6-checksum-tx`` (scalar) + : When false, skip UDP checksum calculation for transmitted packets over IPv6. + + ``udp6-checksum-rx`` (scalar) + : When false, skip UDP checksum calculation for received packets over IPv6. + + ``session-name`` (scalar) + : Session name, required. + + ``session-id`` (scalar) + : Local session id, required. + + ``peer-session-id`` (scalar) + : Peer session id, required. + + ``l2-specific-header`` (scalar) + : layer2specific header type of the session. One of "none" or "default". + Defaults to "default". + ## Properties for device type ``vlans:`` diff --git a/src/networkd.c b/src/networkd.c index 3ac3dce89..5246b6b8a 100644 --- a/src/networkd.c +++ b/src/networkd.c @@ -122,6 +122,42 @@ write_tunnel_params(GString* s, net_definition* def) g_string_free(params, TRUE); } +static void +write_l2tp_params(GString* s, net_definition* def) +{ + GString *params = NULL; + GString *session = NULL; + + params = g_string_sized_new(200); + + g_string_append_printf(params, "TunnelId=%u\n", def->l2tp.local_tunnel_id); + g_string_append_printf(params, "PeerTunnelId=%u\n", def->l2tp.peer_tunnel_id); + if (def->tunnel.local_ip) + g_string_append_printf(params, "Local=%s\n", def->tunnel.local_ip); + g_string_append_printf(params, "Remote=%s\n", def->tunnel.remote_ip); + g_string_append_printf(params, "EncapsulationType=%s\n", def->l2tp.encapsulation_type); + if (!g_ascii_strcasecmp(def->l2tp.encapsulation_type, "udp")) { + g_string_append_printf(params, "UDPSourcePort=%u\n", def->l2tp.udp_source_port); + g_string_append_printf(params, "DestinationPort=%u\n", def->l2tp.udp_destination_port); + g_string_append_printf(params, "UDPChecksum=%s\n", def->l2tp.udp_checksum ? "true" : "false"); + g_string_append_printf(params, "UDP6ZeroChecksumTx=%s\n", def->l2tp.udp6_checksum_tx ? "true" : "false"); + g_string_append_printf(params, "UDP6ZeroChecksumRx=%s\n", def->l2tp.udp6_checksum_rx ? "true" : "false"); + } + + g_string_append_printf(s, "\n[L2TP]\n%s", params->str); + g_string_free(params, TRUE); + + session = g_string_sized_new(200); + g_string_append_printf(session, "Name=%s\n", def->l2tp.session_name); + g_string_append_printf(session, "SessionId=%u\n", def->l2tp.session_id); + g_string_append_printf(session, "PeerSessonID=%u\n", def->l2tp.peer_session_id); + if (def->l2tp.l2_specific_header) + g_string_append_printf(session, "Layer2SpecifiHeader=%s\n", def->l2tp.l2_specific_header); + + g_string_append_printf(s, "\n[L2TPSession]\n%s", session->str); + g_string_free(session, TRUE); +} + static void write_link_file(net_definition* def, const char* rootdir, const char* path) { @@ -290,6 +326,7 @@ write_netdev_file(net_definition* def, const char* rootdir, const char* path) case TUNNEL_MODE_SIT: case TUNNEL_MODE_VTI: case TUNNEL_MODE_VTI6: + case TUNNEL_MODE_L2TP: g_string_append_printf(s, "Kind=%s\n", tunnel_mode_to_string(def->tunnel.mode)); @@ -305,8 +342,14 @@ write_netdev_file(net_definition* def, const char* rootdir, const char* path) g_assert_not_reached(); // LCOV_EXCL_STOP } - - write_tunnel_params(s, def); + switch (def->tunnel.mode) { + case TUNNEL_MODE_L2TP: + write_l2tp_params(s, def); + break; + default: + write_tunnel_params(s, def); + break; + } break; // LCOV_EXCL_START diff --git a/src/parse.c b/src/parse.c index 6e1ddddd7..84f207329 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1430,8 +1430,15 @@ handle_tunnel_addr(yaml_document_t* doc, yaml_node_t* node, const void* data, GE g_autofree char* addr = NULL; char* prefix_len; - /* split off /prefix_len */ addr = g_strdup(scalar(node)); + + /* L2TP-specific forms */ + if (g_ascii_strcasecmp(addr, "auto") == 0 || + g_ascii_strcasecmp(addr, "static") == 0 || + g_ascii_strcasecmp(addr, "dynamic") == 0) + return handle_netdef_str(doc, node, data, error); + + /* split off /prefix_len */ prefix_len = strrchr(addr, '/'); if (prefix_len) return yaml_error(node, error, "address '%s' should not include /prefixlength", scalar(node)); @@ -1625,6 +1632,22 @@ const mapping_entry_handler tunnel_def_handlers[] = { */ {"key", YAML_NO_NODE, handle_tunnel_key_mapping}, {"keys", YAML_NO_NODE, handle_tunnel_key_mapping}, + + /* l2tp; reuses tunnel.local_ip and tunnel.remote_ip*/ + {"local-tunnel-id", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(l2tp.local_tunnel_id)}, + {"peer-tunnel-id", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(l2tp.peer_tunnel_id)}, + {"encapsulation-type", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(l2tp.encapsulation_type)}, + {"udp-source-port", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(l2tp.udp_source_port)}, + {"udp-destination-port", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(l2tp.udp_destination_port)}, + {"udp-checksum", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(l2tp.udp_checksum)}, + {"udp6-checksum-tx", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(l2tp.udp6_checksum_tx)}, + {"udp6-checksum-rx", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(l2tp.udp6_checksum_rx)}, + + /* l2tp session*/ + {"session-name", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(l2tp.session_name)}, + {"session-id", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(l2tp.session_id)}, + {"peer-session-id", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(l2tp.peer_session_id)}, + {"l2-specific-header", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(l2tp.l2_specific_header)}, {NULL} }; diff --git a/src/parse.h b/src/parse.h index 9ec09e88d..9dc311945 100644 --- a/src/parse.h +++ b/src/parse.h @@ -98,6 +98,7 @@ typedef enum { TUNNEL_MODE_GRETAP = 101, TUNNEL_MODE_IP6GRETAP = 102, + TUNNEL_MODE_L2TP = 104, _TUNNEL_MODE_MAX, } tunnel_mode; @@ -116,6 +117,7 @@ tunnel_mode_table[_TUNNEL_MODE_MAX] = { [TUNNEL_MODE_GRETAP] = "gretap", [TUNNEL_MODE_IP6GRETAP] = "ip6gretap", + [TUNNEL_MODE_L2TP] = "l2tp", }; struct optional_address_option { @@ -282,6 +284,24 @@ typedef struct net_definition { char *output_key; } tunnel; + struct { + /* l2tp */ + guint peer_tunnel_id; + guint local_tunnel_id; + char *encapsulation_type; + guint udp_source_port; + guint udp_destination_port; + gboolean udp_checksum; + gboolean udp6_checksum_tx; + gboolean udp6_checksum_rx; + + /* l2tp session */ + char *session_name; + guint session_id; + guint peer_session_id; + char *l2_specific_header; + } l2tp; + authentication_settings auth; gboolean has_auth; } net_definition; diff --git a/src/validation.c b/src/validation.c index 7731be49a..3585d2dc6 100644 --- a/src/validation.c +++ b/src/validation.c @@ -83,6 +83,35 @@ validate_tunnel_grammar(net_definition* nd, yaml_node_t* node, GError** error) return yaml_error(node, error, "%s: 'remote' must be a valid IPv6 address for this tunnel type", nd->id); break; + case TUNNEL_MODE_L2TP: + if (!nd->l2tp.local_tunnel_id) + return yaml_error(node, error, "%s: local_tunnel_id is required.", nd->id); + if (!nd->l2tp.peer_tunnel_id) + return yaml_error(node, error, "%s: peer_tunnel_id is required.", nd->id); + if (!nd->l2tp.session_name) + return yaml_error(node, error, "%s: session_name is required.", nd->id); + if (!nd->l2tp.encapsulation_type) + return yaml_error(node, error, "%s: encapsulation_type is required.", nd->id); + if (g_ascii_strcasecmp(nd->l2tp.encapsulation_type, "udp") == 0) { + if (nd->l2tp.udp_source_port == 0 || nd->l2tp.udp_source_port > 65535) + return yaml_error(node, error, "%s: udp_source_port out of range.", nd->id); + if (nd->l2tp.udp_destination_port == 0 || nd->l2tp.udp_destination_port > 65535) + return yaml_error(node, error, "%s: udp_destination_port out of range.", nd->id); + } else { + if (g_ascii_strcasecmp(nd->l2tp.encapsulation_type, "ip") != 0) + return yaml_error(node, error, "%s: encapsulation_type must be 'ip' or 'udp'.", nd->id); + } + if (!nd->l2tp.session_id) + return yaml_error(node, error, "%s: session_id is required.", nd->id); + if (!nd->l2tp.peer_session_id) + return yaml_error(node, error, "%s: peer_session_id is required.", nd->id); + if (nd->l2tp.l2_specific_header && + g_ascii_strcasecmp(nd->l2tp.l2_specific_header, "default") != 0 && + g_ascii_strcasecmp(nd->l2tp.l2_specific_header, "none") != 0) + return yaml_error(node, error, "%s: l2_specific_header must be 'default' or 'none'.", nd->id); + + break; + default: if (!is_ip4_address(nd->tunnel.local_ip)) return yaml_error(node, error, "%s: 'local' must be a valid IPv4 address for this tunnel type", nd->id); @@ -103,6 +132,7 @@ validate_tunnel_backend_rules(net_definition* nd, yaml_node_t* node, GError** er switch (nd->tunnel.mode) { case TUNNEL_MODE_VTI: case TUNNEL_MODE_VTI6: + case TUNNEL_MODE_L2TP: break; /* TODO: Remove this exception and fix ISATAP handling with the @@ -133,12 +163,14 @@ validate_tunnel_backend_rules(net_definition* nd, yaml_node_t* node, GError** er case TUNNEL_MODE_GRETAP: case TUNNEL_MODE_IP6GRETAP: + case TUNNEL_MODE_L2TP: return yaml_error(node, error, "%s: %s tunnel mode is not supported by NetworkManager", nd->id, g_ascii_strup(tunnel_mode_to_string(nd->tunnel.mode), -1)); break; + default: if (nd->tunnel.input_key) return yaml_error(node, error, "%s: 'input-key' is not required for this tunnel type", nd->id); diff --git a/tests/generator/test_tunnels.py b/tests/generator/test_tunnels.py index 6719efb45..3630b3714 100644 --- a/tests/generator/test_tunnels.py +++ b/tests/generator/test_tunnels.py @@ -18,33 +18,27 @@ from .base import TestBase - -def prepare_config_for_mode(renderer, mode, key=None): - config = """network: - version: 2 - renderer: {} -""".format(renderer) - - if mode == "ip6gre" \ - or mode == "ip6ip6" \ - or mode == "vti6" \ - or mode == "ipip6" \ - or mode == "ip6gretap": +def prepare_config_for_mode(renderer, mode, key=None, **kwargs): + if mode in ("ip6gre", "ip6ip6", "vti6", "ipip6","ip6gretap"): local_ip = "fe80::dead:beef" remote_ip = "2001:fe:ad:de:ad:be:ef:1" else: local_ip = "10.10.10.10" remote_ip = "20.20.20.20" - config += """ + kwargs['local'] = kwargs.get('local', local_ip) + kwargs['remote'] = kwargs.get('remote', remote_ip) + + config = """ +network: + version: 2 + renderer: {} tunnels: tun0: mode: {} - local: {} - remote: {} addresses: [ 15.15.15.15/24 ] gateway4: 20.20.20.21 -""".format(mode, local_ip, remote_ip) +""".format(renderer, mode) # Handle key/keys as str or dict as required by the test if type(key) is str: @@ -58,6 +52,9 @@ def prepare_config_for_mode(renderer, mode, key=None): output: {} """.format(key['input'], key['output']) + for k,v in kwargs.items(): + config += ' {}: {}\n'.format(k.replace('_', '-'), v) + return config @@ -415,6 +412,344 @@ def test_ip6gretap(self): ConfigureWithoutCarrier=yes '''}) + def test_vti_invalid_local(self): + """[networkd] Check that L2TP-specific local does not leak to other tunnel types""" + config = prepare_config_for_mode('networkd', 'vti', local='any') + out = self.generate(config, expect_fail=True) + self.assertIn("Error in network definition: malformed address 'any'", out) + + def test_l2tp_udp(self): + """[networkd] Validate generation of L2TP/UDP tunnels""" + config = prepare_config_for_mode('networkd', 'l2tp', + peer_tunnel_id = 1000, + local_tunnel_id = 2000, + encapsulation_type = 'udp', + udp_source_port = 5000, + udp_destination_port = 6000, + session_name = 'testsession', + session_id = 4000, + peer_session_id = 3000, + local = 'dynamic', + l2_specific_header = 'none') + self.generate(config) + self.assert_networkd({'tun0.netdev': '''[NetDev] +Name=tun0 +Kind=l2tp + +[L2TP] +TunnelId=2000 +PeerTunnelId=1000 +Local=dynamic +Remote=20.20.20.20 +EncapsulationType=udp +UDPSourcePort=5000 +DestinationPort=6000 +UDPChecksum=false +UDP6ZeroChecksumTx=false +UDP6ZeroChecksumRx=false + +[L2TPSession] +Name=testsession +SessionId=4000 +PeerSessonID=3000 +Layer2SpecifiHeader=none +''', + 'tun0.network': '''[Match] +Name=tun0 + +[Network] +LinkLocalAddressing=ipv6 +Address=15.15.15.15/24 +Gateway=20.20.20.21 +ConfigureWithoutCarrier=yes +'''}) + + def test_l2tp_udpv6(self): + """[networkd] [l2tp] Validate generation of L2TP/UDP6 tunnels""" + config = prepare_config_for_mode('networkd', 'l2tp', + peer_tunnel_id = 1000, + local_tunnel_id = 2000, + encapsulation_type = 'udp', + udp_source_port = 5000, + udp_destination_port = 6000, + session_name = 'testsession', + session_id = 4000, + peer_session_id = 3000, + local = '2001:dead:beef::2', + l2_specific_header = 'none') + + self.generate(config) + self.assert_networkd({'tun0.netdev': '''[NetDev] +Name=tun0 +Kind=l2tp + +[L2TP] +TunnelId=2000 +PeerTunnelId=1000 +Local=2001:dead:beef::2 +Remote=20.20.20.20 +EncapsulationType=udp +UDPSourcePort=5000 +DestinationPort=6000 +UDPChecksum=false +UDP6ZeroChecksumTx=false +UDP6ZeroChecksumRx=false + +[L2TPSession] +Name=testsession +SessionId=4000 +PeerSessonID=3000 +Layer2SpecifiHeader=none +''', + 'tun0.network': '''[Match] +Name=tun0 + +[Network] +LinkLocalAddressing=ipv6 +Address=15.15.15.15/24 +Gateway=20.20.20.21 +ConfigureWithoutCarrier=yes +'''}) + + def test_l2tp_ip(self): + """[networkd] [l2tp] Validate generation of L2TP/IP tunnels""" + config = prepare_config_for_mode('networkd', 'l2tp', + peer_tunnel_id = 1000, + local_tunnel_id = 2000, + encapsulation_type = 'ip', + session_name = 'testsession', + session_id = 4000, + peer_session_id = 3000, + local = 'dynamic', + l2_specific_header = 'none') + self.generate(config) + self.assert_networkd({'tun0.netdev': '''[NetDev] +Name=tun0 +Kind=l2tp + +[L2TP] +TunnelId=2000 +PeerTunnelId=1000 +Local=dynamic +Remote=20.20.20.20 +EncapsulationType=ip + +[L2TPSession] +Name=testsession +SessionId=4000 +PeerSessonID=3000 +Layer2SpecifiHeader=none +''', + 'tun0.network': '''[Match] +Name=tun0 + +[Network] +LinkLocalAddressing=ipv6 +Address=15.15.15.15/24 +Gateway=20.20.20.21 +ConfigureWithoutCarrier=yes +'''}) + + def test_l2tp_ip_ipv4(self): + """[networkd] [l2tp] Validate generation of L2TP/IP tunnels""" + config = prepare_config_for_mode('networkd', 'l2tp', + peer_tunnel_id = 1000, + local_tunnel_id = 2000, + encapsulation_type = 'ip', + session_name = 'testsession', + session_id = 4000, + peer_session_id = 3000, + l2_specific_header = 'none') + self.generate(config) + self.assert_networkd({'tun0.netdev': '''[NetDev] +Name=tun0 +Kind=l2tp + +[L2TP] +TunnelId=2000 +PeerTunnelId=1000 +Local=10.10.10.10 +Remote=20.20.20.20 +EncapsulationType=ip + +[L2TPSession] +Name=testsession +SessionId=4000 +PeerSessonID=3000 +Layer2SpecifiHeader=none +''', + 'tun0.network': '''[Match] +Name=tun0 + +[Network] +LinkLocalAddressing=ipv6 +Address=15.15.15.15/24 +Gateway=20.20.20.21 +ConfigureWithoutCarrier=yes +'''}) + + def test_l2tp_noencap_fail(self): + """[networkd] [l2tp] Show error on missing encapsulation_type""" + config = prepare_config_for_mode('networkd', 'l2tp', + peer_tunnel_id = 1000, + local_tunnel_id = 2000, + session_name = 'testsession', + session_id = 4000, + peer_session_id = 3000, + l2_specific_header = 'none') + out = self.generate(config, expect_fail=True) + self.assertIn("Error in network definition: tun0: encapsulation_type is required.", out) + + def test_l2tp_bogus_encap_fail(self): + """[networkd] [l2tp] Show error on bogus encapsulation_type""" + config = prepare_config_for_mode('networkd', 'l2tp', + peer_tunnel_id = 1000, + local_tunnel_id = 2000, + encapsulation_type = 'bogus', + session_name = 'testsession', + session_id = 4000, + peer_session_id = 3000, + l2_specific_header = 'none') + out = self.generate(config, expect_fail=True) + self.assertIn("Error in network definition: tun0: encapsulation_type must be 'ip' or 'udp'.", out) + + def test_l2tp_bogus_specheader_fail(self): + """[networkd] [l2tp] Show error on bogus l2tp_specific_header""" + config = prepare_config_for_mode('networkd', 'l2tp', + peer_tunnel_id = 1000, + local_tunnel_id = 2000, + encapsulation_type = 'ip', + session_name = 'testsession', + session_id = 4000, + peer_session_id = 3000, + l2_specific_header = 'bogus') + out = self.generate(config, expect_fail=True) + self.assertIn("Error in network definition: tun0: l2_specific_header must be 'default' or 'none'.", out) + + def test_l2tp_local_prefix_fail(self): + """[networkd] [l2tp] Show error on prefix present on local_ip""" + config = prepare_config_for_mode('networkd', 'l2tp', + peer_tunnel_id = 1000, + local_tunnel_id = 2000, + encapsulation_type = 'ip', + session_name = 'testsession', + session_id = 4000, + peer_session_id = 3000, + local="1.2.3.4/5", + l2_specific_header = 'none') + out = self.generate(config, expect_fail=True) + self.assertIn("Error in network definition: address '1.2.3.4/5' should not include /prefixlength", out) + + def test_l2tp_local_bogus_fail(self): + """[networkd] [l2tp] Show error on bogus local_ip""" + config = prepare_config_for_mode('networkd', 'l2tp', + peer_tunnel_id = 1000, + local_tunnel_id = 2000, + encapsulation_type = 'ip', + session_name = 'testsession', + session_id = 4000, + peer_session_id = 3000, + local="1.2.3.400", + l2_specific_header = 'none') + out = self.generate(config, expect_fail=True) + self.assertIn("malformed address '1.2.3.400', must be X.X.X.X or X:X:X:X:X:X:X:X", out) + + def test_l2tp_no_session_name_fail(self): + """[networkd] [l2tp] Show error on missing session name""" + config = prepare_config_for_mode('networkd', 'l2tp', + peer_tunnel_id = 1000, + local_tunnel_id = 2000, + encapsulation_type = 'ip', + session_id = 4000, + peer_session_id = 3000, + local="1.2.3.4", + l2_specific_header = 'none') + out = self.generate(config, expect_fail=True) + self.assertIn("Error in network definition: tun0: session_name is required.", out) + + def test_l2tp_no_session_id_fail(self): + """[networkd] [l2tp] Show error on missing session id""" + config = prepare_config_for_mode('networkd', 'l2tp', + peer_tunnel_id = 1000, + local_tunnel_id = 2000, + encapsulation_type = 'ip', + session_name = 'testsession', + peer_session_id = 3000, + local="1.2.3.4", + l2_specific_header = 'none') + out = self.generate(config, expect_fail=True) + self.assertIn("Error in network definition: tun0: session_id is required.", out) + + def test_l2tp_no_peer_session_id_fail(self): + """[networkd] [l2tp] Show error on missing peer_session id""" + config = prepare_config_for_mode('networkd', 'l2tp', + peer_tunnel_id = 1000, + local_tunnel_id = 2000, + encapsulation_type = 'ip', + session_name = 'testsession', + session_id = 4000, + local="1.2.3.4", + l2_specific_header = 'none') + out = self.generate(config, expect_fail=True) + self.assertIn("Error in network definition: tun0: peer_session_id is required.", out) + + def test_l2tp_no_peer_tunnel_id_fail(self): + """[networkd] [l2tp] Show error on missing peer_tunnel_id""" + config = prepare_config_for_mode('networkd', 'l2tp', + local_tunnel_id = 2000, + encapsulation_type = 'ip', + session_name = 'testsession', + session_id = 4000, + peer_session_id = 3000, + local="1.2.3.4", + l2_specific_header = 'none') + out = self.generate(config, expect_fail=True) + self.assertIn("Error in network definition: tun0: peer_tunnel_id is required.", out) + + def test_l2tp_no_local_tunnel_id_fail(self): + """[networkd] [l2tp] Show error on missing local_tunnel_id""" + config = prepare_config_for_mode('networkd', 'l2tp', + peer_tunnel_id = 1000, + encapsulation_type = 'ip', + session_name = 'testsession', + session_id = 4000, + peer_session_id = 3000, + local="1.2.3.4", + l2_specific_header = 'none') + out = self.generate(config, expect_fail=True) + self.assertIn("Error in network definition: tun0: local_tunnel_id is required", out) + + def test_l2tp_bad_udp_source_port_fail(self): + """[networkd] [l2tp] Show error on bad udp_source_port""" + config = prepare_config_for_mode('networkd', 'l2tp', + peer_tunnel_id = 1000, + local_tunnel_id = 2000, + encapsulation_type = 'udp', + udp_source_port = 500000, + udp_destination_port = 6000, + session_name = 'testsession', + session_id = 4000, + peer_session_id = 3000, + local = '2001:dead:beef::2', + l2_specific_header = 'none') + out = self.generate(config, expect_fail=True) + self.assertIn("Error in network definition: tun0: udp_source_port out of range.", out) + + def test_l2tp_udp_destination_port_fail(self): + """[networkd] [l2tp] Show error on bad udp_destination_port""" + config = prepare_config_for_mode('networkd', 'l2tp', + peer_tunnel_id = 1000, + local_tunnel_id = 2000, + encapsulation_type = 'udp', + udp_source_port = 5000, + udp_destination_port = 600000, + session_name = 'testsession', + session_id = 4000, + peer_session_id = 3000, + local = '2001:dead:beef::2', + l2_specific_header = 'none') + out = self.generate(config, expect_fail=True) + self.assertIn("Error in network definition: tun0: udp_destination_port out of range.", out) class TestNetworkManager(TestBase):