system: Get current links from Rtnetlink

This will be used for statistics retrieval (see next commits).

We wrap libnl's rtnl_link objects into unique_ptr with custom deleter
so the lifetime is auto-managed.

Change-Id: Ic2d14d19f3992ad68ae2df70f01c60f49bfea577
diff --git a/src/system/Rtnetlink.cpp b/src/system/Rtnetlink.cpp
index f99c62f..476a216 100644
--- a/src/system/Rtnetlink.cpp
+++ b/src/system/Rtnetlink.cpp
@@ -42,6 +42,19 @@
     }
 }
 
+/** @brief Wraps rtnl object with unique_ptr and adds an appropriate deleter. */
+template <class T>
+std::unique_ptr<T, std::function<void(T*)>> nlObjectWrap(T* obj)
+{
+    return std::unique_ptr<T, std::function<void(T*)>>(obj, [] (T* obj) { nl_object_put(OBJ_CAST(obj)); });
+}
+
+template <class T>
+T* nlObjectClone(T* obj)
+{
+    return reinterpret_cast<T*>(nl_object_clone(OBJ_CAST(obj)));
+}
+
 }
 
 namespace velia::system {
@@ -96,9 +109,18 @@
 
 Rtnetlink::Rtnetlink(LinkCB cbLink, AddrCB cbAddr)
     : m_log(spdlog::get("system"))
+    , m_nlSocket(nl_socket_alloc(), nl_socket_free)
     , m_cbLink(std::move(cbLink))
     , m_cbAddr(std::move(cbAddr))
 {
+    if (!m_nlSocket) {
+        throw RtnetlinkException("nl_socket_alloc failed");
+    }
+
+    if (auto err = nl_connect(m_nlSocket.get(), NETLINK_ROUTE); err < 0) {
+        throw RtnetlinkException("nl_connect", err);
+    }
+
     {
         nl_cache_mngr* tmpManager;
         if (auto err = nl_cache_mngr_alloc(nullptr /* alloc and manage new netlink socket */, NETLINK_ROUTE, NL_AUTO_PROVIDE, &tmpManager); err < 0) {
@@ -130,8 +152,33 @@
     nlCacheForeachWrapper<rtnl_addr>(cacheRouteAddr, [this](rtnl_addr* addr) {
         m_cbAddr(addr, NL_ACT_NEW);
     });
+
+    {
+        nl_cache* tmpCache;
+        if (auto err = rtnl_link_alloc_cache(m_nlSocket.get(), AF_UNSPEC, &tmpCache); err < 0) {
+            throw RtnetlinkException("rtnl_link_alloc_cache", err);
+        }
+        m_nlCacheLink = nlCache(tmpCache, nl_cache_free);
+    }
 }
 
 Rtnetlink::~Rtnetlink() = default;
 
+std::vector<Rtnetlink::nlLink> Rtnetlink::getLinks()
+{
+    resyncCache(m_nlCacheLink);
+
+    std::vector<Rtnetlink::nlLink> res;
+    nlCacheForeachWrapper<rtnl_link>(m_nlCacheLink.get(), [&res](rtnl_link* link) {
+        res.emplace_back(nlObjectWrap(nlObjectClone(link)));
+    });
+
+    return res;
+}
+
+void Rtnetlink::resyncCache(const nlCache& cache)
+{
+    nl_cache_resync(m_nlSocket.get(), cache.get(), [](nl_cache*, nl_object*, int, void*){}, nullptr);
+}
+
 }
diff --git a/src/system/Rtnetlink.h b/src/system/Rtnetlink.h
index 2cf007c..4ee1659 100644
--- a/src/system/Rtnetlink.h
+++ b/src/system/Rtnetlink.h
@@ -12,6 +12,7 @@
 #include <netlink/netlink.h>
 #include <netlink/route/addr.h>
 #include <netlink/route/link.h>
+#include <netlink/route/neighbour.h>
 #include <stdexcept>
 #include <thread>
 #include "utils/log-fwd.h"
@@ -26,15 +27,23 @@
 class Rtnetlink {
 public:
     using nlCacheManager = std::shared_ptr<nl_cache_mngr>;
+    using nlCache = std::unique_ptr<nl_cache, std::function<void(nl_cache*)>>;
+    using nlLink = std::unique_ptr<rtnl_link, std::function<void(rtnl_link*)>>;
+
     using LinkCB = std::function<void(rtnl_link* link, int cacheAction)>; /// cacheAction: NL_ACT_*
     using AddrCB = std::function<void(rtnl_addr* addr, int cacheAction)>; /// cacheAction: NL_ACT_*
 
     Rtnetlink(LinkCB cbLink, AddrCB cbAddr);
     ~Rtnetlink();
+    std::vector<nlLink> getLinks();
 
 private:
+    void resyncCache(const nlCache& cache);
+
     velia::Log m_log;
-    nlCacheManager m_nlCacheManager;
+    std::unique_ptr<nl_sock, std::function<void(nl_sock*)>> m_nlSocket;
+    nlCacheManager m_nlCacheManager; // for updates
+    nlCache m_nlCacheLink; // for getLinks
     LinkCB m_cbLink;
     AddrCB m_cbAddr;
     std::unique_ptr<impl::nlCacheMngrWatcher> m_nlCacheMngrWatcher; // first to destroy, because the thread uses m_nlCacheManager and m_log