diff options
author | Thomas Haller <thaller@redhat.com> | 2018-12-25 18:41:28 +0100 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2019-01-08 10:48:52 +0100 |
commit | 2fab3da78b9ff4ad273cb54834ad584408260770 (patch) | |
tree | 8cf8143594932bd4ee06c12826b0928878123e3f | |
parent | 756aec8d9259a371915f46935f978cfef923f030 (diff) | |
download | NetworkManager-th/wireguard-pt2.tar.gz |
platform: create wireguard netdev interfaceth/wireguard-pt2
The netlink code for WG_CMD_SET_DEVICE is strongly inspired by
WireGuard ([1]) and systemd ([2]).
Currently, nm_platform_link_wireguard_change() always aims to reset
all peers and allowed-ips settings. I think that should be improved
in the future, to support only partial updates.
[1] https://git.zx2c4.com/WireGuard/tree/contrib/examples/embeddable-wg-library/wireguard.c?id=5e99a6d43fe2351adf36c786f5ea2086a8fe7ab8#n1073
[2] https://github.com/systemd/systemd/blob/04ca4d191b13e79e5701ed22dc972f08628d7bcc/src/network/netdev/wireguard.c#L48
-rw-r--r-- | src/platform/nm-linux-platform.c | 339 | ||||
-rw-r--r-- | src/platform/nm-platform.c | 55 | ||||
-rw-r--r-- | src/platform/nm-platform.h | 19 | ||||
-rw-r--r-- | src/platform/nmp-object.h | 4 | ||||
-rw-r--r-- | src/platform/tests/test-link.c | 273 |
5 files changed, 679 insertions, 11 deletions
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c index 2979ee5d38..7db6a9261e 100644 --- a/src/platform/nm-linux-platform.c +++ b/src/platform/nm-linux-platform.c @@ -187,6 +187,12 @@ G_STATIC_ASSERT (RTA_MAX == (__RTA_MAX - 1)); #define WG_CMD_GET_DEVICE 0 #define WG_CMD_SET_DEVICE 1 +#define WGDEVICE_F_REPLACE_PEERS ((guint32) (1U << 0)) + +#define WGPEER_F_REMOVE_ME ((guint32) (1U << 0)) +#define WGPEER_F_REPLACE_ALLOWEDIPS ((guint32) (1U << 1)) + + #define WGDEVICE_A_UNSPEC 0 #define WGDEVICE_A_IFINDEX 1 #define WGDEVICE_A_IFNAME 2 @@ -2164,9 +2170,9 @@ _wireguard_get_device_cb (struct nl_msg *msg, void *arg) static const NMPObject * _wireguard_read_info (NMPlatform *platform /* used only as logging context */, - struct nl_sock *genl, - int wireguard_family_id, - int ifindex) + struct nl_sock *genl, + int wireguard_family_id, + int ifindex) { nm_auto_nlmsg struct nl_msg *msg = NULL; NMPObject *obj = NULL; @@ -2182,6 +2188,8 @@ _wireguard_read_info (NMPlatform *platform /* used only as logging context */, nm_assert (wireguard_family_id >= 0); nm_assert (ifindex > 0); + _LOGT ("wireguard: fetching infomation for ifindex %d (genl-id %d)...", ifindex, wireguard_family_id); + msg = nlmsg_alloc (); if (!genlmsg_put (msg, @@ -2290,6 +2298,330 @@ nla_put_failure: g_return_val_if_reached (NULL); } +static int +_wireguard_get_family_id (NMPlatform *platform, int ifindex_try) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + int wireguard_family_id = -1; + + if (ifindex_try > 0) { + const NMPlatformLink *plink; + + if (nm_platform_link_get_lnk_wireguard (platform, ifindex_try, &plink)) + wireguard_family_id = NMP_OBJECT_UP_CAST (plink)->_link.wireguard_family_id; + } + if (wireguard_family_id < 0) + wireguard_family_id = genl_ctrl_resolve (priv->genl, "wireguard"); + return wireguard_family_id; +} + +static const NMPObject * +_wireguard_refresh_link (NMPlatform *platform, + int wireguard_family_id, + int ifindex) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + nm_auto_nmpobj const NMPObject *obj_old = NULL; + nm_auto_nmpobj const NMPObject *obj_new = NULL; + nm_auto_nmpobj const NMPObject *lnk_new = NULL; + NMPCacheOpsType cache_op; + const NMPObject *plink = NULL; + nm_auto_nmpobj NMPObject *obj = NULL; + + nm_assert (wireguard_family_id >= 0); + nm_assert (ifindex > 0); + + nm_platform_process_events (platform); + + plink = nm_platform_link_get_obj (platform, ifindex, TRUE); + + if ( !plink + || plink->link.type != NM_LINK_TYPE_WIREGUARD) { + nm_platform_link_refresh (platform, ifindex); + plink = nm_platform_link_get_obj (platform, ifindex, TRUE); + if ( !plink + || plink->link.type != NM_LINK_TYPE_WIREGUARD) + return NULL; + if (NMP_OBJECT_GET_TYPE (plink->_link.netlink.lnk) == NMP_OBJECT_TYPE_LNK_WIREGUARD) + lnk_new = nmp_object_ref (plink->_link.netlink.lnk); + } else { + lnk_new = _wireguard_read_info (platform, + priv->genl, + wireguard_family_id, + ifindex); + if (!lnk_new) { + if (NMP_OBJECT_GET_TYPE (plink->_link.netlink.lnk) == NMP_OBJECT_TYPE_LNK_WIREGUARD) + lnk_new = nmp_object_ref (plink->_link.netlink.lnk); + } else if (nmp_object_equal (plink->_link.netlink.lnk, lnk_new)) { + nmp_object_unref (lnk_new); + lnk_new = nmp_object_ref (plink->_link.netlink.lnk); + } + } + + if ( plink->_link.wireguard_family_id == wireguard_family_id + && plink->_link.netlink.lnk == lnk_new) + return plink; + + /* we use nmp_cache_update_netlink() to re-inject the new object into the cache. + * For that, we need to clone it, and tweak it so that it's suitable. It's a bit + * of a hack, in particular that we need to clear driver and udev-device. */ + obj = nmp_object_clone (plink, FALSE); + obj->_link.wireguard_family_id = wireguard_family_id; + nmp_object_unref (obj->_link.netlink.lnk); + obj->_link.netlink.lnk = g_steal_pointer (&lnk_new); + obj->link.driver = NULL; + nm_clear_pointer (&obj->_link.udev.device, udev_device_unref); + + cache_op = nmp_cache_update_netlink (nm_platform_get_cache (platform), + obj, + FALSE, + &obj_old, + &obj_new); + nm_assert (NM_IN_SET (cache_op, NMP_CACHE_OPS_UPDATED)); + if (cache_op != NMP_CACHE_OPS_UNCHANGED) { + cache_on_change (platform, cache_op, obj_old, obj_new); + nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, obj_new); + } + + nm_assert ( !obj_new + || ( NMP_OBJECT_GET_TYPE (obj_new) == NMP_OBJECT_TYPE_LINK + && obj_new->link.type == NM_LINK_TYPE_WIREGUARD + && ( !obj_new->_link.netlink.lnk + || NMP_OBJECT_GET_TYPE (obj_new->_link.netlink.lnk) == NMP_OBJECT_TYPE_LNK_WIREGUARD))); + return obj_new; +} + +static int +_wireguard_create_change_nlmsgs (NMPlatform *platform, + int ifindex, + int wireguard_family_id, + const NMPlatformLnkWireGuard *lnk_wireguard, + const NMPWireGuardPeer *peers, + guint peers_len, + GPtrArray **out_msgs) +{ + gs_unref_ptrarray GPtrArray *msgs = NULL; + nm_auto_nlmsg struct nl_msg *msg = NULL; + const guint IDX_NIL = G_MAXUINT; + guint idx_peer_curr; + guint idx_allowed_ips_curr; + struct nlattr *nest_peers; + struct nlattr *nest_curr_peer; + struct nlattr *nest_allowed_ips; + struct nlattr *nest_curr_allowed_ip; + +#define _nla_nest_end(msg, nest_start) \ + G_STMT_START { \ + if (nla_nest_end ((msg), (nest_start)) < 0) \ + g_return_val_if_reached (-NME_BUG); \ + } G_STMT_END + + /* Adapted from LGPL-2.1+ code [1]. + * + * [1] https://git.zx2c4.com/WireGuard/tree/contrib/examples/embeddable-wg-library/wireguard.c?id=5e99a6d43fe2351adf36c786f5ea2086a8fe7ab8#n1073 */ + + idx_peer_curr = IDX_NIL; + idx_allowed_ips_curr = IDX_NIL; + + /* TODO: for the moment, we always reset all peers and allowed-ips (WGDEVICE_F_REPLACE_PEERS, WGPEER_F_REPLACE_ALLOWEDIPS). + * The platform API should be extended to also support partial updates. In particular, configuring the same configuration + * multiple times, should not clear and re-add all settings, but rather sync the existing settings with the desired configuration. */ + +again: + + msg = nlmsg_alloc (); + if (!genlmsg_put (msg, + NL_AUTO_PORT, + NL_AUTO_SEQ, + wireguard_family_id, + 0, + NLM_F_REQUEST, + WG_CMD_SET_DEVICE, + 1)) + g_return_val_if_reached (-NME_BUG); + + NLA_PUT_U32 (msg, WGDEVICE_A_IFINDEX, (guint32) ifindex); + + if (idx_peer_curr == IDX_NIL) { + NLA_PUT (msg, WGDEVICE_A_PRIVATE_KEY, sizeof (lnk_wireguard->private_key), lnk_wireguard->private_key); + NLA_PUT_U16 (msg, WGDEVICE_A_LISTEN_PORT, lnk_wireguard->listen_port); + NLA_PUT_U32 (msg, WGDEVICE_A_FWMARK, lnk_wireguard->fwmark); + NLA_PUT_U32 (msg, WGDEVICE_A_FLAGS, WGDEVICE_F_REPLACE_PEERS); + } + + if (peers_len == 0) + goto send; + + nest_curr_peer = NULL; + nest_allowed_ips = NULL; + nest_curr_allowed_ip = NULL; + + nest_peers = nla_nest_start (msg, WGDEVICE_A_PEERS); + if (!nest_peers) + g_return_val_if_reached (-NME_BUG); + + if (idx_peer_curr == IDX_NIL) + idx_peer_curr = 0; + for (; idx_peer_curr < peers_len; idx_peer_curr++) { + const NMPWireGuardPeer *p = &peers[idx_peer_curr]; + + nest_curr_peer = nla_nest_start (msg, 0); + if (!nest_curr_peer) + goto toobig_peers; + + if (nla_put (msg, WGPEER_A_PUBLIC_KEY, NMP_WIREGUARD_PUBLIC_KEY_LEN, p->public_key) < 0) + goto toobig_peers; + + if (idx_allowed_ips_curr == IDX_NIL) { + + if (nla_put (msg, WGPEER_A_PRESHARED_KEY, sizeof (p->preshared_key), p->preshared_key) < 0) + goto toobig_peers; + + if (nla_put_uint16 (msg, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, p->persistent_keepalive_interval) < 0) + goto toobig_peers; + + if (nla_put_uint32 (msg, WGPEER_A_FLAGS, WGPEER_F_REPLACE_ALLOWEDIPS) < 0) + goto toobig_peers; + + g_return_val_if_fail (NM_IN_SET (p->endpoint.sa.sa_family, AF_INET, AF_INET6), -NME_BUG); + + if (nla_put (msg, + WGPEER_A_ENDPOINT, + p->endpoint.sa.sa_family == AF_INET + ? sizeof (p->endpoint.in) + : sizeof (p->endpoint.in6), + &p->endpoint) < 0) + goto toobig_peers; + } + + if (p->allowed_ips_len > 0) { + if (idx_allowed_ips_curr == IDX_NIL) + idx_allowed_ips_curr = 0; + + nest_allowed_ips = nla_nest_start (msg, WGPEER_A_ALLOWEDIPS); + if (!nest_allowed_ips) + goto toobig_allowedips; + + for (; idx_allowed_ips_curr < p->allowed_ips_len; idx_allowed_ips_curr++) { + const NMPWireGuardAllowedIP *aip = &p->allowed_ips[idx_allowed_ips_curr]; + + nest_curr_allowed_ip = nla_nest_start (msg, 0); + if (!nest_curr_allowed_ip) + goto toobig_allowedips; + + g_return_val_if_fail (NM_IN_SET (aip->family, AF_INET, AF_INET6), -NME_BUG); + + if (nla_put_uint16 (msg, WGALLOWEDIP_A_FAMILY, aip->family) < 0) + goto toobig_allowedips; + if (nla_put (msg, + WGALLOWEDIP_A_IPADDR, + nm_utils_addr_family_to_size (aip->family), + &aip->addr) < 0) + goto toobig_allowedips; + if (nla_put_uint8 (msg, WGALLOWEDIP_A_CIDR_MASK, aip->mask) < 0) + goto toobig_allowedips; + + _nla_nest_end (msg, nest_curr_allowed_ip); + nest_curr_allowed_ip = NULL; + } + idx_allowed_ips_curr = IDX_NIL; + + _nla_nest_end (msg, nest_allowed_ips); + nest_allowed_ips = NULL; + } + + _nla_nest_end (msg, nest_curr_peer); + nest_curr_peer = NULL; + } + + _nla_nest_end (msg, nest_peers); + goto send; + +toobig_allowedips: + if (nest_curr_allowed_ip) + nla_nest_cancel (msg, nest_curr_allowed_ip); + if (nest_allowed_ips) + nla_nest_cancel (msg, nest_allowed_ips); + _nla_nest_end (msg, nest_curr_peer); + _nla_nest_end (msg, nest_peers); + goto send; + +toobig_peers: + if (nest_curr_peer) + nla_nest_cancel (msg, nest_curr_peer); + _nla_nest_end (msg, nest_peers); + goto send; + +send: + if (!msgs) + msgs = g_ptr_array_new_with_free_func ((GDestroyNotify) nlmsg_free); + g_ptr_array_add (msgs, g_steal_pointer (&msg)); + + if ( idx_peer_curr != IDX_NIL + && idx_peer_curr < peers_len) + goto again; + + NM_SET_OUT (out_msgs, g_steal_pointer (&msgs)); + return 0; + +nla_put_failure: + g_return_val_if_reached (-NME_BUG); + +#undef _nla_nest_end +} + +static int +link_wireguard_change (NMPlatform *platform, + int ifindex, + const NMPlatformLnkWireGuard *lnk_wireguard, + const NMPWireGuardPeer *peers, + guint peers_len) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + gs_unref_ptrarray GPtrArray *msgs = NULL; + int wireguard_family_id; + guint i; + int r; + + wireguard_family_id = _wireguard_get_family_id (platform, ifindex); + if (wireguard_family_id < 0) + return -NME_PL_NO_FIRMWARE; + + r = _wireguard_create_change_nlmsgs (platform, + ifindex, + wireguard_family_id, + lnk_wireguard, + peers, + peers_len, + &msgs); + if (r < 0) { + _LOGW ("wireguard: set-device, cannot construct netlink message: %s", nm_strerror (r)); + return r; + } + + for (i = 0; i < msgs->len; i++) { + r = nl_send_auto (priv->genl, msgs->pdata[i]); + if (r < 0) { + _LOGW ("wireguard: set-device, send netlink message #%u failed: %s", i, nm_strerror (r)); + return r; + } + + do { + r = nl_recvmsgs (priv->genl, NULL); + } while (r == -EAGAIN); + if (r < 0) { + _LOGW ("wireguard: set-device, message #%u was rejected: %s", i, nm_strerror (r)); + return r; + } + + _LOGT ("wireguard: set-device, message #%u sent and confirmed", i); + } + + _wireguard_refresh_link (platform, wireguard_family_id, ifindex); + + return 0; +} + /*****************************************************************************/ /* Copied and heavily modified from libnl3's link_msg_parser(). */ @@ -8055,6 +8387,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass) platform_class->vlan_add = vlan_add; platform_class->link_vlan_change = link_vlan_change; + platform_class->link_wireguard_change = link_wireguard_change; platform_class->link_vxlan_add = link_vxlan_add; platform_class->infiniband_partition_add = infiniband_partition_add; diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c index c750caf567..d097186ef7 100644 --- a/src/platform/nm-platform.c +++ b/src/platform/nm-platform.c @@ -1985,6 +1985,61 @@ nm_platform_link_get_lnk_wireguard (NMPlatform *self, int ifindex, const NMPlatf /*****************************************************************************/ +int +nm_platform_link_wireguard_add (NMPlatform *self, + const char *name, + const NMPlatformLink **out_link) +{ + return nm_platform_link_add (self, name, NM_LINK_TYPE_WIREGUARD, NULL, NULL, 0, out_link); +} + +int +nm_platform_link_wireguard_change (NMPlatform *self, + int ifindex, + const NMPlatformLnkWireGuard *lnk_wireguard, + const struct _NMPWireGuardPeer *peers, + guint peers_len) +{ + _CHECK_SELF (self, klass, -NME_BUG); + + nm_assert (klass->link_wireguard_change); + + if (_LOGD_ENABLED ()) { + char buf_lnk[256]; + char buf_peers[512]; + + buf_peers[0] = '\0'; + if (peers_len > 0) { + char *b = buf_peers; + gsize len = sizeof (buf_peers); + guint i; + + nm_utils_strbuf_append_str (&b, &len, " { "); + for (i = 0; i < peers_len; i++) { + nm_utils_strbuf_append_str (&b, &len, " { "); + nm_platform_wireguard_peer_to_string (&peers[i], b, len); + nm_utils_strbuf_seek_end (&b, &len); + nm_utils_strbuf_append_str (&b, &len, " } "); + } + nm_utils_strbuf_append_str (&b, &len, "}"); + } + + _LOG3D ("link: change wireguard ifindex %d, %s, %u peers%s", + ifindex, + nm_platform_lnk_wireguard_to_string (lnk_wireguard, buf_lnk, sizeof (buf_lnk)), + peers_len, + buf_peers); + } + + return klass->link_wireguard_change (self, + ifindex, + lnk_wireguard, + peers, + peers_len); +} + +/*****************************************************************************/ + /** * nm_platform_link_bridge_add: * @self: platform instance diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h index 13e8fc4d60..9bfc4bc398 100644 --- a/src/platform/nm-platform.h +++ b/src/platform/nm-platform.h @@ -54,6 +54,8 @@ /*****************************************************************************/ +struct _NMPWireGuardPeer; + struct udev_device; typedef gboolean (*NMPObjectPredicateFunc) (const NMPObject *obj, @@ -825,6 +827,12 @@ typedef struct { gboolean (*link_can_assume) (NMPlatform *, int ifindex); + int (*link_wireguard_change) (NMPlatform *self, + int ifindex, + const NMPlatformLnkWireGuard *lnk_wireguard, + const struct _NMPWireGuardPeer *peers, + guint peers_len); + gboolean (*vlan_add) (NMPlatform *, const char *name, int parent, int vlanid, guint32 vlanflags, const NMPlatformLink **out_link); gboolean (*link_vlan_change) (NMPlatform *self, int ifindex, @@ -1377,6 +1385,16 @@ gboolean nm_platform_link_6lowpan_get_properties (NMPlatform *self, int ifindex, int *out_parent); +int nm_platform_link_wireguard_add (NMPlatform *self, + const char *name, + const NMPlatformLink **out_link); + +int nm_platform_link_wireguard_change (NMPlatform *self, + int ifindex, + const NMPlatformLnkWireGuard *lnk_wireguard, + const struct _NMPWireGuardPeer *peers, + guint peers_len); + const NMPlatformIP6Address *nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr address); gboolean nm_platform_object_delete (NMPlatform *self, const NMPObject *route); @@ -1477,7 +1495,6 @@ const char *nm_platform_vlan_qos_mapping_to_string (const char *name, char *buf, gsize len); -struct _NMPWireGuardPeer; const char *nm_platform_wireguard_peer_to_string (const struct _NMPWireGuardPeer *peer, char *buf, gsize len); diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h index efba5f1b8d..e46b16c8f0 100644 --- a/src/platform/nmp-object.h +++ b/src/platform/nmp-object.h @@ -45,9 +45,11 @@ typedef struct { typedef struct _NMPWireGuardPeer { NMSockAddrUnion endpoint; + struct timespec last_handshake_time; guint64 rx_bytes; guint64 tx_bytes; + union { const NMPWireGuardAllowedIP *allowed_ips; guint _construct_idx_start; @@ -56,7 +58,9 @@ typedef struct _NMPWireGuardPeer { guint allowed_ips_len; guint _construct_idx_end; }; + guint16 persistent_keepalive_interval; + guint8 public_key[NMP_WIREGUARD_PUBLIC_KEY_LEN]; guint8 preshared_key[NMP_WIREGUARD_SYMMETRIC_KEY_LEN]; } NMPWireGuardPeer; diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c index b3fd2bc0ae..031bb79a25 100644 --- a/src/platform/tests/test-link.c +++ b/src/platform/tests/test-link.c @@ -466,12 +466,20 @@ test_bridge (void) test_software (NM_LINK_TYPE_BRIDGE, "bridge"); } +static int +_system (const char *cmd) +{ + /* some gcc version really want to warn on -Werror=unused-result. Add a bogus wrapper + * function. */ + return system (cmd); +} + static void test_bond (void) { if (nmtstp_is_root_test () && !g_file_test ("/proc/1/net/bonding", G_FILE_TEST_IS_DIR) && - system("modprobe --show bonding") != 0) { + _system("modprobe --show bonding") != 0) { g_test_skip ("Skipping test for bonding: bonding module not available"); return; } @@ -684,6 +692,232 @@ test_external (void) /*****************************************************************************/ +static guint8 * +_copy_base64 (guint8 *dst, gsize dst_len, const char *base64_src) +{ + g_assert (dst); + g_assert (dst_len > 0); + + if (!base64_src) + memset (dst, 0, dst_len); + else { + gs_free guint8 *b = NULL; + gsize l; + + b = g_base64_decode (base64_src, &l); + g_assert (b); + g_assert (l == dst_len); + + memcpy (dst, b, dst_len); + } + return dst; +} + +typedef struct { + const char *pri; + const char *pub; + const char *pre; +} KeyPair; + +static void +_test_wireguard_change (NMPlatform *platform, + int ifindex, + int test_mode) +{ + const KeyPair self_key = + { "yOWEsaXFxX9/DOkQPzqB9RufZOpfSP4LZZCErP0N0Xo=", "s6pVT2xPwktor9O5bVOSzcPqBu9uzQOUzPQHXLU2jmk=" }; + const KeyPair keys[100] = { + { "+BDHMh11bkheGfvlQpqt8P/H7N1sPXtVi05XraZS0E8=", "QItu7PJadBVXFXGv55CMtVnbRHdrI6E2CGlu2N5oGx4=", "2IvZnKTzbF1UlChznWSsEYtGbPjhYSTT41GXO6zLxvk=" }, + { "qGZyV2BO1nyY/FGYd6elBPirwJC9QyZwqbm2OJAgLkY=", "v8L1FEitO0xo+wW/CVVUnALlw0zGveApSFdlITi/5lI=", "/R2c0JmBNGJzT594NQ0mBJ2XJjxt2QUSo+ZiqeY0EQA=" }, + { "YDgsIb0oe+9NcxIx2r0HEEPQpRMxmRN0ALoLm9Sh40Q=", "nFPs1HaU7uFBvE9xZCMF8oOAjzLpZ49AzDHOluY1O2E=", "zsYED2Ef7zIHJoRBPcen+w4ktrRsLPEfYwZhWIuXfds=" }, + { "kHkosM503LWu43tdYXbNwVOpRrtPgd9XFqcN7k4t6G4=", "b8e092WT+eNmnxCr5WE2QC/MXAjDagfG1g03cs2mBC0=", "VXz0ShGWT7H0CBCg2awfatmOJF15ZtaSMPhMsp+hc3A=" }, + { "4C2w5CEnxH59Y2aa6CJXgLdDtWoNMS2UJounRCM1Jkk=", "gC/R9umlnEQL+Qsz/Y64AlsdMge4ECe5/u/JHZCMWSs=", "2bmL5ISr+V5a7l7xFJ695BLIJyBgx8xnxzybkxiRHOg=" }, + { "KHJSzFGkXcZf/wbH2rr99SYtGKWbL680wA2AcDE94lo=", "BsN23h4aOi458Q3EgMQsodsWQxd9h/RxqskAUpsXfgg=", "nK4Zv34YKEhjuexhq1SgK4oTd4MZJT5gcpYvEuPjc7Q=" }, + { "QGMulXJ9e3AVxtpi8+UUVqPOr/YBWvCNFVsWS9IUnUA=", "kjVclP5Ifi6og2BBCEHKS/aa/WktArB4+ig06lYaVlg=", "0+mmceDPcSRK3vFnYqHd9iAfY+Nyjzf/1KgDeYGlRkQ=" }, + { "AOJiDD4y6GA7P7gugjjQG9Cctvc5Y27fajHz6aU3gU4=", "gEnHn6euHtcMEwZBlX6HANPeN9Of+voBDtltS38xDUw=", "wIH1OxgX6GLxx/bnR+t3fbmjGZDTU3WMxp7t1XGezqM=" }, + { "COsls2BlCltaIrrq1+FU51cWddlmoPPppSeIDunOxGA=", "+n6WuV8Tb1/iZArTrHsyNqkRHABbavBQt9Me72K2KEc=", "t4baiprSO9ZbKD2/RutOY9cr+yCajQWZGCTnQdrFQj0=" }, + { "uHawQq2BRyJlsTPoCa+MfBVnv4MwtRoS+S9FEpFOEVg=", "8lcmr27afeb6iI3BQCaDtvalF2Cl7gxRZgs+nyJ/fEg=", "Eh9o/6W60iujBLIHuRfNrPhOWAn7PambT2JRln9iwA0=" }, + { "yL7hmoE/JfRGAotRzx9xOpjfrDA3BFlPEemFiQw40Wk=", "BHK0PHi5kp7rOfQ46oc9xlVpB+pZeZYXTtH7EXr5TwU=", "BS2h2ZZyW0BlYMmLR29xyHEcZ4jtO7rgj1jkz/EEaxU=" }, + { "ON8YrTHQgoC5e0mAR9FakZ8hZ/9I7ysuE21sG546J1Y=", "Bm3l5I6iH1tDrv6EgdZU9PzHqp0H26Z6ggmmIypwxy8=", "qKVfbCnK1EI3eC28SsL+jyBnEnIF/nRJQJzDevBFdNQ=" }, + { "KGLO0RI0VlQFCG+ETUqk25HdaKUnUPXKOKkTh/w8BXU=", "sBDBwQFC7k8UMdMzuavKBkBjYYKigzvYmGF5rm6muQc=", "BNfZF9d7540pUGGOQCh3iDxAZcPBqX4qqM00GSLPBek=" }, + { "KGdWyEodIn7KKI2e4EFK9tT6Bt3bNMFBRFVTKjjJm2E=", "lrbjU/Xn9wDCZQiU8E5fY6igTSozghvo47QeVIIWaUk=", "LczqW48MW8qDQIZYRELsz/VCzVDc5niROvk7OqrTxR0=" }, + { "wO3xinUGABgEmX+RJZbBbOtAmBuPPFG6oVdimvoo90w=", "dCIvTzR6EerOOsRKnWly1a9WGzbJ4qc+6t3SSzLgWFk=", "wFj0zpr5PadBoBy0couLuZ1qudZbXLbV/j3UT+AyKeo=" }, + { "+JNOBlO4tp9vvQk6UO4r3sILyEgjl+BBoWketZufyn0=", "Q6LSv9y7YQkJEzQ/1mpjJEgOrO8GYPUTgcizjh7Cm34=", "kg7AG9MuN04xPJ5Z0IcNZ4a8d1b/n4GsGIeyA4FaaSE=" }, + { "+EJcThLRwjZ+h1CNNFu15HWzznf4u/lPVw8hifTm2Ec=", "Kkn2jFqwBzyIFQfD0OpmePFSmBYmhKagv4pGgkqsWgE=", "jYpxojj8WKYe/XIXMP+uv1Fv0+TKrs83tqfzP0AGdcI=" }, + { "+DKFqSMNFmxriEFj3qatuzYeTJ9+xWYspZ4ydL3eC0Q=", "3o37bsg6HhRg/M9+fTlLcFYc2w/Bz9rLQySvvYCKbRE=", "Jb9qoDIBat1EexlgfpRbXa7OflptME8/zt93bldkiVE=" }, + { "EH3MjFOMqRoDFQz+hSlJpntWBeH3lTk6WPjTIQjr42o=", "PbPewED/nxSBLdM7AXMj7uS3bCgAAg8M6F4iPLd0b1U=", "pj4+UgOGkpJwlRvX5BRXZzmzAUnDtUtJsS7LzbcJWzw=" }, + { "kL6M2KvO+vPBLEc/a0DpEHTibQ1bwaMRT9b9SkzeP0Y=", "pS3G2bHHlkOE6UHP0qVitDuxXgjEaZTviTjNc55RbVs=", "ZVZpWOtYqhX3CpF1kATg/38J6pvUJo8AS1sVYjs3rUE=" }, + { "sFlcRLDn36fnew2Ld92IHJwnKifdS3aF1MWRPs6K3Wg=", "OpjUOTiWExaDYULTINB4yQFqc3mnU3RjQzGRV+KtdFY=", "of0V/uoFRNljv/XTt/tXgoquLRH93Ty0KNiaPUpEi2w=" }, + { "UJ8hjDsg3jfsnnfPH8Gw9FnCb6taTuviurAfZu+kEFg=", "3byjHksUOv8CNjGGKvHvvrJDURBhCIL5UtfZbgyVWCE=", "9f7dbWif51gGrE7R9LeuewQSrvGFGTOB3ceJC67jSkI=" }, + { "iFFPKGIfqeUKY/w72KAZSjd/PGTqCakHYBV10xMDfnI=", "ehneHATNSXtsJTOiPjVSc0QARkihgcfcvoXKFWKfQnI=", "yKdqDBcRwA7RCg4GiY/b5IsWcExPleOBde/hjxc36a4=" }, + { "GDGocdPJTPUAllxQo7SpXZKqMPn7lpxQELQUX9ETHmE=", "n0ScNEou4ekfrXRRXvcADLu2Afj8g5D3TuDP/I4KrnY=", "8QhswqAhi/ehhcmCwQF5aSh80TvIGC/gRL5jBn5wOH8=" }, + { "UCcrlN8fX2ZdNdhaEBNwktwL+H0ZO7fhaj7rgdUutmw=", "LF8J728ilXs4TphnrgR6r0p7W3912DYnsXkGMEPQnRE=", "cYfMjxl2REYir7frGeB0u+NHAaFYF02ysgpBhOL5ygc=" }, + { "SH0657XuIiHidVmViXZF30RUWtkuWXcWWHmKZHTiOG8=", "7k6j49W1u5qgLE5MQUc1osPVW1oPPhzjrGvJ7o9YamY=", "dV2+7rNk/3LR2IcwYg/c+Wvzep1yjY7/u1I+nnlTQ00=" }, + { "qFQWs6jzrscV42pGISQyA7JDvFFAvEQCWJi584VHD2g=", "AT63nHKLC17yUvkR4lOVPxCr4DD3QhXmcmecOTn+Amc=", "2Qe2fwJbFcu1CmKpktElOFkSQMqlyvlV3ZUIAd/Dcts=" }, + { "6J+yLxgPwWtUbk9I3zbeD8RuK6XkQjJ0wTJ1zSVhflQ=", "NJzMBYyPZjk3eLmgdKaOeWyNER5YZF1mR8Umeiu9f28=", "pp+4+XHw5ZmHGJ7WbZ1xLYRsnTI17QIbb0bzHzYZrBs=" }, + { "KJGoWYNVDUWcEMg+E4tljv1LiWAbdRw2QVapYqdFa1Y=", "M2SGk9WVnzYNGnT777G/JE8uUsY2f7mszTwlue73UDE=", "Jg8N7GbhbYB400foFP+OH0v+hCaL1jW61bajSA6EZqc=" }, + { "uAgrgppPyIvk8S1CHUmaCaORsgFFfBreB8pxXmbSdXw=", "dJ22bER4fD3qF2/yIGWQ7SgmZ990sy/SbANjmUMkzws=", "mKkd0OoAR9sClmD+k3fL9weBsoCy5GQGz9BP0kAQIjc=" }, + { "EI3Q8gePNPrtFyoMcv7hOihQgroF/dfjzPn8yvpGPWQ=", "Y9QhJFeiyuuIZNPU56B6U//ZK+XTTe4EP7h/p3Q+dE8=", "qBVUYw9rTWaxn55nUd55NpCWtxOUSWLt4WJGusiDS8Q=" }, + { "oEQvdRm/yHkx+JvlhHGT5RUFrFEUleKb9DCT55EqZ30=", "8hsh/UHwWADTHntOJq4dy0o7ahcNAAlo2rDpjzzrVXk=", "/GF2inW2mPtA26IgFdgOEBbBEerT740wWuP/8NyANdc=" }, + { "6OWrGKZKNsgfRezSUw29EnHymcgKyEKvX5/pZQlLmWs=", "oaJeO6YSS2dodNEf97DvgWrYnFFelG5daEdN84jVVGw=", "T5wvTdyVxK0LY96kouLjs06oUfhGChfty8OUL1Mddro=" }, + { "mPmzQbh2R+r1DC5hSquzKM1SDrxUdfnBPRPJrpqrgkE=", "BtUHAnYWjDjBI42qBf9dJqezTUikYsF96o6PKEWPrVo=", "MxU8EMmq+vVHpuK/AkFZrZDF2b+VbSqukZLPbNsCcgo=" }, + { "8GVxuoo1Veyr+nqxr5Q4vmsMf5qfiXwSlQ4q3+BU60g=", "uSOLe/E9/OjIgUOk0NMBHB45E0+q4Rd+IUO2UOxmKlM=", "+30F+56OE0Sr3wY2clKw4kgE+2XiucMg7xjK6EemXuk=" }, + { "qORPKb+qFuU/9TpFbRUupHsqm9iyk9pa6cpik+EVDkg=", "bMZxxd+Z9I0XA1h9U8JEY+/mRxWGnvbXDZ5Dxz7YzS0=", "SmkkqOz4OhHuSL6cxuRm9+Mlt50Sfd0sMDFTC78gqOE=" }, + { "2Ko3IYhXKcdOMIJGNpASk9saNSZsI64lyJPOoxpQ2kI=", "xVOc9PxY1VFaZfemmKi+Ei2liHhmeTu+JMa+rS00gnA=", "+398DlW8kWeI2aRaC4QfcrEjwqPKCohyDeWdaI1wvv0=" }, + { "CPioGCVpxnym62nH/QoCt1RiiaaxUcuFjvh5kRhjqHA=", "W0XxlBLrZgFKhggMvvv6oFf/RJbfs92qv8JK9e+5i2o=", "bsK2U6CRAUv1uVgYQ7NpqjWWswFIDiDPDEtU1XQygSA=" }, + { "AF17siKaeiO85hikYN0IWCMGWqPm1UOoCkXMltJGUVk=", "B+PFos9aN2S5bLxzGZHljRZj41j3rIx8RWu0vDUzq1w=", "Qb8d6iDYv3m1h7PE8j0Exl2cSwpHkim/fJ1S4P7MYvY=" }, + { "wMFDBTJzx5tDCBhMkrptYJ8w9EeURjc4xeDQpevxAWo=", "4ec439EXE5WQzvtV9reSX+aMmdq5k7o9Ayt8oQp+RhQ=", "jwQlvdNH5WtSSU10H+fh/JisOlaBaohDPEp/BYnTt1Q=" }, + { "4GaJpIFigNDwd31O84pLIMM/o2qhp0ydlI/ydD/2a2Q=", "r/LdkCoK5/BPGdq2+XJO8sCRhI+8ULFmg0887V43PAI=", "Da+3ZZvEJdx4TYFMIDUlbkmytILnSTNxTKX+sQdjMd4=" }, + { "gMnojGqCLmMfGp2m31xlKZ/rIV2b8ockw9DPahRyu0c=", "H6tKCTosnM4BXKqflXrkTdJyNlCIZhQ3ZRxfrvSdrDk=", "4Z6K3LKIMV89plcjMb9CzSzJl03SWRe/++geBMZcOtY=" }, + { "wKCg22aNNoHnDJ0oAKE46FcSSsREW4AaGn5WxCSeXUs=", "9NDTFC0iPt4HbbWepLHhN5poNTN2fdxJKNadsNT7qzY=", "GSVTOCnfLpJ1VCOLHaKSjCMv7/OlcnQiP5+5woqkud8=" }, + { "oCoykq7pcJcg2X2V5TBRzGwn8hzzHC05WUreuotdznY=", "DxfwnbMqr5Wn5SAyFolfEmNQT6l84Oq69ngpg6H6Iio=", "p1RHBuqhuDa1MAQ2lbqmUQFu0CTwYlf73fWZSj9tQhA=" }, + { "UO7YVyRUVkcKr7c63VWWV2zj36XD3HyDfLZCqvrZmFc=", "360lzYtIyHq5lv/QXSCe4bL4G2J1jBXFJ8yS+Ycr7Bc=", "RRPQ1XWF1HN8Us4dtfn2eemdjgtWm7U8r7mM3y00NOk=" }, + { "eCGFYV/NuGP4H552E8Of1xU+3IvZxGyX+p5UFGW8iHI=", "LqhZ4AS9dQ/MhsQnE5Oy7Q8INXY+P+mrfGY5dtg9SlE=", "SICfqs8T5wP6IzATDCT4ovamBKPdkZ7JP4Cfsb3izec=" }, + { "oI3HBZknoIMMZw1BuYMkTBylt25reX7AbCqtWQv8cno=", "B7dUgLgvQhi0RGmvaMrmf26WdjEVrhaiBclVkCKd4AA=", "5O9K9pLXwxFAt5lfMWh4qGbwX1BM7sz0QGYxAnR77dk=" }, + { "sBfYn14EFVIS2M/E1aahP7mOmRNbNtyDChDMS1s6aGs=", "FLYv0ZvzxMkc9A7OzhC4P1ZRu1aKIQd7u6gqfdekC3c=", "kaYLcNCXnCLgiB8fleMQuboUJsj5u3YAmXL9x3ywV0M=" }, + { "qFwZESU/XYZXUtxwrGsFU9qPAFTzjm7EhTS1Q6ajGlM=", "hraQQaqJCkS2yQXv+ccMOVh9V9a/qgSZJgdMAhrt8ms=", "72oDfnWOn39gdk/ncw8Lv267I0I+m73SwxrpYojpWYk=" }, + { "YHhp6Zf/miuc2QXeI2lTezy0lL0pTv2b+nWNkmjYQWI=", "UhNO5arLzF0WlZSgNOx7+IjWN+GSxDdQxZRp8uIwsyI=", "1Q39Nzv2NGI9zWKWMpYLURAMZUg+FP+OboHHzFU8Anc=" }, + { "mLBBXKaCJ+7qeBZpS3wxGi/SQ4kLzun+K+QwwdwfJVQ=", "gIq/nh7NwCJ36MvRnyrWHaRWu8lTmwfN2NvsjVl6SXQ=", "AahcNR91GDyJBIP+vC8ZuIV8ukqjSGtd8s+cmjVC2Ao=" }, + { "OJG4LZlNNngFtAEQdbVVWVm6QAjOOauGcMZGbQrb40M=", "pFHAC6HaWAOtvTRRVfSHvzG05mp4SJZXKsN/tkSF0kM=", "IoXT3wIqWNxQhYuHWl12ODq/P7RM9LwaqglhmjKg+0g=" }, + { "OOwBFOQNhepiqDf04DehQLh1gpBNOluDF1ia752Yfng=", "u715uJ/XhdjXjThCTJ9w6zzXnIhp3VCxhtso1wk+oBQ=", "5x1Ip3Ym0KzDjGhiYjmpeWWr+dgrZlYwfr02GngPOTM=" }, + { "eNPFnwkQy1qw+IjFAlrDA6+sIsxbWDlzbNSsBW8R1UA=", "OaOXaAfPb1MRpWadawFje8YZ0oxJgdCIDIP8c5X+r1U=", "NtfaRRD0GqujnaQQoNoBbtovgO4dfVwEmEQx/YgnDpw=" }, + { "OLdaZItbtxH3mGqItkibIJp7KV27FrQavjhd5zq6s3Q=", "SLSmAYxkMCGj0DO35cMLkC3NVAqK2VmVFndbOZEdA24=", "SnBO68XQTDjxYbmYaAeEHgLwD2u4D+BPT86raRuUQZM=" }, + { "UBQOEz08izwr4eEK/SnQUpkt+TxCjo6Sya/XOGMLOE0=", "wQwrwezI9LzKevGsJJCBHDG8noR0yIEtOK5Rig97SSo=", "DpyS+0d7lrlFWkztsniG2v/j44vcuvWz3sPeghRyb5A=" }, + { "mN98iuqUKh67ggUdq9ZIQNZCZM90fgycTVqYKEo+DkY=", "GYdXVW1jpS0dN1q9zMehubP7LfYqs34kszN0bXQqxxA=", "AJPIHffB4uvvJJki4xCG0VORVBbF6bc2mZQqUx+idPc=" }, + { "OEd/1it8C3o+NOWxDI3DfLMXVBHJQg15N3E8F8d99l8=", "QL1NcuUkoXxDy7M9VjGslCejcUlnUDHRghFVnr+8fmA=", "nven9Dicl8U6QXuDO8rRNtjd4NYaa90SU+Gmv435XKY=" }, + { "AFMCGDu2oAP68miucsi4fmYX2KeRZnsEGv8tQm8JEng=", "1sNxvk8uZhFsBUgxOXmuCMjDAgBbjVeWe9oaFk5Osy0=", "t5iI5XXd56S5q0Y9HC91gzgF9uGjL9FIy6NUaKqkydo=" }, + { "CIAwfJghQHHr4YlztN9at6/iWkrEVCGFAxNVuQCuT3I=", "zpUOF1h17g7RpBzrVlN7oTRz6e+dxcDL8OsAtHwgLC8=", "kOSwC1p6Uoti9E9Eg7ViPZwCytuvp5Fr5Buw677aogU=" }, + { "sC8vrAVBU0zvhWDRfzfySjvopXm2/cTMkTLmioyO3Es=", "p6H7GWm8NfgyO5OCX/COjvVT4MAnTs9ZUj4uZMK8XHA=", "9Tzqo57V/h7+6nSNAHSBKdmU7ultlvZbAnNKSRlrLi4=" }, + { "eI63gjxCZGnzqZxPEi/ifYphXhxIRI2ZxK8jzqo3mmU=", "XyNzEuU9x37fxFCnrZH89Krs5/UqGVx5wNkGfQCAYz0=", "vZ2fTlRPnJQ+q33YdS5p1aweqPGj/kTMc4Uq80FtFjI=" }, + { "aNJlGtm79/RS4SQ/PC4YM6LFo9zAqDr2/RjLqk/z/1A=", "lgZ9akPrABmfHQMlfNFnnpAJzGtcsaU9mUjEYKfzZHc=", "d0Xt1Bcgphd1HMI0RneA4VdBbMZL1qNGJAvFhb080eA=" }, + { "oONSnHirNh3cuH93Ty0C9AXKebGY+cdF3R0DtPzIQlo=", "TuREKfA8EVQiYWsPx8veUzjN2cz/b72limSLWlrCWxw=", "vEqqKbpZf0EM6EApMUaUH65r3Zr81Y/DSODhE4H7U3Y=" }, + { "+D6RyLEaHJ9YF9WDyOlwh87KaNJcc6lqX8Arp6yqHF0=", "EpecjfIo1/EEbmsgUtzEDqLu2ut+SMmzqaBL9Z/MlCA=", "oYfO6/7XQgEYT9zmr4sqFrk0muK/fEv3FfD8MzZzjkE=" }, + { "OCmW5KQql2PRMJnsMYQjXlr6TSYUbxJBknqZtXJPSHM=", "ZZR2ghHlCwAJu/XlsEZuNS6XiGPwuXzyMPPywYFapVM=", "fqSCXq+pKJJ6yNvlOi+tyQ9E4Y6kc4kblGrVqN2WuXA=" }, + { "APWXDAe8d2ia1CUbf/IzSPXOUjR8TVuJgmISiWw0/EY=", "jrT5P5YCkG+U7cfNTvCKy0GSEgsjwmJtHg+8HBP6ZCA=", "t75aUjZXMPir8Ao0yhVClh9/BdWxSL+11CjK3iELNWk=" }, + { "8Nw4sRis7M/6Om+3w5YHXthyMzLGuP48teqdzbHNPlA=", "lj3q3ZYij3ZJ/QunK8n9I00cv/Z+O1TU1kFFl8x3DTE=", "adqB79P7gbXEYYnSd1/UPCwFffTAPXa9kHWynRBYcGo=" }, + { "yOupps5XbjV0fIZKnGhrpcxB7yDQzbBILC0UMJyVS1c=", "+MDV/t9UCIdgm3IkH3BZlxaRPJ3lejRmrm4UPApq4mk=", "AOJPmQxsU6hjOd+9mHnF0VL7Afih3P1Fr625xFT4FtY=" }, + { "cD2DEl4MBwONuTV0db5XreoVjQAUZFNXqIeFEU3KFkE=", "2CeHrjN7tBX48k4Sgv5fIHG06e57q/ucCL+8DIRmfXw=", "3EUo6MRzs6rSoY/7AFs8wiBiTXPcHzerLh6Xp3aMGKo=" }, + { "EA+S3a9ZeOLiRbhTxaT2wkpyDheAmai+UJa6SFGzSm8=", "GGByUKZx/FPa2OkJoqVTHXx+6jrIpIw5rf0rp43MHko=", "BXoDA3yn0JcMV7hHVzEqhlwAORvhToFO1qG00nas92A=" }, + { "eBeJi/imBiV52WEqrwAprUQggqdQmvTTmWtLq8pDDkM=", "zCX26ZTOZHLpq5x5aIUL1XhIVoXJLp/zcXwnmFA3jBo=", "Dm/DCxXWYXEsmQgxAD3KREK2PF0bUSnV5WRAaya8s1I=" }, + { "8C7p+EQO+CnWUSjHVu3PpeWpUIbLy48zpftZu021plM=", "DxpnF/IbKAh6kmWC5Jpj8iw387EDkrvjsjOb9fbTSng=", "bGrk0OshJB+0oQOK0QGKU8+lotnIDz3oeUnMZGienyM=" }, + { "COIez7YcBJiOJCLxxWV5UGLW5/o009YI0aszlD/PiUc=", "eD9USWV37LFIOxlDSHyOmfFqNJFpORRlzEI+HoF/czI=", "n+/ra86gUSF2pNZS51nt2JgrzXnQJl+dWswOq/Ahs94=" }, + { "iBBSTG9VLC+T9+ahNaQ4umZoig8o7w1DaeOw+cD2BGc=", "XnAxqDlvGnQ6aakv8ABGHVj07qVQfk4NChZbstTMBxg=", "7KKSwu/4yWr1UzFmNMGtiaSwdYMhP/HKbrQLlABL4UE=" }, + { "eNmwattflehr9+KsVqTuwt1YaAc5ONkaIaTQt9Gkhn8=", "1NNVvm++YTTGMKyAXfGOCZ4aDDdFFH5Um3vAg4XimDo=", "bXNrnDTP0pBay9ytZe7xpiKoSi12F7WUXqoIeI++Xvo=" }, + { "QJotpmZINx9eptKpkh9j3JlEDcHdWnjEbicdBS7gPXM=", "3gLYKeoruVZ/AYjym0gciDvRHj45UIWyHhNjWj2Wj28=", "NwuUkkE5yOWT7wed7bltgAk71miz3cSooiIDAdv6kKw=" }, + { "OAYGuxH70OPQvhVIX4BhSCWUyzAI5H2IkYxKgC/AO0M=", "Rj9iNF/FagkXfdLPqc9LHfaoGR8GlvY3gun2FilE508=", "hkNXLVMlBRsMEaQKkSzevcEK2sMu0AShGKQJMNqdzWM=" }, + { "uBrgZ7wLHrOV/0dNiEqo7FjY9VnJqL5eUDHJWAc9QXg=", "3Hnln8ZHfSaK4OzESJe5U6NcLaW56wzfZICzvzefnSo=", "DhXsehe5FAmbUidXT5ZpZIAuu1eF9rkU6cF3FBoKwOE=" }, + { "sJknn3CHvx/812EWU3ddLdLLZFBKsc+wx35GXyiRsHY=", "qqa2dNSt0jWozGyqpokP392H5/DOAUUZmUpyZDaUEEE=", "1Dyz8CvmF17oKT/wG2fu3vRzPzgQv8/OY9GJYew4FG8=" }, + { "yOumS9HN68ZwIm+5hZol3jFQ0DB4SKuW/ld3y8wioGk=", "6PowsbKj/fKSzXZMAfaSkP3fE+4AThL9xm6ysQzMDxg=", "vF8cKu9X9FxgCjyVZ5RG7nuue8RelF5Qsb8Efme4M4A=" }, + { "mISm5vQfPdK72SsaHh6O1/ARvaWCtm+KZNcpTsyt500=", "YCORDQDpb1U8vADdstBgXkg0N7QaAc5VoXJ4QFuA/UY=", "JlrBmfaCgbfEVD9YQq3c03WwwsHWc1nBwp1JkFORC3A=" }, + { "cPyu3Qry6qbsiOJKFGRziZ9LJWJ47k3ZSXiGkQXuQm0=", "/cmBZTbqEp8sababPAxGb3OvDAEE7MlwPOwEFHE+7yM=", "lp0Hhc/rVtpT5FtLLccChqDl3El48XtP6Wm6JwjI7jo=" }, + { "YKsMYU0SINbPwWw4RDCJV6GnzDlSp1ZNwUw2euGWi0A=", "/KmBReATQbFnLg8YKV0jwhqKeishRoWvlVtMX3550Vk=", "7fXpPSMo1Fw2sOXtjTtvFU+DbZvS/FWB9wAsywWx6R4=" }, + { "WD0YI3y71eIp/GXw9i+7scEiQKSBkGihZWE+s6fGmWo=", "RdthAL/qPnAZFb3xBgRMiAtGHNjgokzoKX9iO2K5qhc=", "4dk4HGkT9dBmomGNorDE/hLr/HEFhljtl4zz3M4sG58=" }, + { "oD8KWhJYZVhutJRb0kZlZnB22QUXzi2FfPRD0ll65UM=", "MYTBGHh4Ukj97pKj6qcfWmxGNQzmU3/aBOX2f1tfhG0=", "VT1gC+a9nRJzYMi/TPvRVnn3IQlaop/jKmmxZePEME0=" }, + { "0Ns/1SOiqR2CpHRG03QNzJJd5gxTm1XJmSkFlugjQ3k=", "KvQAI+ekNOa2xfEvfyc9JGcS+CTUrnnhsKrlyJGJixg=", "J8LmSX6zElX3S9q4PNvh2NKUtAiQ3oHiYjSJ7yErPlY=" }, + { "mD6TeF4ezSPXN/csN1OhoAREFSXllI+zl4DUOInVq2Q=", "WmLJ9ep2EqFcSftnYFJsmWyUxqL0zzuSzVEv94PcISM=", "U2+ILy2NDmmfgSW78C8dl8GyHESUc1lXPHPpg5F+gr8=" }, + { "SMJoXOYgHz8HSzY+ByeWLcSP5qFwv7YjRe0bcKesRnM=", "DEsNSOY3TEs9J2YgqroQ4xKq8T8xNJQjvvE4UrTItQw=", "Bws1Hk2+lO+JQ7ME16EbwAdsBkWsGvti0Gb6LY2Lrms=" }, + { "iGhXF0Hg0tqZmpwAMiolxvbvTPClQ7LlBAspSSyFEHE=", "7Xxzpwl7yRWehHNWTYVtFkdChJdXhtY4Mtw1fA9QcCg=", "Swjfv0PjuaE8Oq3a17BVno5I+q49dZlPwKK1bPUoKNI=" }, + { "MIazjx3qTi6Qz3WzhtCPw3i4Q2uZBHcuMoh++ZGFYUk=", "oni8pbFqk9Ya+Fx+911Nl1SN0FD/hR1jwb2RH3t/pRk=", "ZYAFcj67LkbNURYbSnCCWGxAG8QLDGWwbl968mA6ZA4=" }, + { "MAadYdiFM2cPuJF19q20Yoo5KJabuR9TUQ9jG5nvA1w=", "5OcE8XV+UPoBVbgqBQdVF62GZCW9DOQEdxrQsktPPBA=", "FCZsEFXouy+xtxv8X7VroXtvPG1Z1HFHL724tz1jcUI=" }, + { "+GwxMmD2dee5+QmvXNI0NdP+rNWoSXTN42otbp0aZ24=", "Y0N44baz9ihclCUnv6rRbDqCYu4BxQlBfNnTz3NNe2A=", "/LqSgkVQNkQ/oBiZSgpM9Rw7BJv0RvRpEQpvlizvHy0=" }, + { "8FREpCtncOcT7+W2nW4aYSjmSbADtVSH9rIliQZZUH4=", "fTNSd0JeREhXmPfjrmrAu6Lu/yHkB9GyxR3SyO4kZ28=", "262KN/iG/iJEaZeerFm1yVtvhFVGgQFwSvtxTcjZzeg=" }, + { "EDhaRQGtscjoSE9wJOnSXoQVtVruIqyzknty+x/vDWo=", "eMmMgws6ZxDIxZ6QSwGjZO1Mx/r5T+fJjSTKGMBk/BU=", "0CyaJV6AG9bZ0C4yeZ/RDsOs9BdNqZpUxAsD30WmJO4=" }, + { "CJV0UB2YdvVDG1cs4oiJgHAS+f1FocGr/vGCfiovsWQ=", "9/O9GWZEOXVm7On8lftL27PffRORju8OKl6gZd/74CA=", "A+kXRVNOwIrA5DUa+3v7dpRC+Gbxm23LTiYmOUAXUyY=" }, + { "gCjDsJUwZGA7BjYVoCQsvdIgN9Q4lBHlSyKwUrl751A=", "HRwS8T9y2qPYk7JVU/8Y+6cS+Bk8XCLCXxwN/ttbQiI=", "iFotjA6rhUfkDv4S/wspJgEWunEmrlGSGsXcJ0+8laQ=" }, + { "6N5pL4gsuK+shHpDxirTnAGdyKXIlYHyfIhtB0njJGA=", "CVZvW7NaN2XMEEKHodghBA9hLCwee/jrmttiWh/CmEg=", "OpPEd3Sp8r6KdjNDTN4bVHETlGJ92BCK74FCdEaDe9g=" }, + { "UIPPTUdvhlg8qEDv6JRxM4/8F5ORjJz4ud82QZrgeEY=", "7Nd13z5EpB3ChytvQC1CxvDY7n0H8r2Y7lzLEY8hdEk=", "b22PvgU0M2QfNC7ZGN+RXNe5fjOzMsY32IcHTwLNIqw=" }, + { "oBn53Q5fmxKX02PgI6F47Rb+XoLeFQO07ok2tYhk0lE=", "e0gtPDKXCZSoNW1uHqBPQXLfiYgyeqPMU2zZJgPXACI=", "wmjW2wDT2EzFkyaGui7YWNLTRu8Q4eD/GVKM2utZkEs=" }, + }; + gs_unref_ptrarray GPtrArray *allowed_ips_keep_alive = NULL; + gs_unref_array GArray *peers = NULL; + NMPlatformLnkWireGuard lnk_wireguard; + int r; + guint i; + + allowed_ips_keep_alive = g_ptr_array_new_with_free_func (g_free); + + peers = g_array_new (FALSE, TRUE, sizeof (NMPWireGuardPeer)); + + lnk_wireguard = (NMPlatformLnkWireGuard) { + .listen_port = 50754, + .fwmark = 0x1102, + }; + _copy_base64 (lnk_wireguard.private_key, sizeof (lnk_wireguard.private_key), self_key.pri); + _copy_base64 (lnk_wireguard.public_key, sizeof (lnk_wireguard.public_key), self_key.pub); + + if (test_mode == 0) { + /* no peers. */ + } else if (NM_IN_SET (test_mode, 1, 2)) { + guint num_peers = (test_mode == 1) ? 1 : G_N_ELEMENTS (keys); + + for (i = 0; i < num_peers; i++) { + NMPWireGuardPeer peer; + char s_addr[NM_UTILS_INET_ADDRSTRLEN]; + NMSockAddrUnion endpoint; + guint i_allowed_ips, n_allowed_ips; + NMPWireGuardAllowedIP *allowed_ips; + + if ((i % 2) == 1) { + endpoint = (NMSockAddrUnion) { + .in = { + .sin_family = AF_INET, + .sin_addr = nmtst_inet4_from_string (nm_sprintf_buf (s_addr, "192.168.7.%d", i)), + .sin_port = htons (14000 + i), + }, + }; + } else { + endpoint = (NMSockAddrUnion) { + .in6 = { + .sin6_family = AF_INET6, + .sin6_addr = *nmtst_inet6_from_string (nm_sprintf_buf (s_addr, "a:b:c:e::1:%d", i)), + .sin6_port = htons (16000 + i), + }, + }; + } + + if (test_mode == 1) + n_allowed_ips = 1; + else + n_allowed_ips = i % 10; + allowed_ips = g_new0 (NMPWireGuardAllowedIP, n_allowed_ips); + g_ptr_array_add (allowed_ips_keep_alive, allowed_ips); + for (i_allowed_ips = 0; i_allowed_ips < n_allowed_ips; i_allowed_ips++) { + NMPWireGuardAllowedIP *aip = &allowed_ips[i_allowed_ips]; + + aip->family = (i_allowed_ips % 2) ? AF_INET : AF_INET6; + if (aip->family == AF_INET) { + aip->addr.addr4 = nmtst_inet4_from_string (nm_sprintf_buf (s_addr, "10.%u.%u.0", i, i_allowed_ips)); + aip->mask = 32 - (i_allowed_ips % 8); + } else { + aip->addr.addr6 = *nmtst_inet6_from_string (nm_sprintf_buf (s_addr, "a:d:f:%02x:%02x::", i, i_allowed_ips)); + aip->mask = 128 - (i_allowed_ips % 10); + } + } + + peer = (NMPWireGuardPeer) { + .persistent_keepalive_interval = 60+i, + .endpoint = endpoint, + .allowed_ips = n_allowed_ips > 0 ? allowed_ips : NULL, + .allowed_ips_len = n_allowed_ips, + }; + _copy_base64 (peer.public_key, sizeof (peer.public_key), keys[i].pub); + _copy_base64 (peer.preshared_key, sizeof (peer.preshared_key), (i % 3) ? NULL : keys[i].pre); + + g_array_append_val (peers, peer); + } + } else + g_assert_not_reached (); + + r = nm_platform_link_wireguard_change (platform, + ifindex, + &lnk_wireguard, + (const NMPWireGuardPeer *) peers->data, + peers->len); + g_assert (NMTST_NM_ERR_SUCCESS (r)); +} + +/*****************************************************************************/ + typedef struct { NMLinkType link_type; int test_mode; @@ -697,6 +931,7 @@ test_software_detect (gconstpointer user_data) int ifindex, ifindex_parent; const NMPlatformLink *plink; const NMPObject *lnk; + int r; guint i_step; const gboolean ext = test_data->external_command; NMPlatformLnkTun lnk_tun; @@ -1005,6 +1240,19 @@ test_software_detect (gconstpointer user_data) : NULL)); break; } + case NM_LINK_TYPE_WIREGUARD: { + const NMPlatformLink *link; + + r = nm_platform_link_wireguard_add (NM_PLATFORM_GET, DEVICE_NAME, &link); + if (r == -EOPNOTSUPP) { + g_test_skip ("wireguard not supported (modprobe wireguard?)"); + goto out_delete_parent; + } + + g_assert (NMTST_NM_ERR_SUCCESS (r)); + g_assert (NMP_OBJECT_GET_TYPE (NMP_OBJECT_UP_CAST (link)) == NMP_OBJECT_TYPE_LINK); + break; + } default: g_assert_not_reached (); } @@ -1238,6 +1486,18 @@ test_software_detect (gconstpointer user_data) } break; } + case NM_LINK_TYPE_WIREGUARD: { + const NMPlatformLnkWireGuard *plnk = &lnk->lnk_wireguard; + + g_assert (plnk == nm_platform_link_get_lnk_wireguard (NM_PLATFORM_GET, ifindex, NULL)); + + if (plink->n_ifi_flags & IFF_UP) { + _test_wireguard_change (NM_PLATFORM_GET, plink->ifindex, test_data->test_mode); + if (_LOGD_ENABLED ()) + _system ("WG_HIDE_KEYS=never wg show all"); + } + break; + } default: g_assert_not_reached (); } @@ -2800,12 +3060,8 @@ test_ethtool_features_get (void) ethtool_features_dump (features); - if (_LOGT_ENABLED ()) { - int ignore; - - ignore = system ("ethtool -k lo"); - (void) ignore; - } + if (_LOGT_ENABLED ()) + _system ("ethtool -k lo"); if (!do_set) { requested = gfree_keeper->pdata[i_run * 2 - 2]; @@ -2862,6 +3118,9 @@ _nmtstp_setup_tests (void) test_software_detect_add ("/link/software/detect/vlan", NM_LINK_TYPE_VLAN, 0); test_software_detect_add ("/link/software/detect/vxlan/0", NM_LINK_TYPE_VXLAN, 0); test_software_detect_add ("/link/software/detect/vxlan/1", NM_LINK_TYPE_VXLAN, 1); + test_software_detect_add ("/link/software/detect/wireguard/0", NM_LINK_TYPE_WIREGUARD, 0); + test_software_detect_add ("/link/software/detect/wireguard/1", NM_LINK_TYPE_WIREGUARD, 1); + test_software_detect_add ("/link/software/detect/wireguard/2", NM_LINK_TYPE_WIREGUARD, 2); g_test_add_func ("/link/software/vlan/set-xgress", test_vlan_set_xgress); |