Implement Netlink message printer

Bug: 158756457
Test: manual
Change-Id: I93e40bbf1eff1c4c0e502ca38e07865909ba04f3
diff --git a/automotive/can/1.0/default/libnetdevice/Android.bp b/automotive/can/1.0/default/libnetdevice/Android.bp
index 6e2c782..c5fd311 100644
--- a/automotive/can/1.0/default/libnetdevice/Android.bp
+++ b/automotive/can/1.0/default/libnetdevice/Android.bp
@@ -20,11 +20,23 @@
     vendor_available: true,
     relative_install_path: "hw",
     srcs: [
+        "protocols/common/Empty.cpp",
+        "protocols/common/Error.cpp",
+        "protocols/generic/Ctrl.cpp",
+        "protocols/generic/Generic.cpp",
+        "protocols/generic/GenericMessageBase.cpp",
+        "protocols/generic/Unknown.cpp",
+        "protocols/route/Link.cpp",
+        "protocols/route/Route.cpp",
+        "protocols/MessageDefinition.cpp",
+        "protocols/NetlinkProtocol.cpp",
+        "protocols/all.cpp",
         "NetlinkRequest.cpp",
         "NetlinkSocket.cpp",
         "can.cpp",
         "common.cpp",
         "libnetdevice.cpp",
+        "printer.cpp",
         "vlan.cpp",
     ],
     export_include_dirs: ["include"],
diff --git a/automotive/can/1.0/default/libnetdevice/NetlinkRequest.h b/automotive/can/1.0/default/libnetdevice/NetlinkRequest.h
index 3e28d78..5bea333 100644
--- a/automotive/can/1.0/default/libnetdevice/NetlinkRequest.h
+++ b/automotive/can/1.0/default/libnetdevice/NetlinkRequest.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include "types.h"
+
 #include <android-base/macros.h>
 #include <linux/rtnetlink.h>
 
@@ -23,12 +25,10 @@
 
 namespace android::netdevice {
 
-typedef unsigned short rtattrtype_t;  // as in rtnetlink.h
-typedef __u16 nlmsgtype_t;            // as in netlink.h
-
 /** Implementation details, do not use outside NetlinkRequest template. */
 namespace impl {
 
+// TODO(twasilczyk): use nlattr instead of rtattr
 struct rtattr* addattr_l(struct nlmsghdr* n, size_t maxLen, rtattrtype_t type, const void* data,
                          size_t dataLen);
 struct rtattr* addattr_nest(struct nlmsghdr* n, size_t maxLen, rtattrtype_t type);
@@ -36,6 +36,7 @@
 
 }  // namespace impl
 
+// TODO(twasilczyk): rename to NetlinkMessage
 /**
  * Wrapper around NETLINK_ROUTE messages, to build them in C++ style.
  *
@@ -44,6 +45,14 @@
  */
 template <class T, unsigned int BUFSIZE = 128>
 struct NetlinkRequest {
+    struct RequestData {
+        struct nlmsghdr nlmsg;
+        T data;
+        char buf[BUFSIZE];
+    };
+
+    static constexpr size_t totalLength = sizeof(RequestData);
+
     /**
      * Create empty message.
      *
@@ -131,12 +140,7 @@
 
   private:
     bool mIsGood = true;
-
-    struct {
-        struct nlmsghdr nlmsg;
-        T data;
-        char buf[BUFSIZE];
-    } mRequest = {};
+    RequestData mRequest = {};
 
     struct rtattr* nestStart(rtattrtype_t type) {
         if (!mIsGood) return nullptr;
diff --git a/automotive/can/1.0/default/libnetdevice/NetlinkSocket.cpp b/automotive/can/1.0/default/libnetdevice/NetlinkSocket.cpp
index 7817169..15c0f9b 100644
--- a/automotive/can/1.0/default/libnetdevice/NetlinkSocket.cpp
+++ b/automotive/can/1.0/default/libnetdevice/NetlinkSocket.cpp
@@ -16,11 +16,18 @@
 
 #include "NetlinkSocket.h"
 
+#include <libnetdevice/printer.h>
+
 #include <android-base/logging.h>
 
 namespace android::netdevice {
 
-NetlinkSocket::NetlinkSocket(int protocol) {
+/**
+ * Print all outbound/inbound Netlink messages.
+ */
+static constexpr bool kSuperVerbose = false;
+
+NetlinkSocket::NetlinkSocket(int protocol) : mProtocol(protocol) {
     mFd.reset(socket(AF_NETLINK, SOCK_RAW, protocol));
     if (!mFd.ok()) {
         PLOG(ERROR) << "Can't open Netlink socket";
@@ -38,7 +45,13 @@
     }
 }
 
-bool NetlinkSocket::send(struct nlmsghdr* nlmsg) {
+bool NetlinkSocket::send(struct nlmsghdr* nlmsg, size_t totalLen) {
+    if constexpr (kSuperVerbose) {
+        nlmsg->nlmsg_seq = mSeq;
+        LOG(VERBOSE) << (mFailed ? "(not)" : "")
+                     << "sending Netlink message: " << toString(nlmsg, totalLen, mProtocol);
+    }
+
     if (mFailed) return false;
 
     nlmsg->nlmsg_pid = 0;  // kernel
@@ -91,6 +104,11 @@
 
     for (auto nlmsg = reinterpret_cast<struct nlmsghdr*>(buf); NLMSG_OK(nlmsg, remainingLen);
          nlmsg = NLMSG_NEXT(nlmsg, remainingLen)) {
+        if constexpr (kSuperVerbose) {
+            LOG(VERBOSE) << "received Netlink response: "
+                         << toString(nlmsg, sizeof(buf), mProtocol);
+        }
+
         // We're looking for error/ack message only, ignoring others.
         if (nlmsg->nlmsg_type != NLMSG_ERROR) {
             LOG(WARNING) << "Received unexpected Netlink message (ignored): " << nlmsg->nlmsg_type;
diff --git a/automotive/can/1.0/default/libnetdevice/NetlinkSocket.h b/automotive/can/1.0/default/libnetdevice/NetlinkSocket.h
index 2b40ea2..595c31a 100644
--- a/automotive/can/1.0/default/libnetdevice/NetlinkSocket.h
+++ b/automotive/can/1.0/default/libnetdevice/NetlinkSocket.h
@@ -43,7 +43,7 @@
     template <class T, unsigned int BUFSIZE>
     bool send(NetlinkRequest<T, BUFSIZE>& req) {
         if (!req.isGood()) return false;
-        return send(req.header());
+        return send(req.header(), req.totalLength);
     }
 
     /**
@@ -54,11 +54,13 @@
     bool receiveAck();
 
   private:
+    const int mProtocol;
+
     uint32_t mSeq = 0;
     base::unique_fd mFd;
     bool mFailed = false;
 
-    bool send(struct nlmsghdr* msg);
+    bool send(struct nlmsghdr* msg, size_t totalLen);
 
     DISALLOW_COPY_AND_ASSIGN(NetlinkSocket);
 };
diff --git a/automotive/can/1.0/default/libnetdevice/common.cpp b/automotive/can/1.0/default/libnetdevice/common.cpp
index 5c62443..f64d7d3 100644
--- a/automotive/can/1.0/default/libnetdevice/common.cpp
+++ b/automotive/can/1.0/default/libnetdevice/common.cpp
@@ -33,4 +33,27 @@
     return 0;
 }
 
+std::string sanitize(std::string str) {
+    str.erase(std::find(str.begin(), str.end(), '\0'), str.end());
+
+    const auto isInvalid = [](char c) { return !isprint(c); };
+    std::replace_if(str.begin(), str.end(), isInvalid, '?');
+
+    return str;
+}
+
+uint16_t crc16(const nlbuf<uint8_t> data, uint16_t crc) {
+    for (const auto byte : data.getRaw()) {
+        crc ^= byte;
+        for (unsigned i = 0; i < 8; i++) {
+            if (crc & 1) {
+                crc = (crc >> 1) ^ 0xA001;
+            } else {
+                crc >>= 1;
+            }
+        }
+    }
+    return crc;
+}
+
 }  // namespace android::netdevice
diff --git a/automotive/can/1.0/default/libnetdevice/common.h b/automotive/can/1.0/default/libnetdevice/common.h
index 8097f37..c521835 100644
--- a/automotive/can/1.0/default/libnetdevice/common.h
+++ b/automotive/can/1.0/default/libnetdevice/common.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include "nlbuf.h"
+
 #include <string>
 
 namespace android::netdevice {
@@ -31,4 +33,24 @@
  */
 unsigned int nametoindex(const std::string& ifname);
 
+/**
+ * Sanitize a string of unknown contents.
+ *
+ * Trims the string to the first '\0' character and replaces all non-printable characters with '?'.
+ */
+std::string sanitize(std::string str);
+
+/**
+ * Calculates a (optionally running) CRC16 checksum.
+ *
+ * CRC16 isn't a strong checksum, but is good for quick comparison purposes.
+ * One benefit (and also a drawback too) is that all-zero payloads with any length will
+ * always have a checksum of 0x0000.
+ *
+ * \param data Buffer to calculate checksum for
+ * \param crc Previous CRC16 value to continue calculating running checksum
+ * \return CRC16 checksum
+ */
+uint16_t crc16(const nlbuf<uint8_t> data, uint16_t crc = 0);
+
 }  // namespace android::netdevice
diff --git a/automotive/can/1.0/default/libnetdevice/include/libnetdevice/printer.h b/automotive/can/1.0/default/libnetdevice/include/libnetdevice/printer.h
new file mode 100644
index 0000000..efeb6b1
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/include/libnetdevice/printer.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <linux/netlink.h>
+
+#include <string>
+
+namespace android::netdevice {
+
+std::string toString(const nlmsghdr* hdr, size_t bufLen, int protocol);
+
+}  // namespace android::netdevice
diff --git a/automotive/can/1.0/default/libnetdevice/nlbuf.h b/automotive/can/1.0/default/libnetdevice/nlbuf.h
new file mode 100644
index 0000000..f7e53be
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/nlbuf.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/logging.h>
+
+#include <linux/netlink.h>
+
+#include <optional>
+
+namespace android::netdevice {
+
+/**
+ * Buffer containing netlink structure (e.g. struct nlmsghdr, struct nlattr).
+ *
+ * This is a C++-style, memory safe(r) and generic implementation of linux/netlink.h macros.
+ *
+ * While netlink structures contain information about their total length (with payload), they can
+ * not be trusted - the value may either be larger than the buffer message is allocated in or
+ * smaller than the header itself (so it couldn't even fit itself).
+ *
+ * As a solution, nlbuf<> keeps track of two lengths (both attribute for header with payload):
+ * - buffer length - how much memory was allocated to a given structure
+ * - declared length - what nlmsg_len or nla_len says how long the structure is
+ *
+ * In most cases buffer length would be larger than declared length (or equal - modulo alignment -
+ * for continuous data). If that's not the case, there is a potential of ouf-of-bounds read which
+ * this template attempts to protect against.
+ */
+template <typename T>
+class nlbuf {
+    // The following definitions are C++ equivalents of NLMSG_* macros from linux/netlink.h
+
+    static constexpr size_t alignto = NLMSG_ALIGNTO;
+    static_assert(NLMSG_ALIGNTO == NLA_ALIGNTO);
+
+    static constexpr size_t align(size_t ptr) { return (ptr + alignto - 1) & ~(alignto - 1); }
+
+    static constexpr size_t hdrlen = align(sizeof(T));
+
+  public:
+    nlbuf(const T* data, size_t bufferLen) : mData(data), mBufferEnd(pointerAdd(data, bufferLen)) {}
+
+    const T* operator->() const {
+        CHECK(firstOk()) << "buffer can't fit the first element's header";
+        return mData;
+    }
+
+    std::optional<std::reference_wrapper<const T>> getFirst() const {
+        if (!ok()) return std::nullopt;
+        return *mData;
+    }
+
+    /**
+     * Copy the first element of the buffer.
+     *
+     * This is a memory-safe cast operation, useful for reading e.g. uint32_t values
+     * from 1-byte buffer.
+     */
+    T copyFirst() const {
+        T val = {};
+        memcpy(&val, mData, std::min(sizeof(val), remainingLength()));
+        return val;
+    }
+
+    bool firstOk() const { return sizeof(T) <= remainingLength(); }
+
+    template <typename D>
+    const nlbuf<D> data(size_t offset = 0) const {
+        // Equivalent to NLMSG_DATA(hdr) + NLMSG_ALIGN(offset)
+        const D* dptr = reinterpret_cast<const D*>(uintptr_t(mData) + hdrlen + align(offset));
+        return {dptr, dataEnd()};
+    }
+
+    class iterator {
+      public:
+        iterator() : mCurrent(nullptr, size_t(0)) {
+            CHECK(!mCurrent.ok()) << "end() iterator should indicate it's beyond end";
+        }
+        iterator(const nlbuf<T>& buf) : mCurrent(buf) {}
+
+        iterator operator++() {
+            // mBufferEnd stays the same
+            mCurrent.mData = reinterpret_cast<const T*>(  //
+                    uintptr_t(mCurrent.mData) + align(mCurrent.declaredLength()));
+
+            return *this;
+        }
+
+        bool operator==(const iterator& other) const {
+            // all iterators beyond end are the same
+            if (!mCurrent.ok() && !other.mCurrent.ok()) return true;
+
+            return uintptr_t(other.mCurrent.mData) == uintptr_t(mCurrent.mData);
+        }
+
+        const nlbuf<T>& operator*() const { return mCurrent; }
+
+      protected:
+        nlbuf<T> mCurrent;
+    };
+    iterator begin() const { return {*this}; }
+    iterator end() const { return {}; }
+
+    class raw_iterator : public iterator {
+      public:
+        iterator operator++() {
+            this->mCurrent.mData++;  // ignore alignment
+            return *this;
+        }
+        const T& operator*() const { return *this->mCurrent.mData; }
+    };
+
+    class raw_view {
+      public:
+        raw_view(const nlbuf<T>& buffer) : mBuffer(buffer) {}
+        raw_iterator begin() const { return {mBuffer}; }
+        raw_iterator end() const { return {}; }
+
+        const T* ptr() const { return mBuffer.mData; }
+        size_t len() const { return mBuffer.remainingLength(); }
+
+      private:
+        const nlbuf<T>& mBuffer;
+    };
+
+    raw_view getRaw() const { return {*this}; }
+
+  private:
+    const T* mData;
+    const void* mBufferEnd;
+
+    nlbuf(const T* data, const void* bufferEnd) : mData(data), mBufferEnd(bufferEnd) {}
+
+    bool ok() const { return declaredLength() <= remainingLength(); }
+
+    // to be specialized individually for each T with payload after a header
+    inline size_t declaredLengthImpl() const { return sizeof(T); }
+
+    size_t declaredLength() const {
+        // We can't even fit a header, so let's return some absurd high value to trip off
+        // buffer overflow checks.
+        if (sizeof(T) > remainingLength()) return std::numeric_limits<size_t>::max() / 2;
+        return declaredLengthImpl();
+    }
+
+    size_t remainingLength() const {
+        auto len = intptr_t(mBufferEnd) - intptr_t(mData);
+        return (len >= 0) ? len : 0;
+    }
+
+    const void* dataEnd() const {
+        auto declaredEnd = pointerAdd(mData, declaredLength());
+        return std::min(declaredEnd, mBufferEnd);
+    }
+
+    static const void* pointerAdd(const void* ptr, size_t len) {
+        return reinterpret_cast<const void*>(uintptr_t(ptr) + len);
+    }
+
+    template <typename D>
+    friend class nlbuf;  // calling private constructor of data buffers
+};
+
+template <>
+inline size_t nlbuf<nlmsghdr>::declaredLengthImpl() const {
+    return mData->nlmsg_len;
+}
+
+template <>
+inline size_t nlbuf<nlattr>::declaredLengthImpl() const {
+    return mData->nla_len;
+}
+
+}  // namespace android::netdevice
diff --git a/automotive/can/1.0/default/libnetdevice/printer.cpp b/automotive/can/1.0/default/libnetdevice/printer.cpp
new file mode 100644
index 0000000..8e17e7f
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/printer.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <libnetdevice/printer.h>
+
+#include "common.h"
+#include "nlbuf.h"
+#include "protocols/all.h"
+
+#include <android-base/logging.h>
+
+#include <algorithm>
+#include <iomanip>
+#include <sstream>
+
+namespace android::netdevice {
+
+static void flagsToStream(std::stringstream& ss, __u16 nlmsg_flags) {
+    bool first = true;
+    auto printFlag = [&ss, &first, &nlmsg_flags](__u16 flag, const std::string& name) {
+        if (!(nlmsg_flags & flag)) return;
+        nlmsg_flags &= ~flag;
+
+        if (first) {
+            first = false;
+        } else {
+            ss << '|';
+        }
+
+        ss << name;
+    };
+    printFlag(NLM_F_REQUEST, "REQUEST");
+    printFlag(NLM_F_MULTI, "MULTI");
+    printFlag(NLM_F_ACK, "ACK");
+    printFlag(NLM_F_ECHO, "ECHO");
+    printFlag(NLM_F_DUMP_INTR, "DUMP_INTR");
+    printFlag(NLM_F_DUMP_FILTERED, "DUMP_FILTERED");
+
+    // TODO(twasilczyk): print flags depending on request type
+    printFlag(NLM_F_ROOT, "ROOT-REPLACE");
+    printFlag(NLM_F_MATCH, "MATCH-EXCL");
+    printFlag(NLM_F_ATOMIC, "ATOMIC-CREATE");
+    printFlag(NLM_F_APPEND, "APPEND");
+
+    if (nlmsg_flags != 0) {
+        if (!first) ss << '|';
+        ss << std::hex << nlmsg_flags << std::dec;
+    }
+}
+
+static void toStream(std::stringstream& ss, const nlbuf<uint8_t> data) {
+    ss << std::hex;
+    int i = 0;
+    for (const auto byte : data.getRaw()) {
+        if (i++ > 0) ss << ' ';
+        ss << std::setw(2) << unsigned(byte);
+    }
+    ss << std::dec;
+}
+
+static void toStream(std::stringstream& ss, const nlbuf<nlattr> attr,
+                     const protocols::AttributeMap& attrMap) {
+    using DataType = protocols::AttributeDefinition::DataType;
+    const auto attrtype = attrMap[attr->nla_type];
+
+    ss << attrtype.name << ": ";
+    switch (attrtype.dataType) {
+        case DataType::Raw: {
+            toStream(ss, attr.data<uint8_t>());
+            break;
+        }
+        case DataType::Nested: {
+            ss << '{';
+            bool first = true;
+            for (auto childattr : attr.data<nlattr>()) {
+                if (!first) ss << ", ";
+                first = false;
+                toStream(ss, childattr, attrtype.subTypes);
+            }
+            ss << '}';
+            break;
+        }
+        case DataType::String: {
+            const auto str = attr.data<char>().getRaw();
+            ss << '"' << sanitize({str.ptr(), str.len()}) << '"';
+            break;
+        }
+        case DataType::Uint: {
+            ss << attr.data<uint32_t>().copyFirst();
+            break;
+        }
+    }
+}
+
+static std::string toString(const nlbuf<nlmsghdr> hdr, int protocol) {
+    if (!hdr.firstOk()) return "nlmsg{buffer overflow}";
+
+    std::stringstream ss;
+    ss << std::setfill('0');
+
+    auto protocolMaybe = protocols::get(protocol);
+    if (!protocolMaybe.has_value()) {
+        ss << "nlmsg{protocol=" << protocol << "}";
+        return ss.str();
+    }
+    protocols::NetlinkProtocol& protocolDescr = *protocolMaybe;
+
+    auto msgDescMaybe = protocolDescr.getMessageDescriptor(hdr->nlmsg_type);
+    const auto msgTypeName = msgDescMaybe.has_value()
+                                     ? msgDescMaybe->get().getMessageName(hdr->nlmsg_type)
+                                     : std::to_string(hdr->nlmsg_type);
+
+    ss << "nlmsg{" << protocolDescr.getName() << " ";
+
+    ss << "hdr={";
+    ss << "type=" << msgTypeName;
+    if (hdr->nlmsg_flags != 0) {
+        ss << ", flags=";
+        flagsToStream(ss, hdr->nlmsg_flags);
+    }
+    if (hdr->nlmsg_seq != 0) ss << ", seq=" << hdr->nlmsg_seq;
+    if (hdr->nlmsg_pid != 0) ss << ", pid=" << hdr->nlmsg_pid;
+
+    ss << ", crc=" << std::hex << std::setw(4) << crc16(hdr.data<uint8_t>()) << std::dec;
+    ss << "} ";
+
+    if (!msgDescMaybe.has_value()) {
+        toStream(ss, hdr.data<uint8_t>());
+    } else {
+        const protocols::MessageDescriptor& msgDesc = *msgDescMaybe;
+        msgDesc.dataToStream(ss, hdr);
+
+        bool first = true;
+        for (auto attr : hdr.data<nlattr>(msgDesc.getContentsSize())) {
+            if (first) {
+                ss << " attributes: {";
+                first = false;
+            } else {
+                ss << ", ";
+            }
+            toStream(ss, attr, msgDesc.getAttributeMap());
+        }
+        if (!first) ss << '}';
+    }
+
+    ss << "}";
+
+    return ss.str();
+}
+
+std::string toString(const nlmsghdr* hdr, size_t bufLen, int protocol) {
+    return toString({hdr, bufLen}, protocol);
+}
+
+}  // namespace android::netdevice
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/MessageDefinition.cpp b/automotive/can/1.0/default/libnetdevice/protocols/MessageDefinition.cpp
new file mode 100644
index 0000000..cb42896
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/MessageDefinition.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MessageDefinition.h"
+
+namespace android::netdevice::protocols {
+
+AttributeMap::AttributeMap(const std::initializer_list<value_type> attrTypes)
+    : std::map<std::optional<nlattrtype_t>, AttributeDefinition>(attrTypes) {}
+
+const AttributeDefinition AttributeMap::operator[](nlattrtype_t nla_type) const {
+    if (count(nla_type) == 0) {
+        if (count(std::nullopt) == 0) return {std::to_string(nla_type)};
+
+        auto definition = find(std::nullopt)->second;
+        definition.name += std::to_string(nla_type);
+        return definition;
+    }
+    return find(nla_type)->second;
+}
+
+MessageDescriptor::MessageDescriptor(const std::string& name, const MessageTypeMap&& messageTypes,
+                                     const AttributeMap&& attrTypes, size_t contentsSize)
+    : mName(name),
+      mContentsSize(contentsSize),
+      mMessageTypes(messageTypes),
+      mAttributeMap(attrTypes) {}
+
+MessageDescriptor::~MessageDescriptor() {}
+
+size_t MessageDescriptor::getContentsSize() const {
+    return mContentsSize;
+}
+
+const MessageDescriptor::MessageTypeMap& MessageDescriptor::getMessageTypeMap() const {
+    return mMessageTypes;
+}
+
+const AttributeMap& MessageDescriptor::getAttributeMap() const {
+    return mAttributeMap;
+}
+
+const std::string MessageDescriptor::getMessageName(nlmsgtype_t msgtype) const {
+    const auto it = mMessageTypes.find(msgtype);
+    if (it == mMessageTypes.end()) return "?";
+    return it->second;
+}
+
+}  // namespace android::netdevice::protocols
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/MessageDefinition.h b/automotive/can/1.0/default/libnetdevice/protocols/MessageDefinition.h
new file mode 100644
index 0000000..9764f8d
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/MessageDefinition.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "nlbuf.h"
+#include "types.h"
+
+#include <map>
+#include <sstream>
+
+namespace android::netdevice::protocols {
+
+struct AttributeDefinition;
+
+/**
+ * Mapping between nlattrtype_t identifier and attribute definition.
+ *
+ * The map contains values for all nlattrtype_t identifiers - if some is missing, a generic
+ * definition with a identifier as its name will be generated.
+ *
+ * It's possible to define a default attribute to return instead of to_string of its identifier
+ * (useful for nested attribute lists). In such case, an entry with id=std::nullopt needs to be
+ * present in the map.
+ */
+class AttributeMap : private std::map<std::optional<nlattrtype_t>, AttributeDefinition> {
+  public:
+    using std::map<std::optional<nlattrtype_t>, AttributeDefinition>::value_type;
+
+    AttributeMap(const std::initializer_list<value_type> attrTypes);
+
+    const AttributeDefinition operator[](nlattrtype_t nla_type) const;
+};
+
+/**
+ * Attribute definition.
+ *
+ * Describes the name and type (optionally sub types, in case of Nested attribute)
+ * for a given message attribute.
+ */
+struct AttributeDefinition {
+    enum class DataType : uint8_t {
+        Raw,
+        Nested,
+        String,
+        Uint,
+    };
+
+    std::string name;
+    DataType dataType = DataType::Raw;
+    AttributeMap subTypes = {};
+};
+
+/**
+ * Message family descriptor.
+ *
+ * Describes the structure of all message types with the same header and attributes.
+ */
+class MessageDescriptor {
+  protected:
+    typedef std::map<nlmsgtype_t, std::string> MessageTypeMap;
+
+    MessageDescriptor(const std::string& name, const MessageTypeMap&& messageTypes,
+                      const AttributeMap&& attrTypes, size_t contentsSize);
+
+  public:
+    virtual ~MessageDescriptor();
+
+    size_t getContentsSize() const;
+    const MessageTypeMap& getMessageTypeMap() const;
+    const AttributeMap& getAttributeMap() const;
+    const std::string getMessageName(nlmsgtype_t msgtype) const;
+    virtual void dataToStream(std::stringstream& ss, const nlbuf<nlmsghdr> hdr) const = 0;
+
+  private:
+    const std::string mName;
+    const size_t mContentsSize;
+    const MessageTypeMap mMessageTypes;
+    const AttributeMap mAttributeMap;
+};
+
+/**
+ * Message definition template.
+ *
+ * A convenience initialization helper of a message descriptor.
+ */
+template <typename T>
+class MessageDefinition : public MessageDescriptor {
+  public:
+    MessageDefinition(
+            const std::string& name,
+            const std::initializer_list<MessageDescriptor::MessageTypeMap::value_type> messageTypes,
+            const std::initializer_list<AttributeMap::value_type> attrTypes = {})
+        : MessageDescriptor(name, messageTypes, attrTypes, sizeof(T)) {}
+
+    void dataToStream(std::stringstream& ss, const nlbuf<nlmsghdr> hdr) const override {
+        const auto msg = hdr.data<T>().getFirst();
+        if (!msg.has_value()) {
+            ss << "{incomplete payload}";
+            return;
+        }
+
+        toStream(ss, *msg);
+    }
+
+  protected:
+    virtual void toStream(std::stringstream& ss, const T& data) const = 0;
+};
+
+}  // namespace android::netdevice::protocols
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/NetlinkProtocol.cpp b/automotive/can/1.0/default/libnetdevice/protocols/NetlinkProtocol.cpp
new file mode 100644
index 0000000..4b6cefb
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/NetlinkProtocol.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "NetlinkProtocol.h"
+
+namespace android::netdevice::protocols {
+
+NetlinkProtocol::NetlinkProtocol(int protocol, const std::string name,
+                                 const MessageDescriptorList&& messageDescrs)
+    : mProtocol(protocol), mName(name), mMessageDescrs(toMap(messageDescrs, protocol)) {}
+
+NetlinkProtocol::~NetlinkProtocol() {}
+
+int NetlinkProtocol::getProtocol() const {
+    return mProtocol;
+}
+
+const std::string& NetlinkProtocol::getName() const {
+    return mName;
+}
+
+const std::optional<std::reference_wrapper<const MessageDescriptor>>
+NetlinkProtocol::getMessageDescriptor(nlmsgtype_t nlmsg_type) {
+    if (mMessageDescrs.count(nlmsg_type) == 0) return std::nullopt;
+    return *mMessageDescrs.find(nlmsg_type)->second;
+}
+
+NetlinkProtocol::MessageDescriptorMap NetlinkProtocol::toMap(
+        const NetlinkProtocol::MessageDescriptorList& descrs, int protocol) {
+    MessageDescriptorMap map;
+    for (const auto& descr : descrs) {
+        for (const auto& [mtype, mname] : descr->getMessageTypeMap()) {
+            map.emplace(mtype, descr);
+        }
+    }
+
+    const MessageDescriptorList baseDescriptors = {
+            std::make_shared<base::Empty>(),
+            std::make_shared<base::Error>(protocol),
+    };
+
+    for (const auto& descr : baseDescriptors) {
+        for (const auto& [mtype, mname] : descr->getMessageTypeMap()) {
+            map.emplace(mtype, descr);
+        }
+    }
+
+    return map;
+}
+
+}  // namespace android::netdevice::protocols
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/NetlinkProtocol.h b/automotive/can/1.0/default/libnetdevice/protocols/NetlinkProtocol.h
new file mode 100644
index 0000000..0e1878d
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/NetlinkProtocol.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "MessageDefinition.h"
+#include "common/Empty.h"
+#include "common/Error.h"
+#include "types.h"
+
+#include <string>
+#include <vector>
+
+namespace android::netdevice::protocols {
+
+/**
+ * Netlink-based protocol definition.
+ *
+ * Usually it's just an id/name and a list of supported messages.
+ */
+class NetlinkProtocol {
+  public:
+    virtual ~NetlinkProtocol();
+
+    int getProtocol() const;
+
+    const std::string& getName() const;
+
+    virtual const std::optional<std::reference_wrapper<const MessageDescriptor>>
+    getMessageDescriptor(nlmsgtype_t nlmsg_type);
+
+  protected:
+    typedef std::vector<std::shared_ptr<const MessageDescriptor>> MessageDescriptorList;
+
+    NetlinkProtocol(int protocol, const std::string name,
+                    const MessageDescriptorList&& messageDescrs);
+
+  private:
+    typedef std::map<nlmsgtype_t, std::shared_ptr<const MessageDescriptor>> MessageDescriptorMap;
+
+    const int mProtocol;
+    const std::string mName;
+    const MessageDescriptorMap mMessageDescrs;
+
+    static MessageDescriptorMap toMap(const MessageDescriptorList& descrs, int protocol);
+};
+
+}  // namespace android::netdevice::protocols
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/README b/automotive/can/1.0/default/libnetdevice/protocols/README
new file mode 100644
index 0000000..45c95c4
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/README
@@ -0,0 +1,8 @@
+This folder contains message definitions for various protocols based on Netlink.
+
+The structure is as follows:
+protocols/*.(cpp|h)                - base definition classes and protocol definition lookup
+protocols/common/                  - common message types that apply to all protocols
+protocols/<proto>/<proto>.(cpp|h)  - protocol definition (usually just a list of message types)
+protocols/<proto>/*.(cpp|h)        - message definition that covers all message types with the same
+                                     header (T type in MessageDefinition template) and attributes
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/all.cpp b/automotive/can/1.0/default/libnetdevice/protocols/all.cpp
new file mode 100644
index 0000000..980e3d0
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/all.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "all.h"
+
+#include "generic/Generic.h"
+#include "route/Route.h"
+
+#include <map>
+
+namespace android::netdevice::protocols {
+
+// This should be a map of unique_ptr, but it's not trivial to uniformly initialize such a map
+static std::map<int, std::shared_ptr<NetlinkProtocol>> toMap(
+        std::initializer_list<std::shared_ptr<NetlinkProtocol>> l) {
+    std::map<int, std::shared_ptr<NetlinkProtocol>> map;
+    for (auto p : l) {
+        map[p->getProtocol()] = p;
+    }
+    return map;
+}
+
+static auto all = toMap({
+        std::make_unique<generic::Generic>(),
+        std::make_unique<route::Route>(),
+});
+
+std::optional<std::reference_wrapper<NetlinkProtocol>> get(int protocol) {
+    if (all.count(protocol) == 0) return std::nullopt;
+    return *all.find(protocol)->second.get();
+}
+
+}  // namespace android::netdevice::protocols
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/all.h b/automotive/can/1.0/default/libnetdevice/protocols/all.h
new file mode 100644
index 0000000..2180ebb
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/all.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "NetlinkProtocol.h"
+
+namespace android::netdevice::protocols {
+
+/**
+ * Protocol definition lookup.
+ *
+ * \param protocol Protocol identifier from linux/netlink.h
+ * \return Protocol definition or nullopt if it's not implemented
+ */
+std::optional<std::reference_wrapper<NetlinkProtocol>> get(int protocol);
+
+}  // namespace android::netdevice::protocols
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/common/Empty.cpp b/automotive/can/1.0/default/libnetdevice/protocols/common/Empty.cpp
new file mode 100644
index 0000000..9f25203
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/common/Empty.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Empty.h"
+
+namespace android::netdevice::protocols::base {
+
+// clang-format off
+Empty::Empty() : MessageDefinition<char>("nlmsg", {
+    {NLMSG_NOOP, "NOOP"},
+    {NLMSG_DONE, "DONE"},
+    {NLMSG_OVERRUN, "OVERRUN"},
+}) {}
+// clang-format on
+
+void Empty::toStream(std::stringstream&, const char&) const {}
+
+}  // namespace android::netdevice::protocols::base
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/common/Empty.h b/automotive/can/1.0/default/libnetdevice/protocols/common/Empty.h
new file mode 100644
index 0000000..b5b317f
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/common/Empty.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "../MessageDefinition.h"
+
+#include <libnetdevice/printer.h>
+
+namespace android::netdevice::protocols::base {
+
+// no-payload (like NLMSG_NOOP) messages can't be defined with T=void, so let's use char
+class Empty : public MessageDefinition<char> {
+  public:
+    Empty();
+    void toStream(std::stringstream&, const char&) const override;
+};
+
+}  // namespace android::netdevice::protocols::base
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/common/Error.cpp b/automotive/can/1.0/default/libnetdevice/protocols/common/Error.cpp
new file mode 100644
index 0000000..d42a50a
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/common/Error.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Error.h"
+
+#include "../MessageDefinition.h"
+
+#include <libnetdevice/printer.h>
+
+namespace android::netdevice::protocols::base {
+
+// clang-format off
+Error::Error(int protocol) : MessageDefinition<nlmsgerr>("nlmsg", {
+    {NLMSG_ERROR, "ERROR"},
+}), mProtocol(protocol) {}
+// clang-format on
+
+void Error::toStream(std::stringstream& ss, const nlmsgerr& data) const {
+    ss << "nlmsgerr{error=" << data.error
+       << ", msg=" << toString(&data.msg, sizeof(data.msg), mProtocol) << "}";
+}
+
+}  // namespace android::netdevice::protocols::base
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/common/Error.h b/automotive/can/1.0/default/libnetdevice/protocols/common/Error.h
new file mode 100644
index 0000000..1f3c1dd
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/common/Error.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "../MessageDefinition.h"
+
+namespace android::netdevice::protocols::base {
+
+class Error : public MessageDefinition<nlmsgerr> {
+  public:
+    Error(int protocol);
+    void toStream(std::stringstream& ss, const nlmsgerr& data) const override;
+
+  private:
+    const int mProtocol;
+};
+
+}  // namespace android::netdevice::protocols::base
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/generic/Ctrl.cpp b/automotive/can/1.0/default/libnetdevice/protocols/generic/Ctrl.cpp
new file mode 100644
index 0000000..08b2be7
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/generic/Ctrl.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Ctrl.h"
+
+namespace android::netdevice::protocols::generic {
+
+using DataType = AttributeDefinition::DataType;
+
+// clang-format off
+Ctrl::Ctrl() : GenericMessageBase(GENL_ID_CTRL, "ID_CTRL", {
+    {CTRL_CMD_NEWFAMILY, "NEWFAMILY"},
+    {CTRL_CMD_DELFAMILY, "DELFAMILY"},
+    {CTRL_CMD_GETFAMILY, "GETFAMILY"},
+    {CTRL_CMD_NEWOPS, "NEWOPS"},
+    {CTRL_CMD_DELOPS, "DELOPS"},
+    {CTRL_CMD_GETOPS, "GETOPS"},
+    {CTRL_CMD_NEWMCAST_GRP, "NEWMCAST_GRP"},
+    {CTRL_CMD_DELMCAST_GRP, "DELMCAST_GRP"},
+    {CTRL_CMD_GETMCAST_GRP, "GETMCAST_GRP"},
+}, {
+    {CTRL_ATTR_FAMILY_ID, {"FAMILY_ID", DataType::Uint}},
+    {CTRL_ATTR_FAMILY_NAME, {"FAMILY_NAME", DataType::String}},
+    {CTRL_ATTR_VERSION, {"VERSION", DataType::Uint}},
+    {CTRL_ATTR_HDRSIZE, {"HDRSIZE", DataType::Uint}},
+    {CTRL_ATTR_MAXATTR, {"MAXATTR", DataType::Uint}},
+    {CTRL_ATTR_OPS, {"OPS", DataType::Nested, {
+        {std::nullopt, {"OP", DataType::Nested, {
+            {CTRL_ATTR_OP_ID, {"ID", DataType::Uint}},
+            {CTRL_ATTR_OP_FLAGS, {"FLAGS", DataType::Uint}},
+        }}},
+    }}},
+    {CTRL_ATTR_MCAST_GROUPS, {"MCAST_GROUPS", DataType::Nested, {
+        {std::nullopt, {"GRP", DataType::Nested, {
+            {CTRL_ATTR_MCAST_GRP_NAME, {"NAME", DataType::String}},
+            {CTRL_ATTR_MCAST_GRP_ID, {"ID", DataType::Uint}},
+        }}},
+    }}},
+}) {}
+// clang-format on
+
+}  // namespace android::netdevice::protocols::generic
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/generic/Ctrl.h b/automotive/can/1.0/default/libnetdevice/protocols/generic/Ctrl.h
new file mode 100644
index 0000000..804ed2c
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/generic/Ctrl.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "GenericMessageBase.h"
+
+namespace android::netdevice::protocols::generic {
+
+class Ctrl : public GenericMessageBase {
+  public:
+    Ctrl();
+};
+
+}  // namespace android::netdevice::protocols::generic
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/generic/Generic.cpp b/automotive/can/1.0/default/libnetdevice/protocols/generic/Generic.cpp
new file mode 100644
index 0000000..633ef3c
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/generic/Generic.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Generic.h"
+
+#include "Ctrl.h"
+#include "Unknown.h"
+
+namespace android::netdevice::protocols::generic {
+
+Generic::Generic() : NetlinkProtocol(NETLINK_GENERIC, "GENERIC", {std::make_shared<Ctrl>()}) {}
+
+const std::optional<std::reference_wrapper<const MessageDescriptor>> Generic::getMessageDescriptor(
+        nlmsgtype_t nlmsg_type) {
+    const auto desc = NetlinkProtocol::getMessageDescriptor(nlmsg_type);
+    if (desc.has_value()) return desc;
+
+    auto it = mFamilyRegister.find(nlmsg_type);
+    if (it != mFamilyRegister.end()) return *it->second;
+    return *(mFamilyRegister[nlmsg_type] = std::make_shared<Unknown>(nlmsg_type));
+}
+
+}  // namespace android::netdevice::protocols::generic
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/generic/Generic.h b/automotive/can/1.0/default/libnetdevice/protocols/generic/Generic.h
new file mode 100644
index 0000000..b4352f6
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/generic/Generic.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "../NetlinkProtocol.h"
+
+namespace android::netdevice::protocols::generic {
+
+/**
+ * Definition of NETLINK_GENERIC protocol.
+ */
+class Generic : public NetlinkProtocol {
+  public:
+    Generic();
+
+    const std::optional<std::reference_wrapper<const MessageDescriptor>> getMessageDescriptor(
+            nlmsgtype_t nlmsg_type);
+
+  private:
+    std::map<nlmsgtype_t, std::shared_ptr<const MessageDescriptor>> mFamilyRegister;
+};
+
+}  // namespace android::netdevice::protocols::generic
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/generic/GenericMessageBase.cpp b/automotive/can/1.0/default/libnetdevice/protocols/generic/GenericMessageBase.cpp
new file mode 100644
index 0000000..c9f0813
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/generic/GenericMessageBase.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "GenericMessageBase.h"
+
+namespace android::netdevice::protocols::generic {
+
+GenericMessageBase::GenericMessageBase(
+        nlmsgtype_t msgtype, std::string msgname,
+        const std::initializer_list<GenericCommandNameMap::value_type> commandNames,
+        const std::initializer_list<AttributeMap::value_type> attrTypes)
+    : MessageDefinition<struct genlmsghdr>(msgname, {{msgtype, msgname}}, attrTypes),
+      mCommandNames(commandNames) {}
+
+void GenericMessageBase::toStream(std::stringstream& ss, const struct genlmsghdr& data) const {
+    ss << "genlmsghdr{";
+    if (mCommandNames.count(data.cmd) == 0) {
+        ss << "cmd=" << unsigned(data.cmd);
+    } else {
+        ss << "cmd=" << mCommandNames.find(data.cmd)->second;
+    }
+    ss << ", version=" << unsigned(data.version);
+    if (data.reserved != 0) ss << ", reserved=" << data.reserved;
+    ss << "}";
+}
+
+}  // namespace android::netdevice::protocols::generic
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/generic/GenericMessageBase.h b/automotive/can/1.0/default/libnetdevice/protocols/generic/GenericMessageBase.h
new file mode 100644
index 0000000..2a19034
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/generic/GenericMessageBase.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "../MessageDefinition.h"
+
+#include <linux/genetlink.h>
+
+namespace android::netdevice::protocols::generic {
+
+class GenericMessageBase : public MessageDefinition<struct genlmsghdr> {
+  public:
+    typedef std::map<uint8_t, std::string> GenericCommandNameMap;
+
+    GenericMessageBase(
+            nlmsgtype_t msgtype, std::string msgname,
+            const std::initializer_list<GenericCommandNameMap::value_type> commandNames = {},
+            const std::initializer_list<AttributeMap::value_type> attrTypes = {});
+
+    void toStream(std::stringstream& ss, const struct genlmsghdr& data) const override;
+
+  private:
+    const GenericCommandNameMap mCommandNames;
+};
+
+}  // namespace android::netdevice::protocols::generic
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/generic/Unknown.cpp b/automotive/can/1.0/default/libnetdevice/protocols/generic/Unknown.cpp
new file mode 100644
index 0000000..9a71d89
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/generic/Unknown.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Unknown.h"
+
+namespace android::netdevice::protocols::generic {
+
+Unknown::Unknown(nlmsgtype_t msgtype)
+    : GenericMessageBase(msgtype, "Unknown(" + std::to_string(msgtype) + ")") {}
+
+}  // namespace android::netdevice::protocols::generic
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/generic/Unknown.h b/automotive/can/1.0/default/libnetdevice/protocols/generic/Unknown.h
new file mode 100644
index 0000000..82a5501
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/generic/Unknown.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "GenericMessageBase.h"
+
+namespace android::netdevice::protocols::generic {
+
+class Unknown : public GenericMessageBase {
+  public:
+    Unknown(nlmsgtype_t msgtype);
+};
+
+}  // namespace android::netdevice::protocols::generic
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/route/Link.cpp b/automotive/can/1.0/default/libnetdevice/protocols/route/Link.cpp
new file mode 100644
index 0000000..4617d92
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/route/Link.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Link.h"
+
+namespace android::netdevice::protocols::route {
+
+using DataType = AttributeDefinition::DataType;
+
+// clang-format off
+Link::Link() : MessageDefinition<struct ifinfomsg>("link", {
+    {RTM_NEWLINK, "NEWLINK"},
+    {RTM_DELLINK, "DELLINK"},
+    {RTM_GETLINK, "GETLINK"},
+}, {
+    {IFLA_ADDRESS, {"ADDRESS"}},
+    {IFLA_BROADCAST, {"BROADCAST"}},
+    {IFLA_IFNAME, {"IFNAME", DataType::String}},
+    {IFLA_MTU, {"MTU"}},
+    {IFLA_LINK, {"LINK", DataType::Uint}},
+    {IFLA_QDISC, {"QDISC"}},
+    {IFLA_STATS, {"STATS"}},
+    {IFLA_COST, {"COST"}},
+    {IFLA_PRIORITY, {"PRIORITY"}},
+    {IFLA_MASTER, {"MASTER"}},
+    {IFLA_WIRELESS, {"WIRELESS"}},
+    {IFLA_PROTINFO, {"PROTINFO"}},
+    {IFLA_TXQLEN, {"TXQLEN"}},
+    {IFLA_MAP, {"MAP"}},
+    {IFLA_WEIGHT, {"WEIGHT"}},
+    {IFLA_OPERSTATE, {"OPERSTATE"}},
+    {IFLA_LINKMODE, {"LINKMODE"}},
+    {IFLA_LINKINFO, {"LINKINFO", DataType::Nested, {
+        {IFLA_INFO_KIND, {"INFO_KIND", DataType::String}},
+        {IFLA_INFO_DATA, {"INFO_DATA", DataType::Nested}},
+        {IFLA_INFO_XSTATS, {"INFO_XSTATS"}},
+        {IFLA_INFO_SLAVE_KIND, {"INFO_SLAVE_KIND"}},
+        {IFLA_INFO_SLAVE_DATA, {"INFO_SLAVE_DATA"}},
+    }}},
+    {IFLA_NET_NS_PID, {"NET_NS_PID"}},
+    {IFLA_IFALIAS, {"IFALIAS"}},
+    {IFLA_NUM_VF, {"NUM_VF"}},
+    {IFLA_VFINFO_LIST, {"VFINFO_LIST"}},
+    {IFLA_STATS64, {"STATS64"}},
+    {IFLA_VF_PORTS, {"VF_PORTS"}},
+    {IFLA_PORT_SELF, {"PORT_SELF"}},
+    {IFLA_AF_SPEC, {"AF_SPEC"}},
+    {IFLA_GROUP, {"GROUP"}},
+    {IFLA_NET_NS_FD, {"NET_NS_FD"}},
+    {IFLA_EXT_MASK, {"EXT_MASK"}},
+    {IFLA_PROMISCUITY, {"PROMISCUITY"}},
+    {IFLA_NUM_TX_QUEUES, {"NUM_TX_QUEUES"}},
+    {IFLA_NUM_RX_QUEUES, {"NUM_RX_QUEUES"}},
+    {IFLA_CARRIER, {"CARRIER"}},
+    {IFLA_PHYS_PORT_ID, {"PHYS_PORT_ID"}},
+    {IFLA_CARRIER_CHANGES, {"CARRIER_CHANGES"}},
+    {IFLA_PHYS_SWITCH_ID, {"PHYS_SWITCH_ID"}},
+    {IFLA_LINK_NETNSID, {"LINK_NETNSID"}},
+    {IFLA_PHYS_PORT_NAME, {"PHYS_PORT_NAME"}},
+    {IFLA_PROTO_DOWN, {"PROTO_DOWN"}},
+    {IFLA_GSO_MAX_SEGS, {"GSO_MAX_SEGS"}},
+    {IFLA_GSO_MAX_SIZE, {"GSO_MAX_SIZE"}},
+    {IFLA_PAD, {"PAD"}},
+    {IFLA_XDP, {"XDP"}},
+    {IFLA_EVENT, {"EVENT"}},
+    {IFLA_NEW_NETNSID, {"NEW_NETNSID"}},
+    {IFLA_TARGET_NETNSID, {"TARGET_NETNSID"}},
+    {IFLA_CARRIER_UP_COUNT, {"CARRIER_UP_COUNT"}},
+    {IFLA_CARRIER_DOWN_COUNT, {"CARRIER_DOWN_COUNT"}},
+    {IFLA_NEW_IFINDEX, {"NEW_IFINDEX"}},
+    {IFLA_MIN_MTU, {"MIN_MTU"}},
+    {IFLA_MAX_MTU, {"MAX_MTU"}},
+    {IFLA_PROP_LIST, {"PROP_LIST"}},
+    {IFLA_ALT_IFNAME, {"ALT_IFNAME"}},
+    {IFLA_PERM_ADDRESS, {"PERM_ADDRESS"}},
+}) {}
+// clang-format off
+
+void Link::toStream(std::stringstream& ss, const struct ifinfomsg& data) const {
+    ss << "ifinfomsg{"
+       << "family=" << unsigned(data.ifi_family) << ", type=" << data.ifi_type
+       << ", index=" << data.ifi_index << ", flags=" << data.ifi_flags
+       << ", change=" << data.ifi_change << "}";
+}
+
+}  // namespace android::netdevice::protocols::route
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/route/Link.h b/automotive/can/1.0/default/libnetdevice/protocols/route/Link.h
new file mode 100644
index 0000000..bcfce19
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/route/Link.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "../MessageDefinition.h"
+
+#include <linux/rtnetlink.h>
+
+namespace android::netdevice::protocols::route {
+
+class Link : public MessageDefinition<struct ifinfomsg> {
+  public:
+    Link();
+    void toStream(std::stringstream& ss, const struct ifinfomsg& data) const override;
+};
+
+}  // namespace android::netdevice::protocols::route
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/route/Route.cpp b/automotive/can/1.0/default/libnetdevice/protocols/route/Route.cpp
new file mode 100644
index 0000000..456072b
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/route/Route.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Route.h"
+
+#include "Link.h"
+
+namespace android::netdevice::protocols::route {
+
+Route::Route() : NetlinkProtocol(NETLINK_ROUTE, "ROUTE", {std::make_shared<Link>()}) {}
+
+}  // namespace android::netdevice::protocols::route
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/route/Route.h b/automotive/can/1.0/default/libnetdevice/protocols/route/Route.h
new file mode 100644
index 0000000..3051cf9
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/route/Route.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "../NetlinkProtocol.h"
+
+namespace android::netdevice::protocols::route {
+
+/**
+ * Definition of NETLINK_ROUTE protocol.
+ */
+class Route : public NetlinkProtocol {
+  public:
+    Route();
+};
+
+}  // namespace android::netdevice::protocols::route
diff --git a/automotive/can/1.0/default/libnetdevice/types.h b/automotive/can/1.0/default/libnetdevice/types.h
new file mode 100644
index 0000000..9d90c8a
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/types.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <linux/types.h>
+
+namespace android::netdevice {
+
+typedef __u16 nlmsgtype_t;            // nlmsghdr::nlmsg_type
+typedef __u16 nlattrtype_t;           // nlattr::nla_type
+typedef unsigned short rtattrtype_t;  // rtattr::rta_type
+
+}  // namespace android::netdevice