From e5719363f54c8c45233ded86b5b18feb36b601f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Mon, 18 Dec 2017 15:20:34 +0100 Subject: [PATCH] networkd: add support for wireguard interface type More information may be found at wireguard.com. --- man/custom-html.xsl | 12 + man/systemd.netdev.xml | 115 ++++ src/libsystemd/sd-netlink/generic-netlink.c | 1 + src/libsystemd/sd-netlink/netlink-types.c | 52 +- src/libsystemd/sd-netlink/netlink-types.h | 1 + src/network/meson.build | 2 + src/network/netdev/netdev-gperf.gperf | 237 +++---- src/network/netdev/netdev.c | 3 + src/network/netdev/netdev.h | 1 + src/network/netdev/wireguard.c | 721 ++++++++++++++++++++ src/network/netdev/wireguard.h | 98 +++ src/shared/meson.build | 1 + src/shared/wireguard-netlink.h | 179 +++++ src/systemd/sd-netlink.h | 2 +- 14 files changed, 1309 insertions(+), 116 deletions(-) create mode 100644 src/network/netdev/wireguard.c create mode 100644 src/network/netdev/wireguard.h create mode 100644 src/shared/wireguard-netlink.h diff --git a/man/custom-html.xsl b/man/custom-html.xsl index 47ce6abfee..e8a7404df3 100644 --- a/man/custom-html.xsl +++ b/man/custom-html.xsl @@ -73,6 +73,18 @@ + + + + https://git.zx2c4.com/WireGuard/about/src/tools/ + + . + + + + + + diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml index 8f8d54a8eb..eb86db9792 100644 --- a/man/systemd.netdev.xml +++ b/man/systemd.netdev.xml @@ -184,6 +184,9 @@ The virtual CAN tunnel driver (vxcan). Similar to the virtual ethernet driver veth, vxcan implements a local CAN traffic tunnel between two virtual CAN network devices. When creating a vxcan, two vxcan devices are created as pair. When one end receives the packet it appears on its pair and vice versa. The vxcan can be used for cross namespace communication. + wireguard + WireGuard Secure Network Tunnel. + @@ -1009,6 +1012,103 @@ as the [Tun] section. + + [WireGuard] Section Options + + The [WireGuard] section accepts the following + keys: + + + + PrivateKey= + + The Base64 encoded private key for the interface. It can be + generated using the wg genkey command + (see wg8). + This option is mandatory to use wireguard. + + + + ListenPort= + + Sets UDP port for listening. Takes either value between 1 and 65535 + or auto. If auto is specified, + the port is automatically generated based on interface name. + Defaults to auto. + + + + FwMark= + + Sets a firewall mark on outgoing wireguard packets from this interface. + + + + + + + [WireGuardPeer] Section Options + + The [WireGuardPeer] section accepts the following + keys: + + + + PublicKey= + + Sets a Base64 encoded public key calculated by wg pubkey + (see wg8) + from a private key, and usually transmitted out of band to the + author of the configuration file. This option is mandatory for this + section. + + + + PresharedKey= + + Optional preshared key for the interface. It can be generated + by the wg genpsk command. This option adds an + additional layer of symmetric-key cryptography to be mixed into the + already existing public-key cryptography, for post-quantum + resistance. + + + + AllowedIPs= + + Sets a comma-separated list of IP (v4 or v6) addresses with CIDR masks + from which this peer is allowed to send incoming traffic and to + which outgoing traffic for this peer is directed. The catch-all + 0.0.0.0/0 may be specified for matching all IPv4 addresses, and + ::/0 may be specified for matching all IPv6 addresses. + + + + Endpoint= + + Sets an endpoint IP address or hostname, followed by a colon, and then + a port number. This endpoint will be updated automatically once to + the most recent source IP address and port of correctly + authenticated packets from the peer at configuration time. + + + + PersistentKeepalive= + + Sets a seconds interval, between 1 and 65535 inclusive, of how often + to send an authenticated empty packet to the peer for the purpose + of keeping a stateful firewall or NAT mapping valid persistently. + For example, if the interface very rarely sends traffic, but it + might at anytime receive traffic from a peer, and it is behind NAT, + the interface might benefit from having a persistent keepalive + interval of 25 seconds. If set to 0 or "off", this option is + disabled. By default or when unspecified, this option is off. + Most users will not need this. + + + + + [Bond] Section Options @@ -1391,6 +1491,21 @@ Name=macvtap-test Kind=macvtap + + /etc/systemd/network/25-wireguard.netdev + [NetDev] +Name=wg0 +Kind=wireguard + +[WireGuard] +PrivateKey=EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong= +ListenPort=51820 + +[WireGuardPeer] +PublicKey=RDf+LSpeEre7YEIKaxg+wbpsNV7du+ktR99uBEtIiCA= +AllowedIPs=fd31:bf08:57cb::/48,192.168.26.0/24 +Endpoint=wireguard.example.com:51820 + See Also diff --git a/src/libsystemd/sd-netlink/generic-netlink.c b/src/libsystemd/sd-netlink/generic-netlink.c index e6e0f958ba..771658d9ae 100644 --- a/src/libsystemd/sd-netlink/generic-netlink.c +++ b/src/libsystemd/sd-netlink/generic-netlink.c @@ -11,6 +11,7 @@ typedef struct { static const genl_family genl_families[] = { [SD_GENL_ID_CTRL] = { .name = "", .version = 1 }, + [SD_GENL_WIREGUARD] = { .name = "wireguard", .version = 1 }, }; int sd_genl_socket_open(sd_netlink **ret) { diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c index 6dea62e313..718aa74f15 100644 --- a/src/libsystemd/sd-netlink/netlink-types.c +++ b/src/libsystemd/sd-netlink/netlink-types.c @@ -47,6 +47,7 @@ #include "netlink-types.h" #include "string-table.h" #include "util.h" +#include "wireguard-netlink.h" #include "sd-netlink.h" /* Maximum ARP IP target defined in kernel */ @@ -340,7 +341,7 @@ static const char* const nl_union_link_info_data_table[] = { [NL_UNION_LINK_INFO_DATA_VCAN] = "vcan", [NL_UNION_LINK_INFO_DATA_GENEVE] = "geneve", [NL_UNION_LINK_INFO_DATA_VXCAN] = "vxcan", - + [NL_UNION_LINK_INFO_DATA_WIREGUARD] = "wireguard", }; DEFINE_STRING_TABLE_LOOKUP(nl_union_link_info_data, NLUnionLinkInfoData); @@ -672,6 +673,54 @@ const NLTypeSystem rtnl_type_system_root = { .types = rtnl_types, }; +static const NLType genl_wireguard_allowedip_types[] = { + [WGALLOWEDIP_A_FAMILY] = { .type = NETLINK_TYPE_U16 }, + [WGALLOWEDIP_A_IPADDR] = { .type = NETLINK_TYPE_IN_ADDR }, + [WGALLOWEDIP_A_CIDR_MASK] = { .type = NETLINK_TYPE_U8 }, +}; + +static const NLTypeSystem genl_wireguard_allowedip_type_system = { + .count = ELEMENTSOF(genl_wireguard_allowedip_types), + .types = genl_wireguard_allowedip_types, +}; + +static const NLType genl_wireguard_peer_types[] = { + [WGPEER_A_PUBLIC_KEY] = { .size = WG_KEY_LEN }, + [WGPEER_A_FLAGS] = { .type = NETLINK_TYPE_U32 }, + [WGPEER_A_PRESHARED_KEY] = { .size = WG_KEY_LEN }, + [WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL] = { .type = NETLINK_TYPE_U32 }, + [WGPEER_A_ENDPOINT] = { /* either size of sockaddr_in or sockaddr_in6 depending on address family */ }, + [WGPEER_A_ALLOWEDIPS] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_wireguard_allowedip_type_system }, +}; + +static const NLTypeSystem genl_wireguard_peer_type_system = { + .count = ELEMENTSOF(genl_wireguard_peer_types), + .types = genl_wireguard_peer_types, +}; + +static const NLType genl_wireguard_set_device_types[] = { + [WGDEVICE_A_IFINDEX] = { .type = NETLINK_TYPE_U32 }, + [WGDEVICE_A_IFNAME] = { .type = NETLINK_TYPE_STRING }, + [WGDEVICE_A_FLAGS] = { .type = NETLINK_TYPE_U32 }, + [WGDEVICE_A_PRIVATE_KEY] = { .size = WG_KEY_LEN }, + [WGDEVICE_A_LISTEN_PORT] = { .type = NETLINK_TYPE_U16 }, + [WGDEVICE_A_FWMARK] = { .type = NETLINK_TYPE_U32 }, + [WGDEVICE_A_PEERS] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_wireguard_peer_type_system }, +}; + +static const NLTypeSystem genl_wireguard_set_device_type_system = { + .count = ELEMENTSOF(genl_wireguard_set_device_types), + .types = genl_wireguard_set_device_types, +}; + +static const NLType genl_wireguard_cmds[] = { + [WG_CMD_SET_DEVICE] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_wireguard_set_device_type_system }, +}; + +static const NLTypeSystem genl_wireguard_type_system = { + .count = ELEMENTSOF(genl_wireguard_cmds), + .types = genl_wireguard_cmds, +}; static const NLType genl_get_family_types[] = { [CTRL_ATTR_FAMILY_NAME] = { .type = NETLINK_TYPE_STRING }, @@ -694,6 +743,7 @@ static const NLTypeSystem genl_ctrl_id_ctrl_type_system = { static const NLType genl_families[] = { [SD_GENL_ID_CTRL] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_ctrl_id_ctrl_type_system }, + [SD_GENL_WIREGUARD] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_wireguard_type_system }, }; const NLTypeSystem genl_family_type_system_root = { diff --git a/src/libsystemd/sd-netlink/netlink-types.h b/src/libsystemd/sd-netlink/netlink-types.h index 2012319e8a..ea7f8d5e6c 100644 --- a/src/libsystemd/sd-netlink/netlink-types.h +++ b/src/libsystemd/sd-netlink/netlink-types.h @@ -94,6 +94,7 @@ typedef enum NLUnionLinkInfoData { NL_UNION_LINK_INFO_DATA_VCAN, NL_UNION_LINK_INFO_DATA_GENEVE, NL_UNION_LINK_INFO_DATA_VXCAN, + NL_UNION_LINK_INFO_DATA_WIREGUARD, _NL_UNION_LINK_INFO_DATA_MAX, _NL_UNION_LINK_INFO_DATA_INVALID = -1 } NLUnionLinkInfoData; diff --git a/src/network/meson.build b/src/network/meson.build index f97484eb26..d777b5f8c2 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -46,6 +46,8 @@ sources = files(''' netdev/geneve.h netdev/vxcan.c netdev/vxcan.h + netdev/wireguard.c + netdev/wireguard.h networkd-address-label.c networkd-address-label.h networkd-address-pool.c diff --git a/src/network/netdev/netdev-gperf.gperf b/src/network/netdev/netdev-gperf.gperf index 1b4cb5a60c..ba6268fa66 100644 --- a/src/network/netdev/netdev-gperf.gperf +++ b/src/network/netdev/netdev-gperf.gperf @@ -18,6 +18,7 @@ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"") #include "netdev/vrf.h" #include "netdev/netdev.h" #include "netdev/vxcan.h" +#include "netdev/wireguard.h" #include "vlan-util.h" %} struct ConfigPerfItem; @@ -31,117 +32,125 @@ struct ConfigPerfItem; %struct-type %includes %% -Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(NetDev, match_host) -Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(NetDev, match_virt) -Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(NetDev, match_kernel_cmdline) -Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(NetDev, match_kernel_version) -Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(NetDev, match_arch) -NetDev.Description, config_parse_string, 0, offsetof(NetDev, description) -NetDev.Name, config_parse_ifname, 0, offsetof(NetDev, ifname) -NetDev.Kind, config_parse_netdev_kind, 0, offsetof(NetDev, kind) -NetDev.MTUBytes, config_parse_iec_size, 0, offsetof(NetDev, mtu) -NetDev.MACAddress, config_parse_hwaddr, 0, offsetof(NetDev, mac) -VLAN.Id, config_parse_vlanid, 0, offsetof(VLan, id) -VLAN.GVRP, config_parse_tristate, 0, offsetof(VLan, gvrp) -VLAN.MVRP, config_parse_tristate, 0, offsetof(VLan, mvrp) -VLAN.LooseBinding, config_parse_tristate, 0, offsetof(VLan, loose_binding) -VLAN.ReorderHeader, config_parse_tristate, 0, offsetof(VLan, reorder_hdr) -MACVLAN.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode) -MACVTAP.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode) -IPVLAN.Mode, config_parse_ipvlan_mode, 0, offsetof(IPVlan, mode) -IPVLAN.Flags, config_parse_ipvlan_flags, 0, offsetof(IPVlan, flags) -Tunnel.Local, config_parse_tunnel_address, 0, offsetof(Tunnel, local) -Tunnel.Remote, config_parse_tunnel_address, 0, offsetof(Tunnel, remote) -Tunnel.TOS, config_parse_unsigned, 0, offsetof(Tunnel, tos) -Tunnel.TTL, config_parse_unsigned, 0, offsetof(Tunnel, ttl) -Tunnel.Key, config_parse_tunnel_key, 0, offsetof(Tunnel, key) -Tunnel.InputKey, config_parse_tunnel_key, 0, offsetof(Tunnel, ikey) -Tunnel.OutputKey, config_parse_tunnel_key, 0, offsetof(Tunnel, okey) -Tunnel.DiscoverPathMTU, config_parse_bool, 0, offsetof(Tunnel, pmtudisc) -Tunnel.Mode, config_parse_ip6tnl_mode, 0, offsetof(Tunnel, ip6tnl_mode) -Tunnel.IPv6FlowLabel, config_parse_ipv6_flowlabel, 0, offsetof(Tunnel, ipv6_flowlabel) -Tunnel.CopyDSCP, config_parse_bool, 0, offsetof(Tunnel, copy_dscp) -Tunnel.EncapsulationLimit, config_parse_encap_limit, 0, offsetof(Tunnel, encap_limit) -Tunnel.Independent, config_parse_bool, 0, offsetof(Tunnel, independent) -Tunnel.AllowLocalRemote, config_parse_tristate, 0, offsetof(Tunnel, allow_localremote) -Peer.Name, config_parse_ifname, 0, offsetof(Veth, ifname_peer) -Peer.MACAddress, config_parse_hwaddr, 0, offsetof(Veth, mac_peer) -VXCAN.Peer, config_parse_ifname, 0, offsetof(VxCan, ifname_peer) -VXLAN.Id, config_parse_uint64, 0, offsetof(VxLan, id) -VXLAN.Group, config_parse_vxlan_address, 0, offsetof(VxLan, remote) -VXLAN.Local, config_parse_vxlan_address, 0, offsetof(VxLan, local) -VXLAN.Remote, config_parse_vxlan_address, 0, offsetof(VxLan, remote) -VXLAN.TOS, config_parse_unsigned, 0, offsetof(VxLan, tos) -VXLAN.TTL, config_parse_unsigned, 0, offsetof(VxLan, ttl) -VXLAN.MacLearning, config_parse_bool, 0, offsetof(VxLan, learning) -VXLAN.ARPProxy, config_parse_bool, 0, offsetof(VxLan, arp_proxy) -VXLAN.ReduceARPProxy, config_parse_bool, 0, offsetof(VxLan, arp_proxy) -VXLAN.L2MissNotification, config_parse_bool, 0, offsetof(VxLan, l2miss) -VXLAN.L3MissNotification, config_parse_bool, 0, offsetof(VxLan, l3miss) -VXLAN.RouteShortCircuit, config_parse_bool, 0, offsetof(VxLan, route_short_circuit) -VXLAN.UDPCheckSum, config_parse_bool, 0, offsetof(VxLan, udpcsum) -VXLAN.UDPChecksum, config_parse_bool, 0, offsetof(VxLan, udpcsum) -VXLAN.UDP6ZeroCheckSumRx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumrx) -VXLAN.UDP6ZeroChecksumRx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumrx) -VXLAN.UDP6ZeroCheckSumTx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumtx) -VXLAN.UDP6ZeroChecksumTx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumtx) -VXLAN.RemoteChecksumTx, config_parse_bool, 0, offsetof(VxLan, remote_csum_tx) -VXLAN.RemoteChecksumRx, config_parse_bool, 0, offsetof(VxLan, remote_csum_rx) -VXLAN.FDBAgeingSec, config_parse_sec, 0, offsetof(VxLan, fdb_ageing) -VXLAN.GroupPolicyExtension, config_parse_bool, 0, offsetof(VxLan, group_policy) -VXLAN.MaximumFDBEntries, config_parse_unsigned, 0, offsetof(VxLan, max_fdb) -VXLAN.PortRange, config_parse_port_range, 0, 0 -VXLAN.DestinationPort, config_parse_ip_port, 0, offsetof(VxLan, dest_port) -VXLAN.FlowLabel, config_parse_flow_label, 0, 0 -GENEVE.Id, config_parse_geneve_vni, 0, offsetof(Geneve, id) -GENEVE.Remote, config_parse_geneve_address, 0, offsetof(Geneve, remote) -GENEVE.TOS, config_parse_uint8, 0, offsetof(Geneve, tos) -GENEVE.TTL, config_parse_uint8, 0, offsetof(Geneve, ttl) -GENEVE.UDPChecksum, config_parse_bool, 0, offsetof(Geneve, udpcsum) -GENEVE.UDP6ZeroCheckSumRx, config_parse_bool, 0, offsetof(Geneve, udp6zerocsumrx) -GENEVE.UDP6ZeroCheckSumTx, config_parse_bool, 0, offsetof(Geneve, udp6zerocsumtx) -GENEVE.DestinationPort, config_parse_ip_port, 0, offsetof(Geneve, dest_port) -GENEVE.FlowLabel, config_parse_geneve_flow_label, 0, 0 -Tun.OneQueue, config_parse_bool, 0, offsetof(TunTap, one_queue) -Tun.MultiQueue, config_parse_bool, 0, offsetof(TunTap, multi_queue) -Tun.PacketInfo, config_parse_bool, 0, offsetof(TunTap, packet_info) -Tun.User, config_parse_string, 0, offsetof(TunTap, user_name) -Tun.Group, config_parse_string, 0, offsetof(TunTap, group_name) -Tap.OneQueue, config_parse_bool, 0, offsetof(TunTap, one_queue) -Tap.MultiQueue, config_parse_bool, 0, offsetof(TunTap, multi_queue) -Tap.PacketInfo, config_parse_bool, 0, offsetof(TunTap, packet_info) -Tap.VNetHeader, config_parse_bool, 0, offsetof(TunTap, vnet_hdr) -Tap.User, config_parse_string, 0, offsetof(TunTap, user_name) -Tap.Group, config_parse_string, 0, offsetof(TunTap, group_name) -Bond.Mode, config_parse_bond_mode, 0, offsetof(Bond, mode) -Bond.TransmitHashPolicy, config_parse_bond_xmit_hash_policy, 0, offsetof(Bond, xmit_hash_policy) -Bond.LACPTransmitRate, config_parse_bond_lacp_rate, 0, offsetof(Bond, lacp_rate) -Bond.AdSelect, config_parse_bond_ad_select, 0, offsetof(Bond, ad_select) -Bond.FailOverMACPolicy, config_parse_bond_fail_over_mac, 0, offsetof(Bond, fail_over_mac) -Bond.ARPIPTargets, config_parse_arp_ip_target_address, 0, 0 -Bond.ARPValidate, config_parse_bond_arp_validate, 0, offsetof(Bond, arp_validate) -Bond.ARPAllTargets, config_parse_bond_arp_all_targets, 0, offsetof(Bond, arp_all_targets) -Bond.PrimaryReselectPolicy, config_parse_bond_primary_reselect, 0, offsetof(Bond, primary_reselect) -Bond.ResendIGMP, config_parse_unsigned, 0, offsetof(Bond, resend_igmp) -Bond.PacketsPerSlave, config_parse_unsigned, 0, offsetof(Bond, packets_per_slave) -Bond.GratuitousARP, config_parse_unsigned, 0, offsetof(Bond, num_grat_arp) -Bond.AllSlavesActive, config_parse_unsigned, 0, offsetof(Bond, all_slaves_active) -Bond.MinLinks, config_parse_unsigned, 0, offsetof(Bond, min_links) -Bond.MIIMonitorSec, config_parse_sec, 0, offsetof(Bond, miimon) -Bond.UpDelaySec, config_parse_sec, 0, offsetof(Bond, updelay) -Bond.DownDelaySec, config_parse_sec, 0, offsetof(Bond, downdelay) -Bond.ARPIntervalSec, config_parse_sec, 0, offsetof(Bond, arp_interval) -Bond.LearnPacketIntervalSec, config_parse_sec, 0, offsetof(Bond, lp_interval) -Bridge.HelloTimeSec, config_parse_sec, 0, offsetof(Bridge, hello_time) -Bridge.MaxAgeSec, config_parse_sec, 0, offsetof(Bridge, max_age) -Bridge.AgeingTimeSec, config_parse_sec, 0, offsetof(Bridge, ageing_time) -Bridge.ForwardDelaySec, config_parse_sec, 0, offsetof(Bridge, forward_delay) -Bridge.Priority, config_parse_uint16, 0, offsetof(Bridge, priority) -Bridge.GroupForwardMask, config_parse_uint16, 0, offsetof(Bridge, group_fwd_mask) -Bridge.DefaultPVID, config_parse_default_port_vlanid, 0, offsetof(Bridge, default_pvid) -Bridge.MulticastQuerier, config_parse_tristate, 0, offsetof(Bridge, mcast_querier) -Bridge.MulticastSnooping, config_parse_tristate, 0, offsetof(Bridge, mcast_snooping) -Bridge.VLANFiltering, config_parse_tristate, 0, offsetof(Bridge, vlan_filtering) -Bridge.STP, config_parse_tristate, 0, offsetof(Bridge, stp) -VRF.TableId, config_parse_uint32, 0, offsetof(Vrf, table) /* deprecated */ -VRF.Table, config_parse_route_table, 0, offsetof(Vrf, table) +Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(NetDev, match_host) +Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(NetDev, match_virt) +Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(NetDev, match_kernel_cmdline) +Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(NetDev, match_kernel_version) +Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(NetDev, match_arch) +NetDev.Description, config_parse_string, 0, offsetof(NetDev, description) +NetDev.Name, config_parse_ifname, 0, offsetof(NetDev, ifname) +NetDev.Kind, config_parse_netdev_kind, 0, offsetof(NetDev, kind) +NetDev.MTUBytes, config_parse_iec_size, 0, offsetof(NetDev, mtu) +NetDev.MACAddress, config_parse_hwaddr, 0, offsetof(NetDev, mac) +VLAN.Id, config_parse_vlanid, 0, offsetof(VLan, id) +VLAN.GVRP, config_parse_tristate, 0, offsetof(VLan, gvrp) +VLAN.MVRP, config_parse_tristate, 0, offsetof(VLan, mvrp) +VLAN.LooseBinding, config_parse_tristate, 0, offsetof(VLan, loose_binding) +VLAN.ReorderHeader, config_parse_tristate, 0, offsetof(VLan, reorder_hdr) +MACVLAN.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode) +MACVTAP.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode) +IPVLAN.Mode, config_parse_ipvlan_mode, 0, offsetof(IPVlan, mode) +IPVLAN.Flags, config_parse_ipvlan_flags, 0, offsetof(IPVlan, flags) +Tunnel.Local, config_parse_tunnel_address, 0, offsetof(Tunnel, local) +Tunnel.Remote, config_parse_tunnel_address, 0, offsetof(Tunnel, remote) +Tunnel.TOS, config_parse_unsigned, 0, offsetof(Tunnel, tos) +Tunnel.TTL, config_parse_unsigned, 0, offsetof(Tunnel, ttl) +Tunnel.Key, config_parse_tunnel_key, 0, offsetof(Tunnel, key) +Tunnel.InputKey, config_parse_tunnel_key, 0, offsetof(Tunnel, ikey) +Tunnel.OutputKey, config_parse_tunnel_key, 0, offsetof(Tunnel, okey) +Tunnel.DiscoverPathMTU, config_parse_bool, 0, offsetof(Tunnel, pmtudisc) +Tunnel.Mode, config_parse_ip6tnl_mode, 0, offsetof(Tunnel, ip6tnl_mode) +Tunnel.IPv6FlowLabel, config_parse_ipv6_flowlabel, 0, offsetof(Tunnel, ipv6_flowlabel) +Tunnel.CopyDSCP, config_parse_bool, 0, offsetof(Tunnel, copy_dscp) +Tunnel.EncapsulationLimit, config_parse_encap_limit, 0, offsetof(Tunnel, encap_limit) +Tunnel.Independent, config_parse_bool, 0, offsetof(Tunnel, independent) +Tunnel.AllowLocalRemote, config_parse_tristate, 0, offsetof(Tunnel, allow_localremote) +Peer.Name, config_parse_ifname, 0, offsetof(Veth, ifname_peer) +Peer.MACAddress, config_parse_hwaddr, 0, offsetof(Veth, mac_peer) +VXCAN.Peer, config_parse_ifname, 0, offsetof(VxCan, ifname_peer) +VXLAN.Id, config_parse_uint64, 0, offsetof(VxLan, id) +VXLAN.Group, config_parse_vxlan_address, 0, offsetof(VxLan, remote) +VXLAN.Local, config_parse_vxlan_address, 0, offsetof(VxLan, local) +VXLAN.Remote, config_parse_vxlan_address, 0, offsetof(VxLan, remote) +VXLAN.TOS, config_parse_unsigned, 0, offsetof(VxLan, tos) +VXLAN.TTL, config_parse_unsigned, 0, offsetof(VxLan, ttl) +VXLAN.MacLearning, config_parse_bool, 0, offsetof(VxLan, learning) +VXLAN.ARPProxy, config_parse_bool, 0, offsetof(VxLan, arp_proxy) +VXLAN.ReduceARPProxy, config_parse_bool, 0, offsetof(VxLan, arp_proxy) +VXLAN.L2MissNotification, config_parse_bool, 0, offsetof(VxLan, l2miss) +VXLAN.L3MissNotification, config_parse_bool, 0, offsetof(VxLan, l3miss) +VXLAN.RouteShortCircuit, config_parse_bool, 0, offsetof(VxLan, route_short_circuit) +VXLAN.UDPCheckSum, config_parse_bool, 0, offsetof(VxLan, udpcsum) +VXLAN.UDPChecksum, config_parse_bool, 0, offsetof(VxLan, udpcsum) +VXLAN.UDP6ZeroCheckSumRx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumrx) +VXLAN.UDP6ZeroChecksumRx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumrx) +VXLAN.UDP6ZeroCheckSumTx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumtx) +VXLAN.UDP6ZeroChecksumTx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumtx) +VXLAN.RemoteChecksumTx, config_parse_bool, 0, offsetof(VxLan, remote_csum_tx) +VXLAN.RemoteChecksumRx, config_parse_bool, 0, offsetof(VxLan, remote_csum_rx) +VXLAN.FDBAgeingSec, config_parse_sec, 0, offsetof(VxLan, fdb_ageing) +VXLAN.GroupPolicyExtension, config_parse_bool, 0, offsetof(VxLan, group_policy) +VXLAN.MaximumFDBEntries, config_parse_unsigned, 0, offsetof(VxLan, max_fdb) +VXLAN.PortRange, config_parse_port_range, 0, 0 +VXLAN.DestinationPort, config_parse_ip_port, 0, offsetof(VxLan, dest_port) +VXLAN.FlowLabel, config_parse_flow_label, 0, 0 +GENEVE.Id, config_parse_geneve_vni, 0, offsetof(Geneve, id) +GENEVE.Remote, config_parse_geneve_address, 0, offsetof(Geneve, remote) +GENEVE.TOS, config_parse_uint8, 0, offsetof(Geneve, tos) +GENEVE.TTL, config_parse_uint8, 0, offsetof(Geneve, ttl) +GENEVE.UDPChecksum, config_parse_bool, 0, offsetof(Geneve, udpcsum) +GENEVE.UDP6ZeroCheckSumRx, config_parse_bool, 0, offsetof(Geneve, udp6zerocsumrx) +GENEVE.UDP6ZeroCheckSumTx, config_parse_bool, 0, offsetof(Geneve, udp6zerocsumtx) +GENEVE.DestinationPort, config_parse_ip_port, 0, offsetof(Geneve, dest_port) +GENEVE.FlowLabel, config_parse_geneve_flow_label, 0, 0 +Tun.OneQueue, config_parse_bool, 0, offsetof(TunTap, one_queue) +Tun.MultiQueue, config_parse_bool, 0, offsetof(TunTap, multi_queue) +Tun.PacketInfo, config_parse_bool, 0, offsetof(TunTap, packet_info) +Tun.User, config_parse_string, 0, offsetof(TunTap, user_name) +Tun.Group, config_parse_string, 0, offsetof(TunTap, group_name) +Tap.OneQueue, config_parse_bool, 0, offsetof(TunTap, one_queue) +Tap.MultiQueue, config_parse_bool, 0, offsetof(TunTap, multi_queue) +Tap.PacketInfo, config_parse_bool, 0, offsetof(TunTap, packet_info) +Tap.VNetHeader, config_parse_bool, 0, offsetof(TunTap, vnet_hdr) +Tap.User, config_parse_string, 0, offsetof(TunTap, user_name) +Tap.Group, config_parse_string, 0, offsetof(TunTap, group_name) +Bond.Mode, config_parse_bond_mode, 0, offsetof(Bond, mode) +Bond.TransmitHashPolicy, config_parse_bond_xmit_hash_policy, 0, offsetof(Bond, xmit_hash_policy) +Bond.LACPTransmitRate, config_parse_bond_lacp_rate, 0, offsetof(Bond, lacp_rate) +Bond.AdSelect, config_parse_bond_ad_select, 0, offsetof(Bond, ad_select) +Bond.FailOverMACPolicy, config_parse_bond_fail_over_mac, 0, offsetof(Bond, fail_over_mac) +Bond.ARPIPTargets, config_parse_arp_ip_target_address, 0, 0 +Bond.ARPValidate, config_parse_bond_arp_validate, 0, offsetof(Bond, arp_validate) +Bond.ARPAllTargets, config_parse_bond_arp_all_targets, 0, offsetof(Bond, arp_all_targets) +Bond.PrimaryReselectPolicy, config_parse_bond_primary_reselect, 0, offsetof(Bond, primary_reselect) +Bond.ResendIGMP, config_parse_unsigned, 0, offsetof(Bond, resend_igmp) +Bond.PacketsPerSlave, config_parse_unsigned, 0, offsetof(Bond, packets_per_slave) +Bond.GratuitousARP, config_parse_unsigned, 0, offsetof(Bond, num_grat_arp) +Bond.AllSlavesActive, config_parse_unsigned, 0, offsetof(Bond, all_slaves_active) +Bond.MinLinks, config_parse_unsigned, 0, offsetof(Bond, min_links) +Bond.MIIMonitorSec, config_parse_sec, 0, offsetof(Bond, miimon) +Bond.UpDelaySec, config_parse_sec, 0, offsetof(Bond, updelay) +Bond.DownDelaySec, config_parse_sec, 0, offsetof(Bond, downdelay) +Bond.ARPIntervalSec, config_parse_sec, 0, offsetof(Bond, arp_interval) +Bond.LearnPacketIntervalSec, config_parse_sec, 0, offsetof(Bond, lp_interval) +Bridge.HelloTimeSec, config_parse_sec, 0, offsetof(Bridge, hello_time) +Bridge.MaxAgeSec, config_parse_sec, 0, offsetof(Bridge, max_age) +Bridge.AgeingTimeSec, config_parse_sec, 0, offsetof(Bridge, ageing_time) +Bridge.ForwardDelaySec, config_parse_sec, 0, offsetof(Bridge, forward_delay) +Bridge.Priority, config_parse_uint16, 0, offsetof(Bridge, priority) +Bridge.GroupForwardMask, config_parse_uint16, 0, offsetof(Bridge, group_fwd_mask) +Bridge.DefaultPVID, config_parse_default_port_vlanid, 0, offsetof(Bridge, default_pvid) +Bridge.MulticastQuerier, config_parse_tristate, 0, offsetof(Bridge, mcast_querier) +Bridge.MulticastSnooping, config_parse_tristate, 0, offsetof(Bridge, mcast_snooping) +Bridge.VLANFiltering, config_parse_tristate, 0, offsetof(Bridge, vlan_filtering) +Bridge.STP, config_parse_tristate, 0, offsetof(Bridge, stp) +VRF.TableId, config_parse_uint32, 0, offsetof(Vrf, table) /* deprecated */ +VRF.Table, config_parse_route_table, 0, offsetof(Vrf, table) +WireGuard.FwMark, config_parse_unsigned, 0, offsetof(Wireguard, fwmark) +WireGuard.ListenPort, config_parse_wireguard_listen_port, 0, offsetof(Wireguard, port) +WireGuard.PrivateKey, config_parse_wireguard_private_key, 0, 0 +WireGuardPeer.AllowedIPs, config_parse_wireguard_allowed_ips, 0, 0 +WireGuardPeer.Endpoint, config_parse_wireguard_endpoint, 0, 0 +WireGuardPeer.PublicKey, config_parse_wireguard_public_key, 0, 0 +WireGuardPeer.PresharedKey, config_parse_wireguard_preshared_key, 0, 0 +WireGuardPeer.PersistentKeepalive, config_parse_wireguard_keepalive, 0, 0 diff --git a/src/network/netdev/netdev.c b/src/network/netdev/netdev.c index 4adaac26b8..d609b0fb72 100644 --- a/src/network/netdev/netdev.c +++ b/src/network/netdev/netdev.c @@ -49,6 +49,7 @@ #include "netdev/vrf.h" #include "netdev/vcan.h" #include "netdev/vxcan.h" +#include "netdev/wireguard.h" const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX] = { [NETDEV_KIND_BRIDGE] = &bridge_vtable, @@ -75,6 +76,7 @@ const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX] = { [NETDEV_KIND_VCAN] = &vcan_vtable, [NETDEV_KIND_GENEVE] = &geneve_vtable, [NETDEV_KIND_VXCAN] = &vxcan_vtable, + [NETDEV_KIND_WIREGUARD] = &wireguard_vtable, }; static const char* const netdev_kind_table[_NETDEV_KIND_MAX] = { @@ -102,6 +104,7 @@ static const char* const netdev_kind_table[_NETDEV_KIND_MAX] = { [NETDEV_KIND_VCAN] = "vcan", [NETDEV_KIND_GENEVE] = "geneve", [NETDEV_KIND_VXCAN] = "vxcan", + [NETDEV_KIND_WIREGUARD] = "wireguard", }; DEFINE_STRING_TABLE_LOOKUP(netdev_kind, NetDevKind); diff --git a/src/network/netdev/netdev.h b/src/network/netdev/netdev.h index 507b86a078..bd1a94c9be 100644 --- a/src/network/netdev/netdev.h +++ b/src/network/netdev/netdev.h @@ -60,6 +60,7 @@ typedef enum NetDevKind { NETDEV_KIND_VCAN, NETDEV_KIND_GENEVE, NETDEV_KIND_VXCAN, + NETDEV_KIND_WIREGUARD, _NETDEV_KIND_MAX, _NETDEV_KIND_INVALID = -1 } NetDevKind; diff --git a/src/network/netdev/wireguard.c b/src/network/netdev/wireguard.c new file mode 100644 index 0000000000..f1f4bab475 --- /dev/null +++ b/src/network/netdev/wireguard.c @@ -0,0 +1,721 @@ +/*** + This file is part of systemd. + + Copyright 2016-2017 Jörg Thalheim + Copyright 2015-2017 Jason A. Donenfeld . All Rights Reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "alloc-util.h" +#include "parse-util.h" +#include "fd-util.h" +#include "strv.h" +#include "hexdecoct.h" +#include "string-util.h" +#include "wireguard.h" +#include "networkd-link.h" +#include "networkd-util.h" +#include "networkd-manager.h" +#include "wireguard-netlink.h" + +static void resolve_endpoints(NetDev *netdev); + +static WireguardPeer *wireguard_peer_new(Wireguard *w, unsigned section) { + WireguardPeer *peer; + + assert(w); + + if (w->last_peer_section == section && w->peers) + return w->peers; + + peer = new0(WireguardPeer, 1); + if (!peer) + return NULL; + peer->flags = WGPEER_F_REPLACE_ALLOWEDIPS; + + LIST_PREPEND(peers, w->peers, peer); + w->last_peer_section = section; + + return peer; +} + +static int set_wireguard_interface(NetDev *netdev) { + int r; + unsigned int i, j; + WireguardPeer *peer, *peer_start; + WireguardIPmask *mask, *mask_start = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + Wireguard *w; + uint32_t serial; + + assert(netdev); + w = WIREGUARD(netdev); + assert(w); + + peer_start = w->peers; + + do { + message = sd_netlink_message_unref(message); + + r = sd_genl_message_new(netdev->manager->genl, SD_GENL_WIREGUARD, WG_CMD_SET_DEVICE, &message); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to allocate generic netlink message: %m"); + + r = sd_netlink_message_append_string(message, WGDEVICE_A_IFNAME, netdev->ifname); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append wireguard interface name: %m"); + + if (peer_start == w->peers) { + r = sd_netlink_message_append_data(message, WGDEVICE_A_PRIVATE_KEY, &w->private_key, WG_KEY_LEN); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append wireguard private key: %m"); + + r = sd_netlink_message_append_u16(message, WGDEVICE_A_LISTEN_PORT, w->port); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append wireguard port: %m"); + + r = sd_netlink_message_append_u32(message, WGDEVICE_A_FWMARK, w->fwmark); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append wireguard fwmark: %m"); + + r = sd_netlink_message_append_u32(message, WGDEVICE_A_FLAGS, w->flags); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append wireguard flags: %m"); + } + + r = sd_netlink_message_open_container(message, WGDEVICE_A_PEERS); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append wireguard peer attributes: %m"); + + i = 0; + + LIST_FOREACH(peers, peer, peer_start) { + r = sd_netlink_message_open_array(message, ++i); + if (r < 0) + break; + + r = sd_netlink_message_append_data(message, WGPEER_A_PUBLIC_KEY, &peer->public_key, sizeof(peer->public_key)); + if (r < 0) + break; + + if (!mask_start) { + r = sd_netlink_message_append_data(message, WGPEER_A_PRESHARED_KEY, &peer->preshared_key, WG_KEY_LEN); + if (r < 0) + break; + + r = sd_netlink_message_append_u32(message, WGPEER_A_FLAGS, peer->flags); + if (r < 0) + break; + + r = sd_netlink_message_append_u32(message, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval); + if (r < 0) + break; + + if (peer->endpoint.sa.sa_family == AF_INET) { + r = sd_netlink_message_append_data(message, WGPEER_A_ENDPOINT, &peer->endpoint.in, sizeof(peer->endpoint.in)); + if (r < 0) + break; + } else if (peer->endpoint.sa.sa_family == AF_INET6) { + r = sd_netlink_message_append_data(message, WGPEER_A_ENDPOINT, &peer->endpoint.in6, sizeof(peer->endpoint.in6)); + if (r < 0) + break; + } + + mask_start = peer->ipmasks; + } + + r = sd_netlink_message_open_container(message, WGPEER_A_ALLOWEDIPS); + if (r < 0) { + mask_start = NULL; + break; + } + j = 0; + LIST_FOREACH(ipmasks, mask, mask_start) { + r = sd_netlink_message_open_array(message, ++j); + if (r < 0) + break; + + r = sd_netlink_message_append_u16(message, WGALLOWEDIP_A_FAMILY, mask->family); + if (r < 0) + break; + + if (mask->family == AF_INET) { + r = sd_netlink_message_append_in_addr(message, WGALLOWEDIP_A_IPADDR, &mask->ip.in); + if (r < 0) + break; + } else if (mask->family == AF_INET6) { + r = sd_netlink_message_append_in6_addr(message, WGALLOWEDIP_A_IPADDR, &mask->ip.in6); + if (r < 0) + break; + } + + r = sd_netlink_message_append_u8(message, WGALLOWEDIP_A_CIDR_MASK, mask->cidr); + if (r < 0) + break; + + r = sd_netlink_message_close_container(message); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not add wireguard allowed ip: %m"); + } + mask_start = mask; + if (mask_start) { + r = sd_netlink_message_cancel_array(message); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not cancel wireguard allowed ip message attribute: %m"); + } + r = sd_netlink_message_close_container(message); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not add wireguard allowed ip: %m"); + + r = sd_netlink_message_close_container(message); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not add wireguard peer: %m"); + } + + peer_start = peer; + if (peer_start && !mask_start) { + r = sd_netlink_message_cancel_array(message); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not cancel wireguard peers: %m"); + } + + r = sd_netlink_message_close_container(message); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not close wireguard container: %m"); + + r = sd_netlink_send(netdev->manager->genl, message, &serial); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not set wireguard device: %m"); + + } while (peer || mask_start); + + return 0; +} + +static WireguardEndpoint* wireguard_endpoint_free(WireguardEndpoint *e) { + if (!e) + return NULL; + netdev_unref(e->netdev); + e->host = mfree(e->host); + e->port = mfree(e->port); + return mfree(e); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(WireguardEndpoint*, wireguard_endpoint_free); + +static int on_resolve_retry(sd_event_source *s, usec_t usec, void *userdata) { + NetDev *netdev = userdata; + Wireguard *w; + + assert(netdev); + w = WIREGUARD(netdev); + assert(w); + + w->resolve_retry_event_source = sd_event_source_unref(w->resolve_retry_event_source); + + w->unresolved_endpoints = w->failed_endpoints; + w->failed_endpoints = NULL; + + resolve_endpoints(netdev); + + return 0; +} + +/* + * Given the number of retries this function will return will an exponential + * increasing time in milliseconds to wait starting at 200ms and capped at 25 seconds. + */ +static int exponential_backoff_milliseconds(unsigned n_retries) { + return (2 << MAX(n_retries, 7U)) * 100 * USEC_PER_MSEC; +} + +static int wireguard_resolve_handler(sd_resolve_query *q, + int ret, + const struct addrinfo *ai, + void *userdata) { + NetDev *netdev; + Wireguard *w; + _cleanup_(wireguard_endpoint_freep) WireguardEndpoint *e; + int r; + + assert(userdata); + e = userdata; + netdev = e->netdev; + + assert(netdev); + w = WIREGUARD(netdev); + assert(w); + + w->resolve_query = sd_resolve_query_unref(w->resolve_query); + + if (ret != 0) { + log_netdev_error(netdev, "Failed to resolve host '%s:%s': %s", e->host, e->port, gai_strerror(ret)); + LIST_PREPEND(endpoints, w->failed_endpoints, e); + e = NULL; + } else if ((ai->ai_family == AF_INET && ai->ai_addrlen == sizeof(struct sockaddr_in)) || + (ai->ai_family == AF_INET6 && ai->ai_addrlen == sizeof(struct sockaddr_in6))) + memcpy(&e->peer->endpoint, ai->ai_addr, ai->ai_addrlen); + else + log_netdev_error(netdev, "Neither IPv4 nor IPv6 address found for peer endpoint: %s:%s", e->host, e->port); + + if (w->unresolved_endpoints) { + resolve_endpoints(netdev); + return 0; + } + + set_wireguard_interface(netdev); + if (w->failed_endpoints) { + w->n_retries++; + r = sd_event_add_time(netdev->manager->event, + &w->resolve_retry_event_source, + CLOCK_MONOTONIC, + now(CLOCK_MONOTONIC) + exponential_backoff_milliseconds(w->n_retries), + 0, + on_resolve_retry, + netdev); + if (r < 0) + log_netdev_warning_errno(netdev, r, "Could not arm resolve retry handler: %m"); + } + + return 0; +} + +static void resolve_endpoints(NetDev *netdev) { + int r = 0; + Wireguard *w; + WireguardEndpoint *endpoint; + static const struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_DGRAM, + .ai_protocol = IPPROTO_UDP + }; + + assert(netdev); + w = WIREGUARD(netdev); + assert(w); + + LIST_FOREACH(endpoints, endpoint, w->unresolved_endpoints) { + r = sd_resolve_getaddrinfo(netdev->manager->resolve, + &w->resolve_query, + endpoint->host, + endpoint->port, + &hints, + wireguard_resolve_handler, + endpoint); + + if (r == -ENOBUFS) + break; + + LIST_REMOVE(endpoints, w->unresolved_endpoints, endpoint); + + if (r < 0) + log_netdev_error_errno(netdev, r, "Failed create resolver: %m"); + } +} + + +static int netdev_wireguard_post_create(NetDev *netdev, Link *link, sd_netlink_message *m) { + Wireguard *w; + + assert(netdev); + w = WIREGUARD(netdev); + assert(w); + + set_wireguard_interface(netdev); + resolve_endpoints(netdev); + return 0; +} + +int config_parse_wireguard_listen_port(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + uint16_t *s = data; + uint16_t port = 0; + int r; + + assert(rvalue); + assert(data); + + if (!streq(rvalue, "auto")) { + r = parse_ip_port(rvalue, &port); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, r, "Invalid port specification, ignoring assignment: %s", rvalue); + } + + *s = port; + + return 0; +} + +static int parse_wireguard_key(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + _cleanup_free_ void *key = NULL; + size_t len; + int r; + + assert(filename); + assert(rvalue); + assert(userdata); + + r = unbase64mem(rvalue, strlen(rvalue), &key, &len); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Could not parse wireguard key \"%s\", ignoring assignment: %m", rvalue); + return 0; + } + if (len != WG_KEY_LEN) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Wireguard key is too short, ignoring assignment: %s", rvalue); + return 0; + } + + memcpy(userdata, key, WG_KEY_LEN); + return true; +} + +int config_parse_wireguard_private_key(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + Wireguard *w; + + assert(data); + + w = WIREGUARD(data); + + assert(w); + + return parse_wireguard_key(unit, + filename, + line, + section, + section_line, + lvalue, + ltype, + rvalue, + data, + &w->private_key); + +} + +int config_parse_wireguard_preshared_key(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + Wireguard *w; + WireguardPeer *peer; + + assert(data); + + w = WIREGUARD(data); + + assert(w); + + peer = wireguard_peer_new(w, section_line); + if (!peer) + return log_oom(); + + return parse_wireguard_key(unit, + filename, + line, + section, + section_line, + lvalue, + ltype, + rvalue, + data, + peer->preshared_key); +} + + +int config_parse_wireguard_public_key(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + Wireguard *w; + WireguardPeer *peer; + + assert(data); + + w = WIREGUARD(data); + + assert(w); + + peer = wireguard_peer_new(w, section_line); + if (!peer) + return log_oom(); + + return parse_wireguard_key(unit, + filename, + line, + section, + section_line, + lvalue, + ltype, + rvalue, + data, + peer->public_key); +} + +int config_parse_wireguard_allowed_ips(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + union in_addr_union addr; + unsigned char prefixlen; + int r, family; + Wireguard *w; + WireguardPeer *peer; + WireguardIPmask *ipmask; + + assert(rvalue); + assert(data); + + w = WIREGUARD(data); + + peer = wireguard_peer_new(w, section_line); + if (!peer) + return log_oom(); + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&rvalue, &word, "," WHITESPACE, 0); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to split allowed ips \"%s\" option: %m", rvalue); + break; + } + + r = in_addr_prefix_from_string_auto(word, &family, &addr, &prefixlen); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Network address is invalid, ignoring assignment: %s", word); + return 0; + } + + ipmask = new0(WireguardIPmask, 1); + if (!ipmask) + return log_oom(); + ipmask->family = family; + ipmask->ip.in6 = addr.in6; + ipmask->cidr = prefixlen; + + LIST_PREPEND(ipmasks, peer->ipmasks, ipmask); + } + + return 0; +} + +int config_parse_wireguard_endpoint(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + Wireguard *w; + WireguardPeer *peer; + size_t len; + const char *begin, *end = NULL; + _cleanup_free_ char *host = NULL, *port = NULL; + _cleanup_(wireguard_endpoint_freep) WireguardEndpoint *endpoint = NULL; + + assert(data); + assert(rvalue); + + w = WIREGUARD(data); + + assert(w); + + peer = wireguard_peer_new(w, section_line); + if (!peer) + return log_oom(); + + endpoint = new0(WireguardEndpoint, 1); + if (!endpoint) + return log_oom(); + + if (rvalue[0] == '[') { + begin = &rvalue[1]; + end = strchr(rvalue, ']'); + if (!end) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Unable to find matching brace of endpoint, ignoring assignment: %s", rvalue); + return 0; + } + len = end - begin; + ++end; + if (*end != ':' || !*(end + 1)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Unable to find port of endpoint: %s", rvalue); + return 0; + } + ++end; + } else { + begin = rvalue; + end = strrchr(rvalue, ':'); + if (!end || !*(end + 1)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Unable to find port of endpoint: %s", rvalue); + return 0; + } + len = end - begin; + ++end; + } + + host = strndup(begin, len); + if (!host) + return log_oom(); + + port = strdup(end); + if (!port) + return log_oom(); + + endpoint->peer = peer; + endpoint->host = host; + endpoint->port = port; + endpoint->netdev = netdev_ref(data); + LIST_PREPEND(endpoints, w->unresolved_endpoints, endpoint); + + peer = NULL; + host = NULL; + port = NULL; + endpoint = NULL; + + return 0; +} + +int config_parse_wireguard_keepalive(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + int r; + uint16_t keepalive = 0; + Wireguard *w; + WireguardPeer *peer; + + assert(rvalue); + assert(data); + + w = WIREGUARD(data); + + assert(w); + + peer = wireguard_peer_new(w, section_line); + if (!peer) + return log_oom(); + + if (streq(rvalue, "off")) + keepalive = 0; + else { + r = safe_atou16(rvalue, &keepalive); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, r, "The persistent keepalive interval must be 0-65535. Ignore assignment: %s", rvalue); + } + + peer->persistent_keepalive_interval = keepalive; + return 0; +} + +static void wireguard_init(NetDev *netdev) { + Wireguard *w; + + assert(netdev); + + w = WIREGUARD(netdev); + + assert(w); + + w->flags = WGDEVICE_F_REPLACE_PEERS; +} + +static void wireguard_done(NetDev *netdev) { + Wireguard *w; + WireguardPeer *peer; + WireguardIPmask *mask; + + assert(netdev); + w = WIREGUARD(netdev); + assert(!w->unresolved_endpoints); + w->resolve_retry_event_source = sd_event_source_unref(w->resolve_retry_event_source); + + while ((peer = w->peers)) { + LIST_REMOVE(peers, w->peers, peer); + while ((mask = peer->ipmasks)) { + LIST_REMOVE(ipmasks, peer->ipmasks, mask); + free(mask); + } + free(peer); + } +} + +const NetDevVTable wireguard_vtable = { + .object_size = sizeof(Wireguard), + .sections = "Match\0NetDev\0WireGuard\0WireGuardPeer\0", + .post_create = netdev_wireguard_post_create, + .init = wireguard_init, + .done = wireguard_done, + .create_type = NETDEV_CREATE_INDEPENDENT, +}; diff --git a/src/network/netdev/wireguard.h b/src/network/netdev/wireguard.h new file mode 100644 index 0000000000..f788fa4221 --- /dev/null +++ b/src/network/netdev/wireguard.h @@ -0,0 +1,98 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 Jörg Thalheim + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +typedef struct Wireguard Wireguard; + +#include "netdev.h" +#include "sd-resolve.h" +#include "wireguard-netlink.h" +#include "socket-util.h" +#include "in-addr-util.h" + +#ifndef IFNAMSIZ +#define IFNAMSIZ 16 +#endif + +typedef struct WireguardIPmask { + uint16_t family; + union in_addr_union ip; + uint8_t cidr; + + LIST_FIELDS(struct WireguardIPmask, ipmasks); +} WireguardIPmask; + +typedef struct WireguardPeer { + uint8_t public_key[WG_KEY_LEN]; + uint8_t preshared_key[WG_KEY_LEN]; + uint32_t flags; + + union sockaddr_union endpoint; + + uint16_t persistent_keepalive_interval; + + LIST_HEAD(WireguardIPmask, ipmasks); + LIST_FIELDS(struct WireguardPeer, peers); +} WireguardPeer; + +typedef struct WireguardEndpoint { + char *host; + char *port; + + NetDev *netdev; + WireguardPeer *peer; + + LIST_FIELDS(struct WireguardEndpoint, endpoints); +} WireguardEndpoint; + +struct Wireguard { + NetDev meta; + unsigned last_peer_section; + + char interface[IFNAMSIZ]; + uint32_t flags; + + uint8_t public_key[WG_KEY_LEN]; + uint8_t private_key[WG_KEY_LEN]; + uint32_t fwmark; + + uint16_t port; + + LIST_HEAD(WireguardPeer, peers); + size_t allocation_size; + sd_event_source *resolve_retry_event_source; + + LIST_HEAD(WireguardEndpoint, unresolved_endpoints); + LIST_HEAD(WireguardEndpoint, failed_endpoints); + unsigned n_retries; + sd_resolve_query *resolve_query; +}; + +DEFINE_NETDEV_CAST(WIREGUARD, Wireguard); +extern const NetDevVTable wireguard_vtable; + +int config_parse_wireguard_allowed_ips(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_wireguard_endpoint(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_wireguard_listen_port(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); + +int config_parse_wireguard_public_key(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_wireguard_private_key(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_wireguard_preshared_key(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_wireguard_keepalive(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/shared/meson.build b/src/shared/meson.build index 06a944c49d..dbf6cca321 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -120,6 +120,7 @@ shared_sources = ''' volatile-util.h watchdog.c watchdog.h + wireguard-netlink.h '''.split() test_tables_h = files('test-tables.h') diff --git a/src/shared/wireguard-netlink.h b/src/shared/wireguard-netlink.h new file mode 100644 index 0000000000..eb170915a6 --- /dev/null +++ b/src/shared/wireguard-netlink.h @@ -0,0 +1,179 @@ +#pragma once + +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR MIT) + * + * Copyright (C) 2015-2017 Jason A. Donenfeld . All Rights Reserved. + * + * Documentation + * ============= + * + * The below enums and macros are for interfacing with WireGuard, using generic + * netlink, with family WG_GENL_NAME and version WG_GENL_VERSION. It defines two + * methods: get and set. Note that while they share many common attributes, these + * two functions actually accept a slightly different set of inputs and outputs. + * + * WG_CMD_GET_DEVICE + * ----------------- + * + * May only be called via NLM_F_REQUEST | NLM_F_DUMP. The command should contain + * one but not both of: + * + * WGDEVICE_A_IFINDEX: NLA_U32 + * WGDEVICE_A_IFNAME: NLA_NUL_STRING, maxlen IFNAMESIZ - 1 + * + * The kernel will then return several messages (NLM_F_MULTI) containing the following + * tree of nested items: + * + * WGDEVICE_A_IFINDEX: NLA_U32 + * WGDEVICE_A_IFNAME: NLA_NUL_STRING, maxlen IFNAMESIZ - 1 + * WGDEVICE_A_PRIVATE_KEY: len WG_KEY_LEN + * WGDEVICE_A_PUBLIC_KEY: len WG_KEY_LEN + * WGDEVICE_A_LISTEN_PORT: NLA_U16 + * WGDEVICE_A_FWMARK: NLA_U32 + * WGDEVICE_A_PEERS: NLA_NESTED + * 0: NLA_NESTED + * WGPEER_A_PUBLIC_KEY: len WG_KEY_LEN + * WGPEER_A_PRESHARED_KEY: len WG_KEY_LEN + * WGPEER_A_ENDPOINT: struct sockaddr_in or struct sockaddr_in6 + * WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL: NLA_U16 + * WGPEER_A_LAST_HANDSHAKE_TIME: struct timespec + * WGPEER_A_RX_BYTES: NLA_U64 + * WGPEER_A_TX_BYTES: NLA_U64 + * WGPEER_A_ALLOWEDIPS: NLA_NESTED + * 0: NLA_NESTED + * WGALLOWEDIP_A_FAMILY: NLA_U16 + * WGALLOWEDIP_A_IPADDR: struct in_addr or struct in6_addr + * WGALLOWEDIP_A_CIDR_MASK: NLA_U8 + * 1: NLA_NESTED + * ... + * 2: NLA_NESTED + * ... + * ... + * 1: NLA_NESTED + * ... + * ... + * + * It is possible that all of the allowed IPs of a single peer will not + * fit within a single netlink message. In that case, the same peer will + * be written in the following message, except it will only contain + * WGPEER_A_PUBLIC_KEY and WGPEER_A_ALLOWEDIPS. This may occur several + * times in a row for the same peer. It is then up to the receiver to + * coalesce adjacent peers. Likewise, it is possible that all peers will + * not fit within a single message. So, subsequent peers will be sent + * in following messages, except those will only contain WGDEVICE_A_IFNAME + * and WGDEVICE_A_PEERS. It is then up to the receiver to coalesce these + * messages to form the complete list of peers. + * + * Since this is an NLA_F_DUMP command, the final message will always be + * NLMSG_DONE, even if an error occurs. However, this NLMSG_DONE message + * contains an integer error code. It is either zero or a negative error + * code corresponding to the errno. + * + * WG_CMD_SET_DEVICE + * ----------------- + * + * May only be called via NLM_F_REQUEST. The command should contain the following + * tree of nested items, containing one but not both of WGDEVICE_A_IFINDEX + * and WGDEVICE_A_IFNAME: + * + * WGDEVICE_A_IFINDEX: NLA_U32 + * WGDEVICE_A_IFNAME: NLA_NUL_STRING, maxlen IFNAMESIZ - 1 + * WGDEVICE_A_FLAGS: NLA_U32, 0 or WGDEVICE_F_REPLACE_PEERS if all current + * peers should be removed prior to adding the list below. + * WGDEVICE_A_PRIVATE_KEY: len WG_KEY_LEN, all zeros to remove + * WGDEVICE_A_LISTEN_PORT: NLA_U16, 0 to choose randomly + * WGDEVICE_A_FWMARK: NLA_U32, 0 to disable + * WGDEVICE_A_PEERS: NLA_NESTED + * 0: NLA_NESTED + * WGPEER_A_PUBLIC_KEY: len WG_KEY_LEN + * WGPEER_A_FLAGS: NLA_U32, 0 and/or WGPEER_F_REMOVE_ME if the specified peer + * should be removed rather than added/updated and/or + * WGPEER_F_REPLACE_ALLOWEDIPS if all current allowed IPs of + * this peer should be removed prior to adding the list below. + * WGPEER_A_PRESHARED_KEY: len WG_KEY_LEN, all zeros to remove + * WGPEER_A_ENDPOINT: struct sockaddr_in or struct sockaddr_in6 + * WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL: NLA_U16, 0 to disable + * WGPEER_A_ALLOWEDIPS: NLA_NESTED + * 0: NLA_NESTED + * WGALLOWEDIP_A_FAMILY: NLA_U16 + * WGALLOWEDIP_A_IPADDR: struct in_addr or struct in6_addr + * WGALLOWEDIP_A_CIDR_MASK: NLA_U8 + * 1: NLA_NESTED + * ... + * 2: NLA_NESTED + * ... + * ... + * 1: NLA_NESTED + * ... + * ... + * + * It is possible that the amount of configuration data exceeds that of + * the maximum message length accepted by the kernel. In that case, + * several messages should be sent one after another, with each + * successive one filling in information not contained in the prior. Note + * that if WGDEVICE_F_REPLACE_PEERS is specified in the first message, it + * probably should not be specified in fragments that come after, so that + * the list of peers is only cleared the first time but appened after. + * Likewise for peers, if WGPEER_F_REPLACE_ALLOWEDIPS is specified in the + * first message of a peer, it likely should not be specified in subsequent + * fragments. + * + * If an error occurs, NLMSG_ERROR will reply containing an errno. + */ + +#define WG_GENL_NAME "wireguard" +#define WG_GENL_VERSION 1 + +#define WG_KEY_LEN 32 + +enum wg_cmd { + WG_CMD_GET_DEVICE, + WG_CMD_SET_DEVICE, + __WG_CMD_MAX +}; +#define WG_CMD_MAX (__WG_CMD_MAX - 1) + +enum wgdevice_flag { + WGDEVICE_F_REPLACE_PEERS = 1U << 0 +}; +enum wgdevice_attribute { + WGDEVICE_A_UNSPEC, + WGDEVICE_A_IFINDEX, + WGDEVICE_A_IFNAME, + WGDEVICE_A_PRIVATE_KEY, + WGDEVICE_A_PUBLIC_KEY, + WGDEVICE_A_FLAGS, + WGDEVICE_A_LISTEN_PORT, + WGDEVICE_A_FWMARK, + WGDEVICE_A_PEERS, + __WGDEVICE_A_LAST +}; +#define WGDEVICE_A_MAX (__WGDEVICE_A_LAST - 1) + +enum wgpeer_flag { + WGPEER_F_REMOVE_ME = 1U << 0, + WGPEER_F_REPLACE_ALLOWEDIPS = 1U << 1 +}; +enum wgpeer_attribute { + WGPEER_A_UNSPEC, + WGPEER_A_PUBLIC_KEY, + WGPEER_A_PRESHARED_KEY, + WGPEER_A_FLAGS, + WGPEER_A_ENDPOINT, + WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, + WGPEER_A_LAST_HANDSHAKE_TIME, + WGPEER_A_RX_BYTES, + WGPEER_A_TX_BYTES, + WGPEER_A_ALLOWEDIPS, + __WGPEER_A_LAST +}; +#define WGPEER_A_MAX (__WGPEER_A_LAST - 1) + +enum wgallowedip_attribute { + WGALLOWEDIP_A_UNSPEC, + WGALLOWEDIP_A_FAMILY, + WGALLOWEDIP_A_IPADDR, + WGALLOWEDIP_A_CIDR_MASK, + __WGALLOWEDIP_A_LAST +}; +#define WGALLOWEDIP_A_MAX (__WGALLOWEDIP_A_LAST - 1) diff --git a/src/systemd/sd-netlink.h b/src/systemd/sd-netlink.h index 7c4d51ea79..e742807e92 100644 --- a/src/systemd/sd-netlink.h +++ b/src/systemd/sd-netlink.h @@ -36,7 +36,7 @@ _SD_BEGIN_DECLARATIONS; typedef struct sd_netlink sd_netlink; typedef struct sd_genl_socket sd_genl_socket; typedef struct sd_netlink_message sd_netlink_message; -typedef enum {SD_GENL_ID_CTRL} sd_genl_family; +typedef enum {SD_GENL_ID_CTRL, SD_GENL_WIREGUARD} sd_genl_family; /* callback */