Hi Thomas,
On Tue, Apr 03, 2018 at 09:17:05PM +0200, Thomas Haller wrote:
> Hi,
>
> it looks quite good!! :)
> thanks
Much appreciated! Your review comments have been very helpful.
> > diff --git a/libnm-core/nm-core-types-internal.h b/libnm-core/nm-
> > core-types-internal.h
> > index 4d43aaf45..1c4d74ca9 100644
> > --- a/libnm-core/nm-core-types-internal.h
> > +++ b/libnm-core/nm-core-types-internal.h
>
> > @@ -26,11 +26,42 @@
> > #error Cannot use this header.
> > #endif
> >
> > +#include "nm-utils/c-list.h"
> > +
> > typedef struct {
> > guint32 from;
> > guint32 to;
> > } NMVlanQosMapping;
> >
> > +typedef struct {
> > + guint16 family;
>
> Family always either 2 (AF_INET) or 10 (AF_INET6).
> Can you make it a gint8, and move it together with "cidr" field?
Sure.
> > + union {
> > + struct in_addr ip4;
> > + struct in6_addr ip6;
> > + } ip;
>
> NMIPAddr?
D'oh - of course this would already be available somewhere in NM.
Thank you!
> > +typedef struct {
> > + guint8 public_key[NM_WG_PUBLIC_KEY_LEN];
> > + guint8 preshared_key[NM_WG_SYMMETRIC_KEY_LEN];
> > + union {
> > + struct sockaddr addr;
> > + struct sockaddr_in addr4;
> > + struct sockaddr_in6 addr6;
> > + } endpoint;
> > + struct timespec last_handshake_time;
> > + guint64 rx_bytes, tx_bytes;
> > + guint16 persistent_keepalive_interval;
> > +
> > + CList allowedips_lst_head;
>
> CList is a great data structure (+1 for simplicity). But in this case,
> can you not just allocate an array?
>
> guint allowed_ips_len;
> const NMWireguardAllowedIP *allowed_ips;
>
> I would see a point in making NMWireguardAllowedIP individually
> allocated structures, if they'd be reference-counted. But embedding a
> CList in the structure, kinda prevents meaningfully sharing it, because
> the entire list would be shared too (at which point, there is no need
> to make individual elements shared/ref-counted/malloc-ed).
The reason why I had a CList was just so we could quickly perform
allowedip coalescing during genl parsing. (Sometimes all allowedips for
a peer won't fit in one genl message, so they will be sent in batches).
But in any case yeah, peers/allowedip are always received/sent all in
one go. There's no reason why this can't be a plain array-of-structs
after the parsing is done, and I understand how that is preferable.
I'll make that change.
> > --- a/src/platform/nm-linux-platform.c
> > +++ b/src/platform/nm-linux-platform.c
> > @@ -173,6 +173,40 @@ G_STATIC_ASSERT (RTA_MAX == (__RTA_MAX - 1));
> >
> > /*******************************************************************
> > **********/
> >
> > +#define WG_CMD_GET_DEVICE 0
> > +#define WG_CMD_SET_DEVICE 1
> > +
> > +#define IFLA_WG_DEVICE_UNSPEC 0
> > +#define IFLA_WG_DEVICE_IFINDEX 1
> > +#define IFLA_WG_DEVICE_IFNAME 2
> > +#define IFLA_WG_DEVICE_PRIVATE_KEY 3
> > +#define IFLA_WG_DEVICE_PUBLIC_KEY 4
> > +#define IFLA_WG_DEVICE_FLAGS 5
> > +#define IFLA_WG_DEVICE_LISTEN_PORT 6
> > +#define IFLA_WG_DEVICE_FWMARK 7
> > +#define IFLA_WG_DEVICE_PEERS 8
> > +#define __IFLA_WG_DEVICE_MAX 9
> > +
> > +#define IFLA_WG_PEER_UNSPEC 0
> > +#define IFLA_WG_PEER_PUBLIC_KEY 1
> > +#define IFLA_WG_PEER_PRESHARED_KEY 2
> > +#define IFLA_WG_PEER_FLAGS 3
> > +#define IFLA_WG_PEER_ENDPOINT 4
> > +#define IFLA_WG_PEER_PERSISTENT_KEEPALIVE_INTERVAL 5
> > +#define IFLA_WG_PEER_LAST_HANDSHAKE_TIME 6
> > +#define IFLA_WG_PEER_RX_BYTES 7
> > +#define IFLA_WG_PEER_TX_BYTES 8
> > +#define IFLA_WG_PEER_ALLOWEDIPS 9
>
> WireGuard's uapi headers seem to call these fields differently.
> E.g. WGPEER_A_LAST_HANDSHAKE_TIME instead of
> IFLA_WG_PEER_LAST_HANDSHAKE_TIME.
>
> These compat defines are here, so we can compile against older kernel
> headers. The name should be identical to the kernel API, except,
> maybe a NM_ prefix, to distinuish them from the real name. The NM-
> prefix is used if the define is put to a header, otherwise, it is not
> used.
Oh I see now - yeah I've made a mess of these then.
I thought IFLA_* was some naming convention within NM, didn't realize
it's actually kernel uapi.
I'll sort it out.
> > +static void
> > +_wireguard_peer_free (gpointer data)
> > +{
> > + NMWireguardPeer *peer = data;
> > + NMWireguardAllowedIP *allowedip;
> > +
> > + c_list_for_each_entry (allowedip, &peer-
> > >allowedips_lst_head, allowedips_lst)
> > + g_free (allowedip);
>
> this would be wrong, because you'd need c_list_for_each_entry_safe().
> Anyway, let's not make this a C-List.
True.
> > +static NMPObject *
> > +_parse_lnk_wireguard (const char *kind)
> > +{
> > + nm_auto_nmpobj NMPObject *obj = NULL;
> > + NMPObject *obj_result;
> > + NMPObjectLnkWireguard *wgobj;
> > +
> > + if (!nm_streq0 (kind, "wireguard"))
> > + return NULL;
> > +
> > + obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_WIREGUARD, NULL);
> > + wgobj = &obj->_lnk_wireguard;
> > +
> > + wgobj->peers = g_ptr_array_sized_new (1);
> > + g_ptr_array_set_free_func (wgobj->peers,
> > _wireguard_peer_free);
>
> this doesn't seem useful. _parse_lnk_wireguard() doesn't really parse
> anything. Just drop.
Yeah it's a leftover from refactoring.
Gone.
> > @@ -1875,6 +1944,9 @@ _new_from_nl_link (NMPlatform *platform, const
> > NMPCache *cache, struct nlmsghdr
> > case NM_LINK_TYPE_VXLAN:
> > lnk_data = _parse_lnk_vxlan (nl_info_kind,
> > nl_info_data);
> > break;
> > + case NM_LINK_TYPE_WIREGUARD:
> > + lnk_data = _parse_lnk_wireguard (nl_info_kind);
>
> here, no need to create an empty lnk_data instance.
As above.
> > @@ -1914,6 +1986,15 @@ _new_from_nl_link (NMPlatform *platform, const
> > NMPCache *cache, struct nlmsghdr
> > obj->link.tx_packets = link_cached-
> > >link.tx_packets;
> > obj->link.tx_bytes = link_cached-
> > >link.tx_bytes;
> > }
> > + } else {
>
> I think, it's not really if-else.
>
> By default, you'd assume that the cache already contains a suitable
> NMPObjectLnkWireguard instance. Just look into the cache (the lines
> with the "if", and if it's there that's it.
>
> Only if no NMPObjectLnkWireguard can be found in the cache, fetch it
> the first time. As said, this leaves out some notification mechanism to
> refetch the data as necessary. For now, just first do the if-part
> (always), and then always refetch via genl. Then, compare the two, and
> if they differ, keep the newly received one.
I have to admit I got confused about the cache code on this function.
But yeah on second inspection this code is not laid out correctly. For
example, if cache == NULL then we'd never query genl.
> > + /* TODO: we have no mechanism to detect
> > changes to cached WireGuard
> > + * interfaces yet, so any changes will not
> > be picked up. */
> > + switch (obj->link.type) {
> > + case NM_LINK_TYPE_WIREGUARD:
> > + nm_platform_wireguard_get_link_prope
> > rties (platform, &obj->link, lnk_data);
>
> this can be just a static function inside nm-linux-platform.c. Do you
> expect this to be useful from somewhere else?
Not really. I'll just make it static.
> > + if (tbp[IFLA_WG_PEER_PUBLIC_KEY])
> > + memcpy (&peer->public_key, nla_data
> > (tbp[IFLA_WG_PEER_PUBLIC_KEY]), sizeof (peer->public_key));
> > + if (tbp[IFLA_WG_PEER_PRESHARED_KEY])
> > + memcpy (&peer->preshared_key, nla_data
> > (tbp[IFLA_WG_PEER_PRESHARED_KEY]), sizeof (peer->preshared_key));
> > + if (tbp[IFLA_WG_PEER_ENDPOINT])
> > + memcpy (&peer->endpoint, nla_data
> > (tbp[IFLA_WG_PEER_ENDPOINT]), sizeof (peer->endpoint));
>
> this seems wrong. The size of this attribute depends on addr_family.
Good catch. Will fix.
> > +static gboolean
> > +wireguard_get_link_properties (NMPlatform *platform, const
> > NMPlatformLink *link, NMPObject *obj)
> > +{
> > + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE
> > (platform);
> > + nm_auto_nlmsg struct nl_msg *msg = NULL;
> > + struct nl_cb cb = {
> > + .valid_cb = _wireguard_read_device_cb,
> > + .valid_arg = obj,
> > + };
> > +
> > + g_return_val_if_fail (priv->wireguard_family_id, FALSE);
>
> g_return_*() is an assertion. I guess there can be run-time situations,
> where this doesn't hold.
I'll address this (together with next comment).
>
> > @@ -7066,6 +7356,16 @@ constructed (GObject *_object)
> > (EVENT_CONDITIONS |
> > ERROR_CONDITIONS | DISCONNECT_CONDITIONS),
> > event_handler, platform);
> >
> > + priv->genlh = nl_socket_alloc ();
> > + g_assert (priv->genlh);
> > +
> > + nle = nl_connect (priv->genlh, NETLINK_GENERIC);
> > + g_assert (!nle);
> > + _LOGD ("Generic netlink socket established: port=%u, fd=%d",
> > nl_socket_get_local_port (priv->genlh), nl_socket_get_fd (priv-
> > >genlh));
> > +
> > + priv->wireguard_family_id = genl_ctrl_resolve (priv->genlh,
> > "wireguard");
> > + _LOG2D ("kernel-support: wireguard: %s", priv-
> > >wireguard_family_id ? "detected" : "not detected");
>
> I wonder, what happens if the wireguard module is not loaded initially.
> I guess, caching the family-id is right, but you need to retry if it's
> not avilable. For example, in wireguard_get_link_properties()
Makes sense.
Should we attempt a refetch once per RTM_*LINK message then? Or is there
some reliable way to get notified when a new genl family becomes
available?
> > +static void
> > +_vt_cmd_obj_hash_update_lnk_wireguard (const NMPObject *obj,
> > NMHashState *h)
> > +{
> > + nm_assert (NMP_OBJECT_GET_TYPE (obj) ==
> > NMP_OBJECT_TYPE_LNK_WIREGUARD);
> > +
> > + nm_platform_lnk_wireguard_hash_update (&obj->lnk_wireguard,
> > h);
> > + nm_hash_update_val (h, obj->_lnk_wireguard.peers);
>
> You must hash the entire content, not just the peers-pointer.
This will become irrelevant when we have a simple array-of-structs, but:
if copy_lnk_wireguard was implemented by simply grabbing a ref to the
same GPtrArray, would it still be wrong to take a shortcut here by
hashing/testing just the pointer?
> > +static int
> > +_vt_cmd_obj_cmp_lnk_wireguard (const NMPObject *obj1, const
> > NMPObject *obj2)
> > +{
> > + int c;
> > +
> > + c = nm_platform_lnk_wireguard_cmp (&obj1->lnk_wireguard,
> > &obj2->lnk_wireguard);
> > + if (c)
> > + return c;
> > +
> > + return obj1->_lnk_wireguard.peers < obj2-
> > >_lnk_wireguard.peers;
>
> As with hash-update, you must compare the content of peers.
>
> Sidenote: even if you really wanted to compare the pointers, then "<"
> would only return 0 and 1. But cmp must return -1, 0, and 1.
...and that's simply a bug.
> > diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
> > index 0dd2687a3..e64ac6614 100644
> > --- a/src/platform/nmp-object.h
> > +++ b/src/platform/nmp-object.h
> > @@ -213,6 +213,12 @@ typedef struct {
> > NMPlatformLnkVxlan _public;
> > } NMPObjectLnkVxlan;
> >
> > +typedef struct {
> > + NMPlatformLnkWireguard _public;
> > +
> > + GPtrArray *peers;
>
> It makes some sense that @peers is GPtrArray, because that type is ref-
> counted, and possibly multiple NMPlatformLnkWireguard instances could
> share it.
>
> However, see that NMPObjectLnkVlan doesn't try to share the
> ingress_qos_map. It doesn't because, what is instead shared is the
> lnk_vlan instance itself.
>
> See how
> - _parse_lnk_vlan() always creates a new NMPObjectLnkVlan instance.
> Since it parses the netlink attribute at once, it just allocates
> a suitable obj->_lnk_vlan.ingress_qos_map.
> - then, it will reach line
>
> https://cgit.freedesktop.org/NetworkManager/NetworkManager/tree/src/platform/nm-linux-platform.c?id=eb8257dea5802a004af9cccacb30af98440e2172#n1894
> if the newly created object is identical to the already cached one,
> the newly created object is thrown away and the cached one is kept.
> What is shared and ref-countable is the NMPObjectLnkVlan instance itself. Not
> the data pointers to ingress_qos_map.
>
> the same is for NMPObjectLnkWireguard.peers. Just make it a plain array,
> possibly
> also add a "peers_len" field.
Ah - it's starting to click now.
I'll simplify this before the next iteration.
Thanks for your time and patience! :)
_______________________________________________
networkmanager-list mailing list
[email protected]
https://mail.gnome.org/mailman/listinfo/networkmanager-list