networkd: Add support for ERSPAN tunnel

Please see: https://patchwork.ozlabs.org/patch/800327/
```
[NetDev]
Name=erspan-test
Kind=erspan

[Tunnel]
Independent=true
ERSPANIndex=123
Local = 172.16.1.200
Remote = 172.16.1.100
Key=101
SerializeTunneledPackets=true
```
This commit is contained in:
Susant Sahani 2018-11-26 17:20:09 +05:30 committed by Yu Watanabe
parent 5c6b51ff79
commit 2266864b04
12 changed files with 177 additions and 11 deletions

View file

@ -100,6 +100,11 @@
<row><entry><varname>gretap</varname></entry>
<entry>A Level 2 GRE tunnel over IPv4.</entry></row>
<row><entry><varname>erspan</varname></entry>
<entry>ERSPAN mirrors traffic on one or more source ports and delivers the mirrored traffic to one or more destination ports on another switch.
The traffic is encapsulated in generic routing encapsulation (GRE) and is therefore routable across a layer 3 network between the source switch
and the destination switch.</entry></row>
<row><entry><varname>ip6gre</varname></entry>
<entry>A Level 3 GRE tunnel over IPv6.</entry></row>
@ -919,6 +924,22 @@
applicable to SIT tunnels.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>SerializeTunneledPackets=</varname></term>
<listitem>
<para>Takes a boolean value. If set to yes, then packets are serialized. Only applies for ERSPAN tunnel.
Defaults to unset.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>ERSPANIndex=</varname></term>
<listitem>
<para>Specifies the ERSPAN index field for the interface, an integer in the range 1-1048575 associated with
the ERSPAN traffic's source port and direction. This field is mandatory.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>

View file

@ -464,6 +464,7 @@ foreach decl : [['IFLA_INET6_ADDR_GEN_MODE', 'linux/if_link.h'],
['IFLA_VTI_REMOTE', 'linux/if_tunnel.h', '#include <net/if.h>'],
['IFLA_IPTUN_ENCAP_DPORT', 'linux/if_tunnel.h', '#include <net/if.h>'],
['IFLA_GRE_ENCAP_DPORT', 'linux/if_tunnel.h', '#include <net/if.h>'],
['IFLA_GRE_ERSPAN_HWID', 'linux/if_tunnel.h', '#include <net/if.h>'],
['IFLA_BRIDGE_VLAN_INFO', 'linux/if_bridge.h'],
['IFLA_BRPORT_PROXYARP', 'linux/if_link.h'],
['IFLA_BRPORT_LEARNING_SYNC', 'linux/if_link.h'],

View file

@ -877,7 +877,7 @@ struct input_mask {
#define IFLA_IPTUN_MAX (__IFLA_IPTUN_MAX - 1)
#endif
#if !HAVE_IFLA_GRE_ENCAP_DPORT
#if !HAVE_IFLA_GRE_ERSPAN_HWID
#define IFLA_GRE_UNSPEC 0
#define IFLA_GRE_LINK 1
#define IFLA_GRE_IFLAGS 2
@ -896,8 +896,14 @@ struct input_mask {
#define IFLA_GRE_ENCAP_FLAGS 15
#define IFLA_GRE_ENCAP_SPORT 16
#define IFLA_GRE_ENCAP_DPORT 17
#define __IFLA_GRE_MAX 18
#define IFLA_GRE_COLLECT_METADATA 18
#define IFLA_GRE_IGNORE_DF 19
#define IFLA_GRE_FWMARK 20
#define IFLA_GRE_ERSPAN_INDEX 21
#define IFLA_GRE_ERSPAN_VER 22
#define IFLA_GRE_ERSPAN_DIR 23
#define IFLA_GRE_ERSPAN_HWID 24
#define __IFLA_GRE_MAX 25
#define IFLA_GRE_MAX (__IFLA_GRE_MAX - 1)
#endif

View file

@ -265,6 +265,7 @@ static const NLType rtnl_link_info_data_ipgre_types[] = {
[IFLA_GRE_ENCAP_FLAGS] = { .type = NETLINK_TYPE_U16 },
[IFLA_GRE_ENCAP_SPORT] = { .type = NETLINK_TYPE_U16 },
[IFLA_GRE_ENCAP_DPORT] = { .type = NETLINK_TYPE_U16 },
[IFLA_GRE_ERSPAN_INDEX] = { .type = NETLINK_TYPE_U32 },
};
static const NLType rtnl_link_info_data_ipvti_types[] = {
@ -321,6 +322,7 @@ static const char* const nl_union_link_info_data_table[] = {
[NL_UNION_LINK_INFO_DATA_VXLAN] = "vxlan",
[NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL] = "ipip",
[NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL] = "gre",
[NL_UNION_LINK_INFO_DATA_ERSPAN] = "erspan",
[NL_UNION_LINK_INFO_DATA_IPGRETAP_TUNNEL] = "gretap",
[NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL] = "ip6gre",
[NL_UNION_LINK_INFO_DATA_IP6GRETAP_TUNNEL] = "ip6gretap",
@ -360,6 +362,8 @@ static const NLTypeSystem rtnl_link_info_data_type_systems[] = {
.types = rtnl_link_info_data_iptun_types },
[NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types),
.types = rtnl_link_info_data_ipgre_types },
[NL_UNION_LINK_INFO_DATA_ERSPAN] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types),
.types = rtnl_link_info_data_ipgre_types },
[NL_UNION_LINK_INFO_DATA_IPGRETAP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types),
.types = rtnl_link_info_data_ipgre_types },
[NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types),

View file

@ -64,6 +64,7 @@ typedef enum NLUnionLinkInfoData {
NL_UNION_LINK_INFO_DATA_VXLAN,
NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL,
NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL,
NL_UNION_LINK_INFO_DATA_ERSPAN,
NL_UNION_LINK_INFO_DATA_IPGRETAP_TUNNEL,
NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL,
NL_UNION_LINK_INFO_DATA_IP6GRETAP_TUNNEL,

View file

@ -71,6 +71,8 @@ Tunnel.FOUDestinationPort, config_parse_ip_port, 0,
Tunnel.FOUSourcePort, config_parse_ip_port, 0, offsetof(Tunnel, encap_src_port)
Tunnel.Encapsulation, config_parse_fou_encap_type, 0, offsetof(Tunnel, fou_encap_type)
Tunnel.IPv6RapidDeploymentPrefix, config_parse_6rd_prefix, 0, 0
Tunnel.ERSPANIndex, config_parse_uint32, 0, offsetof(Tunnel, erspan_index)
Tunnel.SerializeTunneledPackets, config_parse_tristate, 0, offsetof(Tunnel, erspan_sequence)
FooOverUDP.Protocol, config_parse_uint8, 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)

View file

@ -64,6 +64,7 @@ const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX] = {
[NETDEV_KIND_WIREGUARD] = &wireguard_vtable,
[NETDEV_KIND_NETDEVSIM] = &netdevsim_vtable,
[NETDEV_KIND_FOU] = &foutnl_vtable,
[NETDEV_KIND_ERSPAN] = &erspan_vtable,
};
static const char* const netdev_kind_table[_NETDEV_KIND_MAX] = {
@ -94,6 +95,7 @@ static const char* const netdev_kind_table[_NETDEV_KIND_MAX] = {
[NETDEV_KIND_WIREGUARD] = "wireguard",
[NETDEV_KIND_NETDEVSIM] = "netdevsim",
[NETDEV_KIND_FOU] = "fou",
[NETDEV_KIND_ERSPAN] = "erspan",
};
DEFINE_STRING_TABLE_LOOKUP(netdev_kind, NetDevKind);

View file

@ -45,6 +45,7 @@ typedef enum NetDevKind {
NETDEV_KIND_WIREGUARD,
NETDEV_KIND_NETDEVSIM,
NETDEV_KIND_FOU,
NETDEV_KIND_ERSPAN,
_NETDEV_KIND_MAX,
_NETDEV_KIND_INVALID = -1
} NetDevKind;

View file

@ -173,6 +173,77 @@ static int netdev_gre_fill_message_create(NetDev *netdev, Link *link, sd_netlink
return r;
}
static int netdev_erspan_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
uint32_t ikey = 0;
uint32_t okey = 0;
uint16_t iflags = 0;
uint16_t oflags = 0;
Tunnel *t;
int r;
assert(netdev);
t = ERSPAN(netdev);
assert(t);
assert(IN_SET(t->family, AF_INET, AF_UNSPEC));
assert(m);
r = sd_netlink_message_append_u32(m, IFLA_GRE_ERSPAN_INDEX, t->erspan_index);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_ERSPAN_INDEX attribute: %m");
if (t->key != 0) {
ikey = okey = htobe32(t->key);
iflags |= GRE_KEY;
oflags |= GRE_KEY;
}
if (t->ikey != 0) {
ikey = htobe32(t->ikey);
iflags |= GRE_KEY;
}
if (t->okey != 0) {
okey = htobe32(t->okey);
oflags |= GRE_KEY;
}
if (t->erspan_sequence > 0) {
iflags |= GRE_SEQ;
oflags |= GRE_SEQ;
} else if (t->erspan_sequence == 0) {
iflags &= ~GRE_SEQ;
oflags &= ~GRE_SEQ;
}
r = sd_netlink_message_append_u32(m, IFLA_GRE_IKEY, ikey);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_IKEY attribute: %m");
r = sd_netlink_message_append_u32(m, IFLA_GRE_OKEY, okey);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_OKEY attribute: %m");
r = sd_netlink_message_append_u16(m, IFLA_GRE_IFLAGS, iflags);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_IFLAGS attribute: %m");
r = sd_netlink_message_append_u16(m, IFLA_GRE_OFLAGS, oflags);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_OFLAGS, attribute: %m");
r = sd_netlink_message_append_in_addr(m, IFLA_GRE_LOCAL, &t->local.in);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_LOCAL attribute: %m");
r = sd_netlink_message_append_in_addr(m, IFLA_GRE_REMOTE, &t->remote.in);
if (r < 0)
log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_REMOTE attribute: %m");
return r;
}
static int netdev_ip6gre_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
Tunnel *t;
int r;
@ -415,6 +486,9 @@ static int netdev_tunnel_verify(NetDev *netdev, const char *filename) {
case NETDEV_KIND_IP6TNL:
t = IP6TNL(netdev);
break;
case NETDEV_KIND_ERSPAN:
t = ERSPAN(netdev);
break;
default:
assert_not_reached("Invalid tunnel kind");
}
@ -427,10 +501,10 @@ static int netdev_tunnel_verify(NetDev *netdev, const char *filename) {
return -EINVAL;
}
if (IN_SET(netdev->kind, NETDEV_KIND_VTI, NETDEV_KIND_IPIP, NETDEV_KIND_SIT, NETDEV_KIND_GRE, NETDEV_KIND_GRETAP) &&
if (IN_SET(netdev->kind, NETDEV_KIND_VTI, NETDEV_KIND_IPIP, NETDEV_KIND_SIT, NETDEV_KIND_GRE, NETDEV_KIND_GRETAP, NETDEV_KIND_ERSPAN) &&
(t->family != AF_INET || in_addr_is_null(t->family, &t->local))) {
log_netdev_error(netdev,
"vti/ipip/sit/gre/gretap tunnel without a local IPv4 address configured in %s. Ignoring", filename);
"vti/ipip/sit/gre/gretap/erspan tunnel without a local IPv4 address configured in %s. Ignoring", filename);
return -EINVAL;
}
@ -453,6 +527,11 @@ static int netdev_tunnel_verify(NetDev *netdev, const char *filename) {
return -EINVAL;
}
if (netdev->kind == NETDEV_KIND_ERSPAN && (t->erspan_index >= (1 << 20) || t->erspan_index == 0)) {
log_netdev_error(netdev, "Invalid erspan index %d. Ignoring", t->erspan_index);
return -EINVAL;
}
return 0;
}
@ -729,6 +808,18 @@ static void ip6gre_init(NetDev *n) {
t->ttl = DEFAULT_TNL_HOP_LIMIT;
}
static void erspan_init(NetDev *n) {
Tunnel *t;
assert(n);
t = ERSPAN(n);
assert(t);
t->erspan_sequence = -1;
}
static void ip6tnl_init(NetDev *n) {
Tunnel *t = IP6TNL(n);
@ -822,3 +913,12 @@ const NetDevVTable ip6tnl_vtable = {
.create_type = NETDEV_CREATE_STACKED,
.config_verify = netdev_tunnel_verify,
};
const NetDevVTable erspan_vtable = {
.object_size = sizeof(Tunnel),
.init = erspan_init,
.sections = "Match\0NetDev\0Tunnel\0",
.fill_message_create = netdev_erspan_fill_message_create,
.create_type = NETDEV_CREATE_INDEPENDENT,
.config_verify = netdev_tunnel_verify,
};

View file

@ -29,6 +29,7 @@ typedef struct Tunnel {
int family;
int ipv6_flowlabel;
int allow_localremote;
int erspan_sequence;
unsigned ttl;
unsigned tos;
@ -37,6 +38,7 @@ typedef struct Tunnel {
uint32_t key;
uint32_t ikey;
uint32_t okey;
uint32_t erspan_index;
union in_addr_union local;
union in_addr_union remote;
@ -65,6 +67,7 @@ DEFINE_NETDEV_CAST(SIT, Tunnel);
DEFINE_NETDEV_CAST(VTI, Tunnel);
DEFINE_NETDEV_CAST(VTI6, Tunnel);
DEFINE_NETDEV_CAST(IP6TNL, Tunnel);
DEFINE_NETDEV_CAST(ERSPAN, Tunnel);
extern const NetDevVTable ipip_vtable;
extern const NetDevVTable sit_vtable;
extern const NetDevVTable vti_vtable;
@ -74,6 +77,7 @@ extern const NetDevVTable gretap_vtable;
extern const NetDevVTable ip6gre_vtable;
extern const NetDevVTable ip6gretap_vtable;
extern const NetDevVTable ip6tnl_vtable;
extern const NetDevVTable erspan_vtable;
const char *ip6tnl_mode_to_string(Ip6TnlMode d) _const_;
Ip6TnlMode ip6tnl_mode_from_string(const char *d) _pure_;

View file

@ -0,0 +1,11 @@
[NetDev]
Name=erspan-test
Kind=erspan
[Tunnel]
Independent=true
ERSPANIndex=123
Local = 172.16.1.200
Remote = 172.16.1.100
Key=101
SerializeTunneledPackets=true

View file

@ -153,17 +153,18 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
links =['bridge99', 'bond99', 'bond99', 'vlan99', 'test1', 'macvtap99',
'macvlan99', 'ipvlan99', 'vxlan99', 'veth99', 'vrf99', 'tun99',
'tap99', 'vcan99', 'geneve99', 'dummy98', 'ipiptun99', 'sittun99', '6rdtun99',
'gretap99', 'vtitun99', 'vti6tun99','ip6tnl99', 'gretun99', 'ip6gretap99', 'wg99', 'dropin-test']
'gretap99', 'vtitun99', 'vti6tun99','ip6tnl99', 'gretun99', 'ip6gretap99',
'wg99', 'dropin-test', 'erspan-test']
units = ['25-bridge.netdev', '25-bond.netdev', '21-vlan.netdev', '11-dummy.netdev', '21-vlan.network',
'21-macvtap.netdev', 'macvtap.network', '21-macvlan.netdev', 'macvlan.network', 'vxlan.network',
'25-vxlan.netdev', '25-ipvlan.netdev', 'ipvlan.network', '25-veth.netdev', '25-vrf.netdev',
'25-tun.netdev', '25-tun.netdev', '25-vcan.netdev', '25-geneve.netdev', '25-ipip-tunnel.netdev',
'25-ip6tnl-tunnel.netdev', '25-ip6gre-tunnel.netdev','25-sit-tunnel.netdev', '25-6rd-tunnel.netdev',
'25-gre-tunnel.netdev', '25-gretap-tunnel.netdev', '25-vti-tunnel.netdev', '25-vti6-tunnel.netdev',
'12-dummy.netdev', 'gre.network', 'ipip.network', 'ip6gretap.network', 'gretun.network',
'ip6tnl.network', '25-tap.netdev', 'vti6.network', 'vti.network', 'gretap.network', 'sit.network',
'25-ipip-tunnel-independent.netdev', '25-wireguard.netdev', '6rd.network', '10-dropin-test.netdev']
'25-ip6tnl-tunnel.netdev', '25-ip6gre-tunnel.netdev', '25-sit-tunnel.netdev', '25-6rd-tunnel.netdev',
'25-erspan-tunnel.netdev', '25-gre-tunnel.netdev', '25-gretap-tunnel.netdev', '25-vti-tunnel.netdev',
'25-vti6-tunnel.netdev', '12-dummy.netdev', 'gre.network', 'ipip.network', 'ip6gretap.network',
'gretun.network', 'ip6tnl.network', '25-tap.netdev', 'vti6.network', 'vti.network', 'gretap.network',
'sit.network', '25-ipip-tunnel-independent.netdev', '25-wireguard.netdev', '6rd.network', '10-dropin-test.netdev']
def setUp(self):
self.link_remove(self.links)
@ -383,6 +384,18 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
self.assertTrue(self.link_exits('dummy98'))
self.assertTrue(self.link_exits('sittun99'))
def test_erspan_tunnel(self):
self.copy_unit_to_networkd_unit_path('25-erspan-tunnel.netdev')
self.start_networkd()
self.assertTrue(self.link_exits('erspan-test'))
output = subprocess.check_output(['ip', '-d', 'link', 'show', 'erspan-test']).rstrip().decode('utf-8')
print(output)
self.assertTrue(output, '172.16.1.200')
self.assertTrue(output, '172.16.1.100')
self.assertTrue(output, '101')
def test_tunnel_independent(self):
self.copy_unit_to_networkd_unit_path('25-ipip-tunnel-independent.netdev')