Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add L2TP support #114

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 49 additions & 3 deletions doc/netplan.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
"~".
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:``

Expand Down
47 changes: 45 additions & 2 deletions src/networkd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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));
Expand All @@ -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
Expand Down
25 changes: 24 additions & 1 deletion src/parse.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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}
};

Expand Down
20 changes: 20 additions & 0 deletions src/parse.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ typedef enum {
TUNNEL_MODE_GRETAP = 101,
TUNNEL_MODE_IP6GRETAP = 102,

lxnt marked this conversation as resolved.
Show resolved Hide resolved
TUNNEL_MODE_L2TP = 104,
_TUNNEL_MODE_MAX,
} tunnel_mode;

Expand All @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down
32 changes: 32 additions & 0 deletions src/validation.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down
Loading