Basic support for generic netlink.

Add the ability to perform basic genetlink requests and dumps,
and find generic netlink families by name.

Bug: 63449462
Test: all_tests.sh passes on android-4.9
Change-Id: Ic15c40328007768f33a56c559535f004a8bf8fda
diff --git a/net/test/genetlink.py b/net/test/genetlink.py
new file mode 100755
index 0000000..dda3964
--- /dev/null
+++ b/net/test/genetlink.py
@@ -0,0 +1,123 @@
+#!/usr/bin/python
+#
+# Copyright 2017 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.
+
+"""Classes for generic netlink."""
+
+import collections
+from socket import *  # pylint: disable=wildcard-import
+import struct
+
+import cstruct
+import netlink
+
+### Generic netlink constants. See include/uapi/linux/genetlink.h.
+# The generic netlink control family.
+GENL_ID_CTRL = 16
+
+# Commands.
+CTRL_CMD_GETFAMILY = 3
+
+# Attributes.
+CTRL_ATTR_FAMILY_ID = 1
+CTRL_ATTR_FAMILY_NAME = 2
+CTRL_ATTR_VERSION = 3
+CTRL_ATTR_HDRSIZE = 4
+CTRL_ATTR_MAXATTR = 5
+CTRL_ATTR_OPS = 6
+CTRL_ATTR_MCAST_GROUPS = 7
+
+# Attributes netsted inside CTRL_ATTR_OPS.
+CTRL_ATTR_OP_ID = 1
+CTRL_ATTR_OP_FLAGS = 2
+
+
+# Data structure formats.
+# These aren't constants, they're classes. So, pylint: disable=invalid-name
+Genlmsghdr = cstruct.Struct("genlmsghdr", "BBxx", "cmd version")
+
+
+class GenericNetlink(netlink.NetlinkSocket):
+  """Base class for all generic netlink classes."""
+
+  NL_DEBUG = []
+
+  def __init__(self):
+    super(GenericNetlink, self).__init__(netlink.NETLINK_GENERIC)
+
+  def _SendCommand(self, family, command, version, data, flags):
+    genlmsghdr = Genlmsghdr((command, version))
+    self._SendNlRequest(family, genlmsghdr.Pack() + data, flags)
+
+  def _Dump(self, family, command, version):
+    msg = Genlmsghdr((command, version))
+    return super(GenericNetlink, self)._Dump(family, msg, Genlmsghdr, "")
+
+
+class GenericNetlinkControl(GenericNetlink):
+  """Generic netlink control class.
+
+  This interface is used to manage other generic netlink families. We currently
+  use it only to find the family ID for address families of interest."""
+
+  def _DecodeOps(self, data):
+    ops = []
+    Op = collections.namedtuple("Op", ["id", "flags"])
+    while data:
+      # Skip the nest marker.
+      datalen, index, data = data[:2], data[2:4], data[4:]
+
+      nla, nla_data, data = self._ReadNlAttr(data)
+      if nla.nla_type != CTRL_ATTR_OP_ID:
+        raise ValueError("Expected CTRL_ATTR_OP_ID, got %d" % nla.nla_type)
+      op_id = struct.unpack("=I", nla_data)[0]
+
+      nla, nla_data, data = self._ReadNlAttr(data)
+      if nla.nla_type != CTRL_ATTR_OP_FLAGS:
+        raise ValueError("Expected CTRL_ATTR_OP_FLAGS, got %d" % nla.type)
+      op_flags = struct.unpack("=I", nla_data)[0]
+
+      ops.append(Op(op_id, op_flags))
+    return ops
+
+  def _Decode(self, command, msg, nla_type, nla_data):
+    """Decodes generic netlink control attributes to human-readable format."""
+
+    name = self._GetConstantName(__name__, nla_type, "CTRL_ATTR_")
+
+    if name == "CTRL_ATTR_FAMILY_ID":
+      data = struct.unpack("=H", nla_data)[0]
+    elif name == "CTRL_ATTR_FAMILY_NAME":
+      data = nla_data.strip("\x00")
+    elif name in ["CTRL_ATTR_VERSION", "CTRL_ATTR_HDRSIZE", "CTRL_ATTR_MAXATTR"]:
+      data = struct.unpack("=I", nla_data)[0]
+    elif name == "CTRL_ATTR_OPS":
+      data = self._DecodeOps(nla_data)
+    else:
+      data = nla_data
+
+    return name, data
+
+  def GetFamily(self, name):
+    """Returns the family ID for the specified family name."""
+    data = self._NlAttrStr(CTRL_ATTR_FAMILY_NAME, name)
+    self._SendCommand(GENL_ID_CTRL, CTRL_CMD_GETFAMILY, 0, data, netlink.NLM_F_REQUEST)
+    hdr, attrs = self._GetMsg(Genlmsghdr)
+    return attrs["CTRL_ATTR_FAMILY_ID"]
+
+
+if __name__ == "__main__":
+  g = GenericNetlinkControl()
+  print g.GetFamily("tcp_metrics")
diff --git a/net/test/netlink.py b/net/test/netlink.py
index e3a9c05..8a9e795 100644
--- a/net/test/netlink.py
+++ b/net/test/netlink.py
@@ -29,6 +29,7 @@
 NETLINK_ROUTE = 0
 NETLINK_SOCK_DIAG = 4
 NETLINK_XFRM = 6
+NETLINK_GENERIC = 16
 
 # Request constants.
 NLM_F_REQUEST = 1
@@ -101,6 +102,17 @@
     """No-op, nonspecific version of decode."""
     return nla_type, nla_data
 
+  def _ReadNlAttr(self, data):
+    # Read the nlattr header.
+    nla, data = cstruct.Read(data, NLAttr)
+
+    # Read the data.
+    datalen = nla.nla_len - len(nla)
+    padded_len = PaddedLength(nla.nla_len) - len(nla)
+    nla_data, data = data[:datalen], data[padded_len:]
+
+    return nla, nla_data, data
+
   def _ParseAttributes(self, command, msg, data):
     """Parses and decodes netlink attributes.
 
@@ -120,13 +132,7 @@
     """
     attributes = {}
     while data:
-      # Read the nlattr header.
-      nla, data = cstruct.Read(data, NLAttr)
-
-      # Read the data.
-      datalen = nla.nla_len - len(nla)
-      padded_len = PaddedLength(nla.nla_len) - len(nla)
-      nla_data, data = data[:datalen], data[padded_len:]
+      nla, nla_data, data = self._ReadNlAttr(data)
 
       # If it's an attribute we know about, try to decode it.
       nla_name, nla_data = self._Decode(command, msg, nla.nla_type, nla_data)