ip.c route change support for extended attributes.

This fixes the extended attribute settings for the ip route change
command; extended attributes are collected in a separate buffer
and added to the RTM message in a specific RTA_METRICS attribute.
Only added setter support for the RTAX_MTU (per-route MTU), as that
was the only option currently parsed.

Also added support for displaying extended attributes, even those
not currently settable.
diff --git a/toys/pending/ip.c b/toys/pending/ip.c
index c4bb5d6..82e18fd 100644
--- a/toys/pending/ip.c
+++ b/toys/pending/ip.c
@@ -103,6 +103,23 @@
 static int filter_nlmesg(int (*fun)(struct nlmsghdr *mhdr, char **), char **);
 static int  ipaddr_print(struct linkdata *, int flg);
 
+// extended route attribute metrics.
+static const char *mx_names[RTAX_MAX + 1] = {
+    [RTAX_MTU] = "mtu",
+    [RTAX_WINDOW] = "window",
+    [RTAX_RTT] = "rtt",
+    [RTAX_RTTVAR] = "rttvar",
+    [RTAX_SSTHRESH] = "ssthresh",
+    [RTAX_CWND] = "cwnd",
+    [RTAX_ADVMSS] = "advmss",
+    [RTAX_REORDERING] = "reordering",
+    [RTAX_HOPLIMIT] = "hoplimit",
+    [RTAX_INITCWND] = "initcwnd",
+    [RTAX_FEATURES] = "features",
+    [RTAX_RTO_MIN] = "rto_min",
+    [RTAX_INITRWND] = "initrwnd",
+    [RTAX_QUICKACK] = "quickack",
+    [RTAX_CC_ALGO] = "congctl"};
 
 // ===========================================================================
 // Common Code for IP Options (like: addr, link, route etc.)
@@ -427,6 +444,35 @@
   else *len = (af == AF_INET6) ? 16 : 4;
 }
 
+/*
+ * Add a route attribute to a buffer; this is primarily used for extended
+ * attributes which get collected in a separate buffer from the normal route
+ * attributes and later get added to the main rtm message.
+ */
+static void add_varlen_rtattr_to_buffer(struct rtattr *rta, int maxlen,
+                                        int type, void *data, int alen) {
+  struct rtattr *subrta;
+  int len = RTA_LENGTH(alen);
+  if (RTA_ALIGN(rta->rta_len) + RTA_ALIGN(len) > maxlen) {
+      error_exit("RTA exceeds max length %d", maxlen);
+  }
+  subrta = (struct rtattr*)(((char*)rta) + RTA_ALIGN(rta->rta_len));
+  subrta->rta_type = type;
+  subrta->rta_len = len;
+  if (alen) {
+    memcpy(RTA_DATA(subrta), data, alen);
+  }
+  rta->rta_len = NLMSG_ALIGN(rta->rta_len) + RTA_ALIGN(len);
+}
+
+static void add_uint32_rtattr_to_buffer(struct rtattr *rta, int maxlen,
+                                        int type, uint32_t attr) {
+  add_varlen_rtattr_to_buffer(rta, maxlen, type, (char*)&attr, sizeof(attr));
+}
+
+/*
+ * Add a route attribute to a RTM message.
+ */
 static void add_string_to_rtattr(struct nlmsghdr *n, int maxlen,
     int type, void *data, int alen)
 {
@@ -1463,6 +1509,65 @@
   error_exit(errmsg);
 }
 
+static void print_rta_metrics(char* out, const struct rtattr *mxattr)
+{
+  int32_t tvar = RTA_PAYLOAD(mxattr);
+  struct rtattr *rta, *mxrta[RTAX_MAX+1] = {0,};
+  unsigned int mxlock = 0;
+  int i;
+
+  for (rta = RTA_DATA(mxattr); RTA_OK(rta, tvar); rta=RTA_NEXT(rta, tvar))
+    if (rta->rta_type <= RTA_MAX) mxrta[rta->rta_type] = rta;
+
+  if (mxrta[RTAX_LOCK])
+    mxlock = *(u_int32_t *)RTA_DATA(mxrta[RTAX_LOCK]);
+
+  for (i = 2; i <= RTAX_MAX; i++) {
+    uint32_t val = 0;
+
+    if (mxrta[i] == NULL && !(mxlock & (1 << i)))
+      continue;
+
+    if (mxrta[i] != NULL && i != RTAX_CC_ALGO)
+      val = *(u_int32_t *)RTA_DATA(mxrta[i]);
+
+    if (i == RTAX_HOPLIMIT && (int)val == -1)
+      continue;
+
+    if (i < sizeof(mx_names)/sizeof(char *) && mx_names[i])
+      sprintf(out, "%s%s ", out, mx_names[i]);
+    else
+      sprintf(out, "%smetric %d ", out, i);
+
+    if (mxlock & (1<<i))
+      sprintf(out, "%slock ", out);
+
+    switch (i) {
+      case RTAX_RTT:
+      case RTAX_RTTVAR:
+      case RTAX_RTO_MIN:
+        if (i == RTAX_RTT)
+          val /= 8;
+        else if (i == RTAX_RTTVAR)
+          val /= 4;
+
+        if (val >= 1000)
+          sprintf(out, "%s%gs ", out, val / 1e3);
+        else
+          sprintf(out, "%s%ums ", out, val);
+        break;
+
+      case RTAX_CC_ALGO:
+        sprintf(out, "%scongestion %s ", out, (const char*)RTA_DATA(mxrta[i]));
+        break;
+
+      default:
+        sprintf(out, "%s%u ", out, val);
+        break;
+    }
+  }
+}
+
 static int display_route_info(struct nlmsghdr *mhdr, char **argv)
 {
   char *inetval = NULL, out[1024] = {0};
@@ -1592,6 +1697,10 @@
   if (attr[RTA_IIF] && !gfilter.idev)
     sprintf(out, "%s iif %s", out, 
         if_indextoname(*(int*)RTA_DATA(attr[RTA_IIF]), toybuf));
+
+  if (attr[RTA_METRICS])
+    print_rta_metrics(out, attr[RTA_METRICS]);
+
   if (TT.flush || (TT.connected && !TT.from_ok)) 
     memcpy(toybuf, (void*)mhdr,mhdr->nlmsg_len);
 
@@ -1947,8 +2056,7 @@
         if (!*++argv) show_iproute_help();
       }
       idx = atolx(*argv);
-      add_string_to_rtattr(&req.hdr, sizeof(req),
-          RTAX_MTU, (char*)&idx, sizeof(idx));
+      add_uint32_rtattr_to_buffer(mxrta, sizeof(mxbuf), RTAX_MTU, idx);
     } else if (idx == 4) {
       if (!*++argv) show_iproute_help();
       if ((idx = idxfromRPDB(*argv,RPDB_rtprotos)) < 0)
@@ -1998,8 +2106,7 @@
   }
   if (mxrta->rta_len > RTA_LENGTH(0)) {
     if (mxlock)
-      add_string_to_rtattr(&req.hdr, sizeof(req),
-          RTAX_LOCK, (char*)&mxlock, sizeof(mxlock));
+      add_uint32_rtattr_to_buffer(mxrta, sizeof(mxbuf), RTAX_LOCK, mxlock);
     add_string_to_rtattr(&req.hdr, sizeof(req),
         RTA_METRICS, RTA_DATA(mxrta), RTA_PAYLOAD(mxrta));
   }