<row><entry><varname>geneve</varname></entry>
<entry>A GEneric NEtwork Virtualization Encapsulation (GENEVE) netdev driver.</entry></row>
+ <row><entry><varname>hsr</varname></entry>
+ <entry>A High-availability Seamless Redundancy (HSR) or Parallel Redundancy Protocol (PRP) interface. HSR and PRP are two protocols defined by the IEC 62439-3 standard, providing seamless failover against failure of any single network component.</entry></row>
+
<row><entry><varname>l2tp</varname></entry>
<entry>A Layer 2 Tunneling Protocol (L2TP) is a tunneling protocol used to support virtual private networks (VPNs) or as part of the delivery of services by ISPs. It does not provide any encryption or confidentiality by itself</entry></row>
</variablelist>
</refsect1>
+ <refsect1>
+ <title>[HSR] Section Options</title>
+
+ <para>The [HSR] section only applies for
+ netdevs of kind <literal>hsr</literal>, and accepts the
+ following keys:</para>
+
+ <variablelist class='network-directives'>
+ <varlistentry>
+ <term><varname>Ports=</varname></term>
+ <listitem>
+ <para>Specifies the underlying interfaces. This field is mandatory and must contain exactly two
+ interface names separated by space. This option can be specified multiples times, hence the two cases below have the same result:
+ <programlisting>Ports=eth1 eth2</programlisting>
+ <programlisting>Ports=eth1
+Ports=eth2</programlisting>
+ All the previous assignments are cleared when an empty string is specified.
+ </para>
+
+ <xi:include href="version-info.xml" xpointer="v258"/>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><varname>Protocol=</varname></term>
+ <listitem>
+ <para>Specifies the protocol used by the interface. Takes one of <literal>hsr</literal> or
+ <literal>prp</literal>. Defaults to <literal>hsr</literal>.</para>
+
+ <para>Both protocols work by sending two copies of every outgoing frame, one for each of the two
+ ports. The destination node receives the two frames and and keeps only the first one. If a link
+ fails, only one of the two frames is received. HSR uses a ring topology where the two outgoing
+ frames are sent in opposite directions in the ring. PRP doesn't need a specific topology, but it
+ requires two completely redundant networks attached to the two ports.</para>
+
+ <xi:include href="version-info.xml" xpointer="v258"/>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><varname>Supervision=</varname></term>
+ <listitem>
+ <para>Specifies the last byte of the destination MAC address of supervision frames. Takes a number
+ between 0 and 255. Defaults to 0. Supervision frames are used by the HSR and the PRP protocols to
+ monitor the integrity of the network and the presence of nodes. The first 5 bytes of the
+ destination MAC are always 01:15:4E:00:01 while the last byte is configurable.
+ </para>
+
+ <xi:include href="version-info.xml" xpointer="v258"/>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
<refsect1>
<title>[BareUDP] Section Options</title>
[IFLA_GRE_ERSPAN_HWID] = BUILD_POLICY(U16),
};
+static const NLAPolicy rtnl_link_info_data_hsr_policies[] = {
+ [IFLA_HSR_SLAVE1] = BUILD_POLICY(U32),
+ [IFLA_HSR_SLAVE2] = BUILD_POLICY(U32),
+ [IFLA_HSR_MULTICAST_SPEC] = BUILD_POLICY(U8),
+ [IFLA_HSR_PROTOCOL] = BUILD_POLICY(U8),
+};
+
static const NLAPolicy rtnl_link_info_data_ipoib_policies[] = {
[IFLA_IPOIB_PKEY] = BUILD_POLICY(U16),
[IFLA_IPOIB_MODE] = BUILD_POLICY(U16),
BUILD_UNION_ELEMENT_BY_STRING("gretap", rtnl_link_info_data_gre),
/*
BUILD_UNION_ELEMENT_BY_STRING("gtp", rtnl_link_info_data_gtp),
- BUILD_UNION_ELEMENT_BY_STRING("hsr", rtnl_link_info_data_hsr),
*/
+ BUILD_UNION_ELEMENT_BY_STRING("hsr", rtnl_link_info_data_hsr),
BUILD_UNION_ELEMENT_BY_STRING("ip6erspan", rtnl_link_info_data_gre),
BUILD_UNION_ELEMENT_BY_STRING("ip6gre", rtnl_link_info_data_gre),
BUILD_UNION_ELEMENT_BY_STRING("ip6gretap", rtnl_link_info_data_gre),
'netdev/dummy.c',
'netdev/fou-tunnel.c',
'netdev/geneve.c',
+ 'netdev/hsr.c',
'netdev/ifb.c',
'netdev/ipoib.c',
'netdev/ipvlan.c',
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+/* Make sure the net/if.h header is included before any linux/ one */
+#include <net/if.h>
+#include <linux/if_arp.h>
+#include <netinet/in.h>
+
+#include "hsr.h"
+#include "netlink-util.h"
+#include "networkd-manager.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "strv.h"
+
+static const char * const hsr_protocol_table[_NETDEV_HSR_PROTOCOL_MAX] = {
+ [NETDEV_HSR_PROTOCOL_HSR] = "hsr",
+ [NETDEV_HSR_PROTOCOL_PRP] = "prp",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(hsr_protocol, HsrProtocol);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_hsr_protocol, hsr_protocol, HsrProtocol);
+
+static int hsr_get_port_links(NetDev *netdev, Link **ret1, Link **ret2) {
+ Hsr *h = ASSERT_PTR(HSR(netdev));
+ Link *link1, *link2;
+ int r;
+
+ r = link_get_by_name(netdev->manager, h->ports[0], &link1);
+ if (r < 0)
+ return r;
+
+ r = link_get_by_name(netdev->manager, h->ports[1], &link2);
+ if (r < 0)
+ return r;
+
+ if (ret1)
+ *ret1 = link1;
+ if (ret2)
+ *ret2 = link2;
+
+ return 0;
+}
+
+static int netdev_hsr_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ Hsr *h = ASSERT_PTR(HSR(netdev));
+ Link *link1, *link2;
+ int r;
+
+ assert(m);
+
+ r = hsr_get_port_links(netdev, &link1, &link2);
+ if (r < 0)
+ return r;
+
+ if (link1->ifindex == link2->ifindex)
+ return log_netdev_warning_errno(
+ netdev, SYNTHETIC_ERRNO(EINVAL), "the two HSR ports must be different");
+
+ r = sd_netlink_message_append_u32(m, IFLA_HSR_SLAVE1, link1->ifindex);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_append_u32(m, IFLA_HSR_SLAVE2, link2->ifindex);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_append_u8(m, IFLA_HSR_PROTOCOL, h->protocol);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_append_u8(m, IFLA_HSR_MULTICAST_SPEC, h->supervision);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int netdev_hsr_config_verify(NetDev *netdev, const char *filename) {
+ Hsr *h = ASSERT_PTR(HSR(netdev));
+
+ assert(filename);
+
+ if (strv_length(h->ports) != 2)
+ return log_netdev_warning_errno(
+ netdev,
+ SYNTHETIC_ERRNO(EINVAL),
+ "HSR needs two ports set, ignoring \"%s\".",
+ filename);
+
+ if (streq(h->ports[0], h->ports[1]))
+ return log_netdev_warning_errno(
+ netdev,
+ SYNTHETIC_ERRNO(EINVAL),
+ "the two HSR ports must be different, ignoring \"%s\".",
+ filename);
+
+ return 0;
+}
+
+static int netdev_hsr_is_ready_to_create(NetDev *netdev, Link *link) {
+ return hsr_get_port_links(netdev, NULL, NULL) >= 0;
+}
+
+static void netdev_hsr_done(NetDev *netdev) {
+ Hsr *h = ASSERT_PTR(HSR(netdev));
+
+ strv_free(h->ports);
+}
+
+static void netdev_hsr_init(NetDev *netdev) {
+ Hsr *h = ASSERT_PTR(HSR(netdev));
+
+ h->protocol = NETDEV_HSR_PROTOCOL_HSR;
+}
+
+const NetDevVTable hsr_vtable = {
+ .object_size = sizeof(Hsr),
+ .init = netdev_hsr_init,
+ .done = netdev_hsr_done,
+ .config_verify = netdev_hsr_config_verify,
+ .is_ready_to_create = netdev_hsr_is_ready_to_create,
+ .fill_message_create = netdev_hsr_fill_message_create,
+ .sections = NETDEV_COMMON_SECTIONS "HSR\0",
+ .create_type = NETDEV_CREATE_INDEPENDENT,
+ .iftype = ARPHRD_ETHER,
+ .generate_mac = true,
+};
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+typedef struct Hsr Hsr;
+
+#include <linux/if_link.h>
+
+#include "netdev.h"
+
+typedef enum HsrProtocol {
+ NETDEV_HSR_PROTOCOL_HSR = HSR_PROTOCOL_HSR,
+ NETDEV_HSR_PROTOCOL_PRP = HSR_PROTOCOL_PRP,
+ _NETDEV_HSR_PROTOCOL_MAX,
+ _NETDEV_HSR_PROTOCOL_INVALID = -EINVAL,
+} HsrProtocol;
+
+struct Hsr {
+ NetDev meta;
+
+ char **ports;
+ HsrProtocol protocol;
+ uint8_t supervision;
+};
+
+DEFINE_NETDEV_CAST(HSR, Hsr);
+extern const NetDevVTable hsr_vtable;
+
+HsrProtocol hsr_protocol_from_string(const char *d) _pure_;
+
+CONFIG_PARSER_PROTOTYPE(config_parse_hsr_protocol);
#include "conf-parser.h"
#include "fou-tunnel.h"
#include "geneve.h"
+#include "hsr.h"
#include "ipoib.h"
#include "ipvlan.h"
#include "l2tp-tunnel.h"
GENEVE.IPDoNotFragment, config_parse_geneve_df, 0, offsetof(Geneve, geneve_df)
GENEVE.FlowLabel, config_parse_geneve_flow_label, 0, 0
GENEVE.InheritInnerProtocol, config_parse_bool, 0, offsetof(Geneve, inherit_inner_protocol)
+HSR.Ports, config_parse_ifnames, IFNAME_VALID_ALTERNATIVE, offsetof(Hsr, ports)
+HSR.Protocol, config_parse_hsr_protocol, 0, offsetof(Hsr, protocol)
+HSR.Supervision, config_parse_uint8, 0, offsetof(Hsr, supervision)
MACsec.Port, config_parse_macsec_port, 0, 0
MACsec.Encrypt, config_parse_tristate, 0, offsetof(MACsec, encrypt)
MACsecReceiveChannel.Port, config_parse_macsec_port, 0, 0
#include "fd-util.h"
#include "fou-tunnel.h"
#include "geneve.h"
+#include "hsr.h"
#include "ifb.h"
#include "ipoib.h"
#include "ipvlan.h"
[NETDEV_KIND_GENEVE] = &geneve_vtable,
[NETDEV_KIND_GRE] = &gre_vtable,
[NETDEV_KIND_GRETAP] = &gretap_vtable,
+ [NETDEV_KIND_HSR] = &hsr_vtable,
[NETDEV_KIND_IFB] = &ifb_vtable,
[NETDEV_KIND_IP6GRE] = &ip6gre_vtable,
[NETDEV_KIND_IP6GRETAP] = &ip6gretap_vtable,
[NETDEV_KIND_GENEVE] = "geneve",
[NETDEV_KIND_GRE] = "gre",
[NETDEV_KIND_GRETAP] = "gretap",
+ [NETDEV_KIND_HSR] = "hsr",
[NETDEV_KIND_IFB] = "ifb",
[NETDEV_KIND_IP6GRE] = "ip6gre",
[NETDEV_KIND_IP6GRETAP] = "ip6gretap",
"-Bridge\0" \
"-FooOverUDP\0" \
"-GENEVE\0" \
+ "-HSR\0" \
"-IPoIB\0" \
"-IPVLAN\0" \
"-IPVTAP\0" \
NETDEV_KIND_GENEVE,
NETDEV_KIND_GRE,
NETDEV_KIND_GRETAP,
+ NETDEV_KIND_HSR,
NETDEV_KIND_IFB,
NETDEV_KIND_IP6GRE,
NETDEV_KIND_IP6GRETAP,
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[NetDev]
+Name=hsr99
+Kind=hsr
+
+[HSR]
+Ports=test1 dummy98
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=hsr99
+
+[Network]
+IPv6AcceptRA=no
networkctl_reload()
self.wait_online('ipvlan99:degraded', 'test1:degraded')
+ @expectedFailureIfModuleIsNotAvailable('hsr')
+ def test_hsr(self):
+ first = True
+ for proto, supervision in [['hsr', 9], ['prp', 127]]:
+ if first:
+ first = False
+ else:
+ self.tearDown()
+
+ print(f'### test_hsr(proto={proto}, supervision={supervision})')
+ with self.subTest(proto=proto, supervision=supervision):
+ copy_network_unit('25-hsr.netdev', '25-hsr.network',
+ '11-dummy.netdev', '11-dummy.network',
+ '12-dummy.netdev', '12-dummy-no-address.network')
+ with open(os.path.join(network_unit_dir, '25-hsr.netdev'), mode='a', encoding='utf-8') as f:
+ f.write('Protocol=' + proto + '\nSupervision=' + str(supervision))
+
+ start_networkd()
+ self.wait_online('hsr99:degraded')
+ self.networkctl_check_unit('hsr99', '25-hsr', '25-hsr')
+ self.networkctl_check_unit('test1', '11-dummy', '11-dummy')
+ self.networkctl_check_unit('dummy98', '12-dummy', '12-dummy-no-address')
+
+ output = check_output('ip -d link show hsr99')
+ print(output)
+ self.assertRegex(output, 'hsr slave1 test1 slave2 dummy98')
+ self.assertRegex(output, f'supervision 01:15:4e:00:01:{supervision:02x}')
+ self.assertRegex(output, 'proto ' + ('0' if proto == 'hsr' else '1') + ' ')
+
+ touch_network_unit('25-hsr.netdev')
+ networkctl_reload()
+ self.wait_online('hsr99:degraded')
+
@expectedFailureIfModuleIsNotAvailable('ipvtap')
def test_ipvtap(self):
first = True