net: refactor netlink message parsing, add extack support

Change-Id: Iaa132d8cb1aece6cfc608cc1c3b4478982ac706e
diff --git a/src/net/detail/netlink-util.hpp b/src/net/detail/netlink-util.hpp
new file mode 100644
index 0000000..15f54f3
--- /dev/null
+++ b/src/net/detail/netlink-util.hpp
@@ -0,0 +1,339 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library 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 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Davide Pesavento <davide.pesavento@lip6.fr>
+ */
+
+#ifndef NDN_NET_NETLINK_UTIL_HPP
+#define NDN_NET_NETLINK_UTIL_HPP
+
+#include "../../common.hpp"
+#include "../ethernet.hpp"
+
+#ifndef NDN_CXX_HAVE_RTNETLINK
+#error "This file should not be included ..."
+#endif
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <string.h>
+
+#include <cstring>
+#include <map>
+
+#include <boost/asio/ip/address.hpp>
+
+namespace ndn {
+namespace net {
+
+template<typename T>
+constexpr size_t
+getAttributeLength(const T* attr);
+
+template<>
+constexpr size_t
+getAttributeLength(const nlattr* attr)
+{
+  return attr->nla_len;
+}
+
+template<>
+constexpr size_t
+getAttributeLength(const rtattr* attr)
+{
+  return attr->rta_len;
+}
+
+template<typename T>
+constexpr size_t
+getAttributeLengthAligned(const T* attr);
+
+template<>
+constexpr size_t
+getAttributeLengthAligned(const nlattr* attr)
+{
+  return NLA_ALIGN(attr->nla_len);
+}
+
+template<>
+constexpr size_t
+getAttributeLengthAligned(const rtattr* attr)
+{
+  return RTA_ALIGN(attr->rta_len);
+}
+
+template<typename T>
+constexpr uint16_t
+getAttributeType(const T* attr);
+
+template<>
+constexpr uint16_t
+getAttributeType(const nlattr* attr)
+{
+  return attr->nla_type & NLA_TYPE_MASK;
+}
+
+template<>
+constexpr uint16_t
+getAttributeType(const rtattr* attr)
+{
+  return attr->rta_type;
+}
+
+template<typename T>
+const uint8_t*
+getAttributeValue(const T* attr);
+
+template<>
+inline const uint8_t*
+getAttributeValue(const nlattr* attr)
+{
+  return reinterpret_cast<const uint8_t*>(attr) + NLA_HDRLEN;
+}
+
+template<>
+inline const uint8_t*
+getAttributeValue(const rtattr* attr)
+{
+  return reinterpret_cast<const uint8_t*>(RTA_DATA(const_cast<rtattr*>(attr)));
+}
+
+template<typename T>
+constexpr size_t
+getAttributeValueLength(const T* attr);
+
+template<>
+constexpr size_t
+getAttributeValueLength(const nlattr* attr)
+{
+  return attr->nla_len - NLA_HDRLEN;
+}
+
+template<>
+constexpr size_t
+getAttributeValueLength(const rtattr* attr)
+{
+  return RTA_PAYLOAD(attr);
+}
+
+template<typename T>
+class NetlinkMessageAttributes;
+
+class NetlinkMessage
+{
+public:
+  explicit
+  NetlinkMessage(const uint8_t* buf, size_t buflen) noexcept
+    : m_msg(reinterpret_cast<const nlmsghdr*>(buf))
+    , m_length(buflen)
+  {
+    BOOST_ASSERT(buf != nullptr);
+  }
+
+  const nlmsghdr&
+  operator*() const noexcept
+  {
+    return *m_msg;
+  }
+
+  const nlmsghdr*
+  operator->() const noexcept
+  {
+    return m_msg;
+  }
+
+  bool
+  isValid() const noexcept
+  {
+    return NLMSG_OK(m_msg, m_length);
+  }
+
+  NetlinkMessage
+  getNext() const noexcept
+  {
+    BOOST_ASSERT(isValid());
+
+    // mimic NLMSG_NEXT
+    auto thisLen = NLMSG_ALIGN(m_msg->nlmsg_len);
+    return NetlinkMessage{reinterpret_cast<const uint8_t*>(m_msg) + thisLen, m_length - thisLen};
+  }
+
+  template<typename T>
+  const T*
+  getPayload() const noexcept
+  {
+    BOOST_ASSERT(isValid());
+
+    if (m_msg->nlmsg_len < NLMSG_LENGTH(sizeof(T)))
+      return nullptr;
+
+    return reinterpret_cast<const T*>(NLMSG_DATA(const_cast<nlmsghdr*>(m_msg)));
+  }
+
+  template<typename AttributeT, typename PayloadT>
+  NetlinkMessageAttributes<AttributeT>
+  getAttributes(const PayloadT* p) const noexcept
+  {
+    BOOST_ASSERT(isValid());
+
+    auto begin = reinterpret_cast<const uint8_t*>(p) + NLMSG_ALIGN(sizeof(PayloadT));
+    auto length = NLMSG_PAYLOAD(m_msg, sizeof(PayloadT));
+    return NetlinkMessageAttributes<AttributeT>{reinterpret_cast<const AttributeT*>(begin), length};
+  }
+
+private:
+  const nlmsghdr* m_msg;
+  size_t m_length;
+};
+
+template<typename T>
+class NetlinkMessageAttributes
+{
+  // empty type used to implement tag dispatching in getAttributeByType()
+  template<typename U>
+  struct AttrValueTypeTag {};
+
+public:
+  explicit
+  NetlinkMessageAttributes(const T* begin, size_t length) noexcept
+  {
+    for (; isAttrValid(begin, length); begin = getNextAttr(begin, length)) {
+      m_attrs[getAttributeType(begin)] = begin;
+    }
+  }
+
+  size_t
+  size() const noexcept
+  {
+    return m_attrs.size();
+  }
+
+  template<typename U>
+  optional<U>
+  getAttributeByType(uint16_t attrType) const
+  {
+    auto it = m_attrs.find(attrType);
+    if (it == m_attrs.end())
+      return nullopt;
+
+    return convertAttrValue(getAttributeValue(it->second),
+                            getAttributeValueLength(it->second),
+                            AttrValueTypeTag<U>{});
+  }
+
+private:
+  static bool
+  isAttrValid(const T* attr, size_t nBytesRemaining) noexcept
+  {
+    return attr != nullptr &&
+           nBytesRemaining >= sizeof(T) &&
+           getAttributeLength(attr) >= sizeof(T) &&
+           getAttributeLength(attr) <= nBytesRemaining;
+  }
+
+  static const T*
+  getNextAttr(const T* attr, size_t& nBytesRemaining) noexcept
+  {
+    auto len = getAttributeLengthAligned(attr);
+    if (len > nBytesRemaining) // prevent integer underflow
+      return nullptr;
+
+    nBytesRemaining -= len;
+    return reinterpret_cast<const T*>(reinterpret_cast<const uint8_t*>(attr) + len);
+  }
+
+  template<typename Integral>
+  static std::enable_if_t<std::is_integral<Integral>::value, optional<Integral>>
+  convertAttrValue(const uint8_t* val, size_t len, AttrValueTypeTag<Integral>)
+  {
+    if (len < sizeof(Integral))
+      return nullopt;
+
+    Integral i;
+    std::memcpy(&i, val, sizeof(Integral));
+    return i;
+  }
+
+  static optional<std::string>
+  convertAttrValue(const uint8_t* val, size_t len, AttrValueTypeTag<std::string>)
+  {
+    auto str = reinterpret_cast<const char*>(val);
+    if (::strnlen(str, len) < len)
+      return std::string(str);
+    else
+      return nullopt;
+  }
+
+  static optional<ethernet::Address>
+  convertAttrValue(const uint8_t* val, size_t len, AttrValueTypeTag<ethernet::Address>)
+  {
+    if (len < ethernet::ADDR_LEN)
+      return nullopt;
+
+    return ethernet::Address(val);
+  }
+
+  template<typename IpAddress>
+  static std::enable_if_t<std::is_same<IpAddress, boost::asio::ip::address_v4>::value ||
+                          std::is_same<IpAddress, boost::asio::ip::address_v6>::value, optional<IpAddress>>
+  convertAttrValue(const uint8_t* val, size_t len, AttrValueTypeTag<IpAddress>)
+  {
+    typename IpAddress::bytes_type bytes;
+    if (len < bytes.size())
+      return nullopt;
+
+    std::copy_n(val, bytes.size(), bytes.begin());
+    return IpAddress(bytes);
+  }
+
+private:
+  std::map<uint16_t, const T*> m_attrs;
+};
+
+inline const char*
+nlmsgTypeToString(uint16_t type) noexcept
+{
+#define NLMSG_STRINGIFY(x) case NLMSG_##x: return "<" #x ">"
+#define RTM_STRINGIFY(x) case RTM_##x: return "<" #x ">"
+  switch (type) {
+    NLMSG_STRINGIFY(NOOP);
+    NLMSG_STRINGIFY(ERROR);
+    NLMSG_STRINGIFY(DONE);
+    NLMSG_STRINGIFY(OVERRUN);
+    RTM_STRINGIFY(NEWLINK);
+    RTM_STRINGIFY(DELLINK);
+    RTM_STRINGIFY(GETLINK);
+    RTM_STRINGIFY(NEWADDR);
+    RTM_STRINGIFY(DELADDR);
+    RTM_STRINGIFY(GETADDR);
+    RTM_STRINGIFY(NEWROUTE);
+    RTM_STRINGIFY(DELROUTE);
+    RTM_STRINGIFY(GETROUTE);
+    default:
+      return "";
+  }
+#undef NLMSG_STRINGIFY
+#undef RTM_STRINGIFY
+}
+
+} // namespace net
+} // namespace ndn
+
+#endif // NDN_NET_NETLINK_UTIL_HPP