selftests: add PM netlink functional tests
authorPaolo Abeni <pabeni@redhat.com>
Fri, 27 Mar 2020 21:48:52 +0000 (14:48 -0700)
committerDavid S. Miller <davem@davemloft.net>
Mon, 30 Mar 2020 05:14:49 +0000 (22:14 -0700)
This introduces basic self-tests for the PM netlink,
checking the basic APIs and possible exceptional
values.

Signed-off-by: Paolo Abeni <pabeni@redhat.com>
Signed-off-by: Mat Martineau <mathew.j.martineau@linux.intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
tools/testing/selftests/net/mptcp/.gitignore
tools/testing/selftests/net/mptcp/Makefile
tools/testing/selftests/net/mptcp/pm_netlink.sh [new file with mode: 0755]
tools/testing/selftests/net/mptcp/pm_nl_ctl.c [new file with mode: 0644]

index ba450e6..70c831f 100644 (file)
@@ -1,12 +1,13 @@
 # SPDX-License-Identifier: GPL-2.0
 
 top_srcdir = ../../../../..
+KSFT_KHDR_INSTALL := 1
 
-CFLAGS =  -Wall -Wl,--no-as-needed -O2 -g
+CFLAGS =  -Wall -Wl,--no-as-needed -O2 -g  -I$(top_srcdir)/usr/include
 
-TEST_PROGS := mptcp_connect.sh
+TEST_PROGS := mptcp_connect.sh pm_netlink.sh
 
-TEST_GEN_FILES = mptcp_connect
+TEST_GEN_FILES = mptcp_connect pm_nl_ctl
 
 TEST_FILES := settings
 
diff --git a/tools/testing/selftests/net/mptcp/pm_netlink.sh b/tools/testing/selftests/net/mptcp/pm_netlink.sh
new file mode 100755 (executable)
index 0000000..8c7bd72
--- /dev/null
@@ -0,0 +1,130 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+ksft_skip=4
+ret=0
+
+usage() {
+       echo "Usage: $0 [ -h ]"
+}
+
+
+while getopts "$optstring" option;do
+       case "$option" in
+       "h")
+               usage $0
+               exit 0
+               ;;
+       "?")
+               usage $0
+               exit 1
+               ;;
+       esac
+done
+
+sec=$(date +%s)
+rndh=$(printf %x $sec)-$(mktemp -u XXXXXX)
+ns1="ns1-$rndh"
+err=$(mktemp)
+ret=0
+
+cleanup()
+{
+       rm -f $out
+       ip netns del $ns1
+}
+
+ip -Version > /dev/null 2>&1
+if [ $? -ne 0 ];then
+       echo "SKIP: Could not run test without ip tool"
+       exit $ksft_skip
+fi
+
+trap cleanup EXIT
+
+ip netns add $ns1 || exit $ksft_skip
+ip -net $ns1 link set lo up
+ip netns exec $ns1 sysctl -q net.mptcp.enabled=1
+
+check()
+{
+       local cmd="$1"
+       local expected="$2"
+       local msg="$3"
+       local out=`$cmd 2>$err`
+       local cmd_ret=$?
+
+       printf "%-50s %s" "$msg"
+       if [ $cmd_ret -ne 0 ]; then
+               echo "[FAIL] command execution '$cmd' stderr "
+               cat $err
+               ret=1
+       elif [ "$out" = "$expected" ]; then
+               echo "[ OK ]"
+       else
+               echo -n "[FAIL] "
+               echo "expected '$expected' got '$out'"
+               ret=1
+       fi
+}
+
+check "ip netns exec $ns1 ./pm_nl_ctl dump" "" "defaults addr list"
+check "ip netns exec $ns1 ./pm_nl_ctl limits" "accept 0
+subflows 0" "defaults limits"
+
+ip netns exec $ns1 ./pm_nl_ctl add 10.0.1.1
+ip netns exec $ns1 ./pm_nl_ctl add 10.0.1.2 flags subflow dev lo
+ip netns exec $ns1 ./pm_nl_ctl add 10.0.1.3 flags signal,backup
+check "ip netns exec $ns1 ./pm_nl_ctl get 1" "id 1 flags  10.0.1.1 " "simple add/get addr"
+
+check "ip netns exec $ns1 ./pm_nl_ctl dump" \
+"id 1 flags  10.0.1.1
+id 2 flags subflow dev lo 10.0.1.2
+id 3 flags signal,backup 10.0.1.3 " "dump addrs"
+
+ip netns exec $ns1 ./pm_nl_ctl del 2
+check "ip netns exec $ns1 ./pm_nl_ctl get 2" "" "simple del addr"
+check "ip netns exec $ns1 ./pm_nl_ctl dump" \
+"id 1 flags  10.0.1.1
+id 3 flags signal,backup 10.0.1.3 " "dump addrs after del"
+
+ip netns exec $ns1 ./pm_nl_ctl add 10.0.1.3
+check "ip netns exec $ns1 ./pm_nl_ctl get 4" "" "duplicate addr"
+
+ip netns exec $ns1 ./pm_nl_ctl add 10.0.1.4 id 10 flags signal
+check "ip netns exec $ns1 ./pm_nl_ctl get 4" "id 4 flags signal 10.0.1.4 " "id addr increment"
+
+for i in `seq 5 9`; do
+       ip netns exec $ns1 ./pm_nl_ctl add 10.0.1.$i flags signal >/dev/null 2>&1
+done
+check "ip netns exec $ns1 ./pm_nl_ctl get 9" "id 9 flags signal 10.0.1.9 " "hard addr limit"
+check "ip netns exec $ns1 ./pm_nl_ctl get 10" "" "above hard addr limit"
+
+for i in `seq 9 256`; do
+       ip netns exec $ns1 ./pm_nl_ctl del $i
+       ip netns exec $ns1 ./pm_nl_ctl add 10.0.0.9
+done
+check "ip netns exec $ns1 ./pm_nl_ctl dump" "id 1 flags  10.0.1.1
+id 3 flags signal,backup 10.0.1.3
+id 4 flags signal 10.0.1.4
+id 5 flags signal 10.0.1.5
+id 6 flags signal 10.0.1.6
+id 7 flags signal 10.0.1.7
+id 8 flags signal 10.0.1.8 " "id limit"
+
+ip netns exec $ns1 ./pm_nl_ctl flush
+check "ip netns exec $ns1 ./pm_nl_ctl dump" "" "flush addrs"
+
+ip netns exec $ns1 ./pm_nl_ctl limits 9 1
+check "ip netns exec $ns1 ./pm_nl_ctl limits" "accept 0
+subflows 0" "rcv addrs above hard limit"
+
+ip netns exec $ns1 ./pm_nl_ctl limits 1 9
+check "ip netns exec $ns1 ./pm_nl_ctl limits" "accept 0
+subflows 0" "subflows above hard limit"
+
+ip netns exec $ns1 ./pm_nl_ctl limits 8 8
+check "ip netns exec $ns1 ./pm_nl_ctl limits" "accept 8
+subflows 8" "set limits"
+
+exit $ret
diff --git a/tools/testing/selftests/net/mptcp/pm_nl_ctl.c b/tools/testing/selftests/net/mptcp/pm_nl_ctl.c
new file mode 100644 (file)
index 0000000..de92093
--- /dev/null
@@ -0,0 +1,616 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <errno.h>
+#include <error.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <arpa/inet.h>
+#include <net/if.h>
+
+#include <linux/rtnetlink.h>
+#include <linux/genetlink.h>
+
+#include "linux/mptcp.h"
+
+#ifndef MPTCP_PM_NAME
+#define MPTCP_PM_NAME          "mptcp_pm"
+#endif
+
+static void syntax(char *argv[])
+{
+       fprintf(stderr, "%s add|get|del|flush|dump|accept [<args>]\n", argv[0]);
+       fprintf(stderr, "\tadd [flags signal|subflow|backup] [id <nr>] [dev <name>] <ip>\n");
+       fprintf(stderr, "\tdel <id>\n");
+       fprintf(stderr, "\tget <id>\n");
+       fprintf(stderr, "\tflush\n");
+       fprintf(stderr, "\tdump\n");
+       fprintf(stderr, "\tlimits [<rcv addr max> <subflow max>]\n");
+       exit(0);
+}
+
+static int init_genl_req(char *data, int family, int cmd, int version)
+{
+       struct nlmsghdr *nh = (void *)data;
+       struct genlmsghdr *gh;
+       int off = 0;
+
+       nh->nlmsg_type = family;
+       nh->nlmsg_flags = NLM_F_REQUEST;
+       nh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
+       off += NLMSG_ALIGN(sizeof(*nh));
+
+       gh = (void *)(data + off);
+       gh->cmd = cmd;
+       gh->version = version;
+       off += NLMSG_ALIGN(sizeof(*gh));
+       return off;
+}
+
+static void nl_error(struct nlmsghdr *nh)
+{
+       struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nh);
+       int len = nh->nlmsg_len - sizeof(*nh);
+       uint32_t off;
+
+       if (len < sizeof(struct nlmsgerr))
+               error(1, 0, "netlink error message truncated %d min %ld", len,
+                     sizeof(struct nlmsgerr));
+
+       if (!err->error) {
+               /* check messages from kernel */
+               struct rtattr *attrs = (struct rtattr *)NLMSG_DATA(nh);
+
+               while (RTA_OK(attrs, len)) {
+                       if (attrs->rta_type == NLMSGERR_ATTR_MSG)
+                               fprintf(stderr, "netlink ext ack msg: %s\n",
+                                       (char *)RTA_DATA(attrs));
+                       if (attrs->rta_type == NLMSGERR_ATTR_OFFS) {
+                               memcpy(&off, RTA_DATA(attrs), 4);
+                               fprintf(stderr, "netlink err off %d\n",
+                                       (int)off);
+                       }
+                       attrs = RTA_NEXT(attrs, len);
+               }
+       } else {
+               fprintf(stderr, "netlink error %d", err->error);
+       }
+}
+
+/* do a netlink command and, if max > 0, fetch the reply  */
+static int do_nl_req(int fd, struct nlmsghdr *nh, int len, int max)
+{
+       struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK };
+       socklen_t addr_len;
+       void *data = nh;
+       int rem, ret;
+       int err = 0;
+
+       nh->nlmsg_len = len;
+       ret = sendto(fd, data, len, 0, (void *)&nladdr, sizeof(nladdr));
+       if (ret != len)
+               error(1, errno, "send netlink: %uB != %uB\n", ret, len);
+       if (max == 0)
+               return 0;
+
+       addr_len = sizeof(nladdr);
+       rem = ret = recvfrom(fd, data, max, 0, (void *)&nladdr, &addr_len);
+       if (ret < 0)
+               error(1, errno, "recv netlink: %uB\n", ret);
+
+       /* Beware: the NLMSG_NEXT macro updates the 'rem' argument */
+       for (; NLMSG_OK(nh, rem); nh = NLMSG_NEXT(nh, rem)) {
+               if (nh->nlmsg_type == NLMSG_ERROR) {
+                       nl_error(nh);
+                       err = 1;
+               }
+       }
+       if (err)
+               error(1, 0, "bailing out due to netlink error[s]");
+       return ret;
+}
+
+static int genl_parse_getfamily(struct nlmsghdr *nlh)
+{
+       struct genlmsghdr *ghdr = NLMSG_DATA(nlh);
+       int len = nlh->nlmsg_len;
+       struct rtattr *attrs;
+
+       if (nlh->nlmsg_type != GENL_ID_CTRL)
+               error(1, errno, "Not a controller message, len=%d type=0x%x\n",
+                     nlh->nlmsg_len, nlh->nlmsg_type);
+
+       len -= NLMSG_LENGTH(GENL_HDRLEN);
+
+       if (len < 0)
+               error(1, errno, "wrong controller message len %d\n", len);
+
+       if (ghdr->cmd != CTRL_CMD_NEWFAMILY)
+               error(1, errno, "Unknown controller command %d\n", ghdr->cmd);
+
+       attrs = (struct rtattr *) ((char *) ghdr + GENL_HDRLEN);
+       while (RTA_OK(attrs, len)) {
+               if (attrs->rta_type == CTRL_ATTR_FAMILY_ID)
+                       return *(__u16 *)RTA_DATA(attrs);
+               attrs = RTA_NEXT(attrs, len);
+       }
+
+       error(1, errno, "can't find CTRL_ATTR_FAMILY_ID attr");
+       return -1;
+}
+
+static int resolve_mptcp_pm_netlink(int fd)
+{
+       char data[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
+                 NLMSG_ALIGN(sizeof(struct genlmsghdr)) +
+                 1024];
+       struct nlmsghdr *nh;
+       struct rtattr *rta;
+       int namelen;
+       int off = 0;
+
+       memset(data, 0, sizeof(data));
+       nh = (void *)data;
+       off = init_genl_req(data, GENL_ID_CTRL, CTRL_CMD_GETFAMILY, 0);
+
+       rta = (void *)(data + off);
+       namelen = strlen(MPTCP_PM_NAME) + 1;
+       rta->rta_type = CTRL_ATTR_FAMILY_NAME;
+       rta->rta_len = RTA_LENGTH(namelen);
+       memcpy(RTA_DATA(rta), MPTCP_PM_NAME, namelen);
+       off += NLMSG_ALIGN(rta->rta_len);
+
+       do_nl_req(fd, nh, off, sizeof(data));
+       return genl_parse_getfamily((void *)data);
+}
+
+int add_addr(int fd, int pm_family, int argc, char *argv[])
+{
+       char data[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
+                 NLMSG_ALIGN(sizeof(struct genlmsghdr)) +
+                 1024];
+       struct rtattr *rta, *nest;
+       struct nlmsghdr *nh;
+       u_int16_t family;
+       u_int32_t flags;
+       int nest_start;
+       u_int8_t id;
+       int off = 0;
+       int arg;
+
+       memset(data, 0, sizeof(data));
+       nh = (void *)data;
+       off = init_genl_req(data, pm_family, MPTCP_PM_CMD_ADD_ADDR,
+                           MPTCP_PM_VER);
+
+       if (argc < 3)
+               syntax(argv);
+
+       nest_start = off;
+       nest = (void *)(data + off);
+       nest->rta_type = NLA_F_NESTED | MPTCP_PM_ATTR_ADDR;
+       nest->rta_len = RTA_LENGTH(0);
+       off += NLMSG_ALIGN(nest->rta_len);
+
+       /* addr data */
+       rta = (void *)(data + off);
+       if (inet_pton(AF_INET, argv[2], RTA_DATA(rta))) {
+               family = AF_INET;
+               rta->rta_type = MPTCP_PM_ADDR_ATTR_ADDR4;
+               rta->rta_len = RTA_LENGTH(4);
+       } else if (inet_pton(AF_INET6, argv[2], RTA_DATA(rta))) {
+               family = AF_INET6;
+               rta->rta_type = MPTCP_PM_ADDR_ATTR_ADDR6;
+               rta->rta_len = RTA_LENGTH(16);
+       } else
+               error(1, errno, "can't parse ip %s", argv[2]);
+       off += NLMSG_ALIGN(rta->rta_len);
+
+       /* family */
+       rta = (void *)(data + off);
+       rta->rta_type = MPTCP_PM_ADDR_ATTR_FAMILY;
+       rta->rta_len = RTA_LENGTH(2);
+       memcpy(RTA_DATA(rta), &family, 2);
+       off += NLMSG_ALIGN(rta->rta_len);
+
+       for (arg = 3; arg < argc; arg++) {
+               if (!strcmp(argv[arg], "flags")) {
+                       char *tok, *str;
+
+                       /* flags */
+                       flags = 0;
+                       if (++arg >= argc)
+                               error(1, 0, " missing flags value");
+
+                       /* do not support flag list yet */
+                       for (str = argv[arg]; (tok = strtok(str, ","));
+                            str = NULL) {
+                               if (!strcmp(tok, "subflow"))
+                                       flags |= MPTCP_PM_ADDR_FLAG_SUBFLOW;
+                               else if (!strcmp(tok, "signal"))
+                                       flags |= MPTCP_PM_ADDR_FLAG_SIGNAL;
+                               else if (!strcmp(tok, "backup"))
+                                       flags |= MPTCP_PM_ADDR_FLAG_BACKUP;
+                               else
+                                       error(1, errno,
+                                             "unknown flag %s", argv[arg]);
+                       }
+
+                       rta = (void *)(data + off);
+                       rta->rta_type = MPTCP_PM_ADDR_ATTR_FLAGS;
+                       rta->rta_len = RTA_LENGTH(4);
+                       memcpy(RTA_DATA(rta), &flags, 4);
+                       off += NLMSG_ALIGN(rta->rta_len);
+               } else if (!strcmp(argv[arg], "id")) {
+                       if (++arg >= argc)
+                               error(1, 0, " missing id value");
+
+                       id = atoi(argv[arg]);
+                       rta = (void *)(data + off);
+                       rta->rta_type = MPTCP_PM_ADDR_ATTR_ID;
+                       rta->rta_len = RTA_LENGTH(1);
+                       memcpy(RTA_DATA(rta), &id, 1);
+                       off += NLMSG_ALIGN(rta->rta_len);
+               } else if (!strcmp(argv[arg], "dev")) {
+                       int32_t ifindex;
+
+                       if (++arg >= argc)
+                               error(1, 0, " missing dev name");
+
+                       ifindex = if_nametoindex(argv[arg]);
+                       if (!ifindex)
+                               error(1, errno, "unknown device %s", argv[arg]);
+
+                       rta = (void *)(data + off);
+                       rta->rta_type = MPTCP_PM_ADDR_ATTR_IF_IDX;
+                       rta->rta_len = RTA_LENGTH(4);
+                       memcpy(RTA_DATA(rta), &ifindex, 4);
+                       off += NLMSG_ALIGN(rta->rta_len);
+               } else
+                       error(1, 0, "unknown keyword %s", argv[arg]);
+       }
+       nest->rta_len = off - nest_start;
+
+       do_nl_req(fd, nh, off, 0);
+       return 0;
+}
+
+int del_addr(int fd, int pm_family, int argc, char *argv[])
+{
+       char data[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
+                 NLMSG_ALIGN(sizeof(struct genlmsghdr)) +
+                 1024];
+       struct rtattr *rta, *nest;
+       struct nlmsghdr *nh;
+       int nest_start;
+       u_int8_t id;
+       int off = 0;
+
+       memset(data, 0, sizeof(data));
+       nh = (void *)data;
+       off = init_genl_req(data, pm_family, MPTCP_PM_CMD_DEL_ADDR,
+                           MPTCP_PM_VER);
+
+       /* the only argument is the address id */
+       if (argc != 3)
+               syntax(argv);
+
+       id = atoi(argv[2]);
+
+       nest_start = off;
+       nest = (void *)(data + off);
+       nest->rta_type = NLA_F_NESTED | MPTCP_PM_ATTR_ADDR;
+       nest->rta_len =  RTA_LENGTH(0);
+       off += NLMSG_ALIGN(nest->rta_len);
+
+       /* build a dummy addr with only the ID set */
+       rta = (void *)(data + off);
+       rta->rta_type = MPTCP_PM_ADDR_ATTR_ID;
+       rta->rta_len = RTA_LENGTH(1);
+       memcpy(RTA_DATA(rta), &id, 1);
+       off += NLMSG_ALIGN(rta->rta_len);
+       nest->rta_len = off - nest_start;
+
+       do_nl_req(fd, nh, off, 0);
+       return 0;
+}
+
+static void print_addr(struct rtattr *attrs, int len)
+{
+       uint16_t family = 0;
+       char str[1024];
+       uint32_t flags;
+       uint8_t id;
+
+       while (RTA_OK(attrs, len)) {
+               if (attrs->rta_type == MPTCP_PM_ADDR_ATTR_FAMILY)
+                       memcpy(&family, RTA_DATA(attrs), 2);
+               if (attrs->rta_type == MPTCP_PM_ADDR_ATTR_ADDR4) {
+                       if (family != AF_INET)
+                               error(1, errno, "wrong IP (v4) for family %d",
+                                     family);
+                       inet_ntop(AF_INET, RTA_DATA(attrs), str, sizeof(str));
+                       printf("%s ", str);
+               }
+               if (attrs->rta_type == MPTCP_PM_ADDR_ATTR_ADDR6) {
+                       if (family != AF_INET6)
+                               error(1, errno, "wrong IP (v6) for family %d",
+                                     family);
+                       inet_ntop(AF_INET6, RTA_DATA(attrs), str, sizeof(str));
+                       printf("%s ", str);
+               }
+               if (attrs->rta_type == MPTCP_PM_ADDR_ATTR_ID) {
+                       memcpy(&id, RTA_DATA(attrs), 1);
+                       printf("id %d ", id);
+               }
+               if (attrs->rta_type == MPTCP_PM_ADDR_ATTR_FLAGS) {
+                       memcpy(&flags, RTA_DATA(attrs), 4);
+
+                       printf("flags ");
+                       if (flags & MPTCP_PM_ADDR_FLAG_SIGNAL) {
+                               printf("signal");
+                               flags &= ~MPTCP_PM_ADDR_FLAG_SIGNAL;
+                               if (flags)
+                                       printf(",");
+                       }
+
+                       if (flags & MPTCP_PM_ADDR_FLAG_SUBFLOW) {
+                               printf("subflow");
+                               flags &= ~MPTCP_PM_ADDR_FLAG_SUBFLOW;
+                               if (flags)
+                                       printf(",");
+                       }
+
+                       if (flags & MPTCP_PM_ADDR_FLAG_BACKUP) {
+                               printf("backup");
+                               flags &= ~MPTCP_PM_ADDR_FLAG_BACKUP;
+                               if (flags)
+                                       printf(",");
+                       }
+
+                       /* bump unknown flags, if any */
+                       if (flags)
+                               printf("0x%x", flags);
+                       printf(" ");
+               }
+               if (attrs->rta_type == MPTCP_PM_ADDR_ATTR_IF_IDX) {
+                       char name[IF_NAMESIZE], *ret;
+                       int32_t ifindex;
+
+                       memcpy(&ifindex, RTA_DATA(attrs), 4);
+                       ret = if_indextoname(ifindex, name);
+                       if (ret)
+                               printf("dev %s ", ret);
+                       else
+                               printf("dev unknown/%d", ifindex);
+               }
+
+               attrs = RTA_NEXT(attrs, len);
+       }
+       printf("\n");
+}
+
+static void print_addrs(struct nlmsghdr *nh, int pm_family, int total_len)
+{
+       struct rtattr *attrs;
+
+       for (; NLMSG_OK(nh, total_len); nh = NLMSG_NEXT(nh, total_len)) {
+               int len = nh->nlmsg_len;
+
+               if (nh->nlmsg_type == NLMSG_DONE)
+                       break;
+               if (nh->nlmsg_type == NLMSG_ERROR)
+                       nl_error(nh);
+               if (nh->nlmsg_type != pm_family)
+                       continue;
+
+               len -= NLMSG_LENGTH(GENL_HDRLEN);
+               attrs = (struct rtattr *) ((char *) NLMSG_DATA(nh) +
+                                          GENL_HDRLEN);
+               while (RTA_OK(attrs, len)) {
+                       if (attrs->rta_type ==
+                           (MPTCP_PM_ATTR_ADDR | NLA_F_NESTED))
+                               print_addr((void *)RTA_DATA(attrs),
+                                          attrs->rta_len);
+                       attrs = RTA_NEXT(attrs, len);
+               }
+       }
+}
+
+int get_addr(int fd, int pm_family, int argc, char *argv[])
+{
+       char data[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
+                 NLMSG_ALIGN(sizeof(struct genlmsghdr)) +
+                 1024];
+       struct rtattr *rta, *nest;
+       struct nlmsghdr *nh;
+       int nest_start;
+       u_int8_t id;
+       int off = 0;
+
+       memset(data, 0, sizeof(data));
+       nh = (void *)data;
+       off = init_genl_req(data, pm_family, MPTCP_PM_CMD_GET_ADDR,
+                           MPTCP_PM_VER);
+
+       /* the only argument is the address id */
+       if (argc != 3)
+               syntax(argv);
+
+       id = atoi(argv[2]);
+
+       nest_start = off;
+       nest = (void *)(data + off);
+       nest->rta_type = NLA_F_NESTED | MPTCP_PM_ATTR_ADDR;
+       nest->rta_len =  RTA_LENGTH(0);
+       off += NLMSG_ALIGN(nest->rta_len);
+
+       /* build a dummy addr with only the ID set */
+       rta = (void *)(data + off);
+       rta->rta_type = MPTCP_PM_ADDR_ATTR_ID;
+       rta->rta_len = RTA_LENGTH(1);
+       memcpy(RTA_DATA(rta), &id, 1);
+       off += NLMSG_ALIGN(rta->rta_len);
+       nest->rta_len = off - nest_start;
+
+       print_addrs(nh, pm_family, do_nl_req(fd, nh, off, sizeof(data)));
+       return 0;
+}
+
+int dump_addrs(int fd, int pm_family, int argc, char *argv[])
+{
+       char data[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
+                 NLMSG_ALIGN(sizeof(struct genlmsghdr)) +
+                 1024];
+       pid_t pid = getpid();
+       struct nlmsghdr *nh;
+       int off = 0;
+
+       memset(data, 0, sizeof(data));
+       nh = (void *)data;
+       off = init_genl_req(data, pm_family, MPTCP_PM_CMD_GET_ADDR,
+                           MPTCP_PM_VER);
+       nh->nlmsg_flags |= NLM_F_DUMP;
+       nh->nlmsg_seq = 1;
+       nh->nlmsg_pid = pid;
+       nh->nlmsg_len = off;
+
+       print_addrs(nh, pm_family, do_nl_req(fd, nh, off, sizeof(data)));
+       return 0;
+}
+
+int flush_addrs(int fd, int pm_family, int argc, char *argv[])
+{
+       char data[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
+                 NLMSG_ALIGN(sizeof(struct genlmsghdr)) +
+                 1024];
+       struct nlmsghdr *nh;
+       int off = 0;
+
+       memset(data, 0, sizeof(data));
+       nh = (void *)data;
+       off = init_genl_req(data, pm_family, MPTCP_PM_CMD_FLUSH_ADDRS,
+                           MPTCP_PM_VER);
+
+       do_nl_req(fd, nh, off, 0);
+       return 0;
+}
+
+static void print_limits(struct nlmsghdr *nh, int pm_family, int total_len)
+{
+       struct rtattr *attrs;
+       uint32_t max;
+
+       for (; NLMSG_OK(nh, total_len); nh = NLMSG_NEXT(nh, total_len)) {
+               int len = nh->nlmsg_len;
+
+               if (nh->nlmsg_type == NLMSG_DONE)
+                       break;
+               if (nh->nlmsg_type == NLMSG_ERROR)
+                       nl_error(nh);
+               if (nh->nlmsg_type != pm_family)
+                       continue;
+
+               len -= NLMSG_LENGTH(GENL_HDRLEN);
+               attrs = (struct rtattr *) ((char *) NLMSG_DATA(nh) +
+                                          GENL_HDRLEN);
+               while (RTA_OK(attrs, len)) {
+                       int type = attrs->rta_type;
+
+                       if (type != MPTCP_PM_ATTR_RCV_ADD_ADDRS &&
+                           type != MPTCP_PM_ATTR_SUBFLOWS)
+                               goto next;
+
+                       memcpy(&max, RTA_DATA(attrs), 4);
+                       printf("%s %u\n", type == MPTCP_PM_ATTR_SUBFLOWS ?
+                                         "subflows" : "accept", max);
+
+next:
+                       attrs = RTA_NEXT(attrs, len);
+               }
+       }
+}
+
+int get_set_limits(int fd, int pm_family, int argc, char *argv[])
+{
+       char data[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
+                 NLMSG_ALIGN(sizeof(struct genlmsghdr)) +
+                 1024];
+       uint32_t rcv_addr = 0, subflows = 0;
+       int cmd, len = sizeof(data);
+       struct nlmsghdr *nh;
+       int off = 0;
+
+       /* limit */
+       if (argc == 4) {
+               rcv_addr = atoi(argv[2]);
+               subflows = atoi(argv[3]);
+               cmd = MPTCP_PM_CMD_SET_LIMITS;
+       } else {
+               cmd = MPTCP_PM_CMD_GET_LIMITS;
+       }
+
+       memset(data, 0, sizeof(data));
+       nh = (void *)data;
+       off = init_genl_req(data, pm_family, cmd, MPTCP_PM_VER);
+
+       /* limit */
+       if (cmd == MPTCP_PM_CMD_SET_LIMITS) {
+               struct rtattr *rta = (void *)(data + off);
+
+               rta->rta_type = MPTCP_PM_ATTR_RCV_ADD_ADDRS;
+               rta->rta_len = RTA_LENGTH(4);
+               memcpy(RTA_DATA(rta), &rcv_addr, 4);
+               off += NLMSG_ALIGN(rta->rta_len);
+
+               rta = (void *)(data + off);
+               rta->rta_type = MPTCP_PM_ATTR_SUBFLOWS;
+               rta->rta_len = RTA_LENGTH(4);
+               memcpy(RTA_DATA(rta), &subflows, 4);
+               off += NLMSG_ALIGN(rta->rta_len);
+
+               /* do not expect a reply */
+               len = 0;
+       }
+
+       len = do_nl_req(fd, nh, off, len);
+       if (cmd == MPTCP_PM_CMD_GET_LIMITS)
+               print_limits(nh, pm_family, len);
+       return 0;
+}
+
+int main(int argc, char *argv[])
+{
+       int fd, pm_family;
+
+       if (argc < 2)
+               syntax(argv);
+
+       fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
+       if (fd == -1)
+               error(1, errno, "socket netlink");
+
+       pm_family = resolve_mptcp_pm_netlink(fd);
+
+       if (!strcmp(argv[1], "add"))
+               return add_addr(fd, pm_family, argc, argv);
+       else if (!strcmp(argv[1], "del"))
+               return del_addr(fd, pm_family, argc, argv);
+       else if (!strcmp(argv[1], "flush"))
+               return flush_addrs(fd, pm_family, argc, argv);
+       else if (!strcmp(argv[1], "get"))
+               return get_addr(fd, pm_family, argc, argv);
+       else if (!strcmp(argv[1], "dump"))
+               return dump_addrs(fd, pm_family, argc, argv);
+       else if (!strcmp(argv[1], "limits"))
+               return get_set_limits(fd, pm_family, argc, argv);
+
+       fprintf(stderr, "unknown sub-command: %s", argv[1]);
+       syntax(argv);
+       return 0;
+}