Skip to content

Commit

Permalink
Add L2TP support
Browse files Browse the repository at this point in the history
  • Loading branch information
lxnt committed Dec 5, 2019
1 parent 50ac74f commit 315d28c
Show file tree
Hide file tree
Showing 6 changed files with 539 additions and 4 deletions.
48 changes: 47 additions & 1 deletion doc/netplan.md
Original file line number Diff line number Diff line change
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_ip`` (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
47 changes: 47 additions & 0 deletions src/parse.c
Original file line number Diff line number Diff line change
Expand Up @@ -1516,6 +1516,36 @@ handle_tunnel_key_mapping(yaml_document_t* doc, yaml_node_t* node, const void* _
return ret;
}

static gboolean
handle_l2tp_local_addr(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error)
{
g_autofree char* addr = NULL;
char* prefix_len;

addr = g_strdup(scalar(node));

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));

/* is it an IPv4 address? */
if (is_ip4_address(addr))
return handle_netdef_ip4(doc, node, data, error);

/* is it an IPv6 address? */
if (is_ip6_address(addr))
return handle_netdef_ip6(doc, node, data, error);

return yaml_error(node, error, "malformed address '%s', must be X.X.X.X or X:X:X:X:X:X:X:X"
" or one of 'auto', 'static' or 'dynamic'.", scalar(node));
}

/****************************************************
* Grammar and handlers for network devices
****************************************************/
Expand Down Expand Up @@ -1625,6 +1655,23 @@ 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_ip", YAML_SCALAR_NODE, handle_l2tp_local_addr, NULL, netdef_offset(tunnel.local_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,

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

0 comments on commit 315d28c

Please sign in to comment.