naccyde / rpms / iproute

Forked from rpms/iproute 5 months ago
Clone

Blame SOURCES/0017-add-support-for-mptcp-netlink-interface.patch

359b1d
From 1f25184a76227f8a7a1e425434e6e0f0bd13457d Mon Sep 17 00:00:00 2001
359b1d
From: Andrea Claudi <aclaudi@redhat.com>
359b1d
Date: Thu, 4 Jun 2020 19:26:50 +0200
359b1d
Subject: [PATCH] add support for mptcp netlink interface
359b1d
359b1d
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1812207
359b1d
Upstream Status: unknown commit 7e0767cd862bb
359b1d
359b1d
commit 7e0767cd862bb5dd2d41c41c5e6f55d633f953ea
359b1d
Author: Paolo Abeni <pabeni@redhat.com>
359b1d
Date:   Thu Apr 23 15:37:08 2020 +0200
359b1d
359b1d
    add support for mptcp netlink interface
359b1d
359b1d
    Implement basic commands to:
359b1d
    - manipulate MPTCP endpoints list
359b1d
    - manipulate MPTCP connection limits
359b1d
359b1d
    Examples:
359b1d
    1. Allows multiple subflows per MPTCP connection
359b1d
       $ ip mptcp limits set subflows 2
359b1d
359b1d
    2. Accept ADD_ADDR announcement from the peer (server):
359b1d
       $ ip mptcp limits set add_addr_accepted 2
359b1d
359b1d
    3. Add a ipv4 address to be annunced for backup subflows:
359b1d
       $ ip mptcp endpoint add 10.99.1.2 signal backup
359b1d
359b1d
    4. Add an ipv6 address used as source for additional subflows:
359b1d
       $ ip mptcp endpoint add 2001::2 subflow
359b1d
359b1d
    Signed-off-by: Paolo Abeni <pabeni@redhat.com>
359b1d
    Signed-off-by: David Ahern <dsahern@gmail.com>
359b1d
---
359b1d
 ip/Makefile    |   2 +-
359b1d
 ip/ip.c        |   3 +-
359b1d
 ip/ip_common.h |   1 +
359b1d
 ip/ipmptcp.c   | 436 +++++++++++++++++++++++++++++++++++++++++++++++++
359b1d
 4 files changed, 440 insertions(+), 2 deletions(-)
359b1d
 create mode 100644 ip/ipmptcp.c
359b1d
359b1d
diff --git a/ip/Makefile b/ip/Makefile
359b1d
index 5ab78d7d3b84e..8735b8e4706b3 100644
359b1d
--- a/ip/Makefile
359b1d
+++ b/ip/Makefile
359b1d
@@ -11,7 +11,7 @@ IPOBJ=ip.o ipaddress.o ipaddrlabel.o iproute.o iprule.o ipnetns.o \
359b1d
     iplink_bridge.o iplink_bridge_slave.o ipfou.o iplink_ipvlan.o \
359b1d
     iplink_geneve.o iplink_vrf.o iproute_lwtunnel.o ipmacsec.o ipila.o \
359b1d
     ipvrf.o iplink_xstats.o ipseg6.o iplink_netdevsim.o iplink_rmnet.o \
359b1d
-    ipnexthop.o
359b1d
+    ipnexthop.o ipmptcp.o
359b1d
 
359b1d
 RTMONOBJ=rtmon.o
359b1d
 
359b1d
diff --git a/ip/ip.c b/ip/ip.c
359b1d
index fed26f8d48279..8d62f4e312bdc 100644
359b1d
--- a/ip/ip.c
359b1d
+++ b/ip/ip.c
359b1d
@@ -51,7 +51,7 @@ static void usage(void)
359b1d
 		"where  OBJECT := { link | address | addrlabel | route | rule | neigh | ntable |\n"
359b1d
 		"                   tunnel | tuntap | maddress | mroute | mrule | monitor | xfrm |\n"
359b1d
 		"                   netns | l2tp | fou | macsec | tcp_metrics | token | netconf | ila |\n"
359b1d
-		"                   vrf | sr | nexthop }\n"
359b1d
+		"                   vrf | sr | nexthop | mptcp }\n"
359b1d
 		"       OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |\n"
359b1d
 		"                    -h[uman-readable] | -iec | -j[son] | -p[retty] |\n"
359b1d
 		"                    -f[amily] { inet | inet6 | mpls | bridge | link } |\n"
359b1d
@@ -103,6 +103,7 @@ static const struct cmd {
359b1d
 	{ "vrf",	do_ipvrf},
359b1d
 	{ "sr",		do_seg6 },
359b1d
 	{ "nexthop",	do_ipnh },
359b1d
+	{ "mptcp",	do_mptcp },
359b1d
 	{ "help",	do_help },
359b1d
 	{ 0 }
359b1d
 };
359b1d
diff --git a/ip/ip_common.h b/ip/ip_common.h
359b1d
index cd916ec87c265..0dd4a53fc8333 100644
359b1d
--- a/ip/ip_common.h
359b1d
+++ b/ip/ip_common.h
359b1d
@@ -82,6 +82,7 @@ void vrf_reset(void);
359b1d
 int netns_identify_pid(const char *pidstr, char *name, int len);
359b1d
 int do_seg6(int argc, char **argv);
359b1d
 int do_ipnh(int argc, char **argv);
359b1d
+int do_mptcp(int argc, char **argv);
359b1d
 
359b1d
 int iplink_get(char *name, __u32 filt_mask);
359b1d
 int iplink_ifla_xstats(int argc, char **argv);
359b1d
diff --git a/ip/ipmptcp.c b/ip/ipmptcp.c
359b1d
new file mode 100644
359b1d
index 0000000000000..bc12418bd39c6
359b1d
--- /dev/null
359b1d
+++ b/ip/ipmptcp.c
359b1d
@@ -0,0 +1,436 @@
359b1d
+// SPDX-License-Identifier: GPL-2.0
359b1d
+
359b1d
+#include <stdio.h>
359b1d
+#include <string.h>
359b1d
+#include <rt_names.h>
359b1d
+#include <errno.h>
359b1d
+
359b1d
+#include <linux/genetlink.h>
359b1d
+#include <linux/mptcp.h>
359b1d
+
359b1d
+#include "utils.h"
359b1d
+#include "ip_common.h"
359b1d
+#include "libgenl.h"
359b1d
+#include "json_print.h"
359b1d
+
359b1d
+static void usage(void)
359b1d
+{
359b1d
+	fprintf(stderr,
359b1d
+		"Usage:	ip mptcp endpoint add ADDRESS [ dev NAME ] [ id ID ]\n"
359b1d
+		"				      [ FLAG-LIST ]\n"
359b1d
+		"	ip mptcp endpoint delete id ID\n"
359b1d
+		"	ip mptcp endpoint show [ id ID ]\n"
359b1d
+		"	ip mptcp endpoint flush\n"
359b1d
+		"	ip mptcp limits set [ subflows NR ] [ add_addr_accepted NR ]\n"
359b1d
+		"	ip mptcp limits show\n"
359b1d
+		"FLAG-LIST := [ FLAG-LIST ] FLAG\n"
359b1d
+		"FLAG  := [ signal | subflow | backup ]\n");
359b1d
+
359b1d
+	exit(-1);
359b1d
+}
359b1d
+
359b1d
+/* netlink socket */
359b1d
+static struct rtnl_handle genl_rth = { .fd = -1 };
359b1d
+static int genl_family = -1;
359b1d
+
359b1d
+#define MPTCP_BUFLEN	4096
359b1d
+#define MPTCP_REQUEST(_req,  _cmd, _flags)	\
359b1d
+	GENL_REQUEST(_req, MPTCP_BUFLEN, genl_family, 0,	\
359b1d
+		     MPTCP_PM_VER, _cmd, _flags)
359b1d
+
359b1d
+/* Mapping from argument to address flag mask */
359b1d
+static const struct {
359b1d
+	const char *name;
359b1d
+	unsigned long value;
359b1d
+} mptcp_addr_flag_names[] = {
359b1d
+	{ "signal",		MPTCP_PM_ADDR_FLAG_SIGNAL },
359b1d
+	{ "subflow",		MPTCP_PM_ADDR_FLAG_SUBFLOW },
359b1d
+	{ "backup",		MPTCP_PM_ADDR_FLAG_BACKUP },
359b1d
+};
359b1d
+
359b1d
+static void print_mptcp_addr_flags(unsigned int flags)
359b1d
+{
359b1d
+	unsigned int i;
359b1d
+
359b1d
+	for (i = 0; i < ARRAY_SIZE(mptcp_addr_flag_names); i++) {
359b1d
+		unsigned long mask = mptcp_addr_flag_names[i].value;
359b1d
+
359b1d
+		if (flags & mask) {
359b1d
+			print_string(PRINT_FP, NULL, "%s ",
359b1d
+				     mptcp_addr_flag_names[i].name);
359b1d
+			print_bool(PRINT_JSON,
359b1d
+				   mptcp_addr_flag_names[i].name, NULL, true);
359b1d
+		}
359b1d
+
359b1d
+		flags &= ~mask;
359b1d
+	}
359b1d
+
359b1d
+	if (flags) {
359b1d
+		/* unknown flags */
359b1d
+		SPRINT_BUF(b1);
359b1d
+
359b1d
+		snprintf(b1, sizeof(b1), "%02x", flags);
359b1d
+		print_string(PRINT_ANY, "rawflags", "rawflags %s ", b1);
359b1d
+	}
359b1d
+}
359b1d
+
359b1d
+static int get_flags(const char *arg, __u32 *flags)
359b1d
+{
359b1d
+	unsigned int i;
359b1d
+
359b1d
+	for (i = 0; i < ARRAY_SIZE(mptcp_addr_flag_names); i++) {
359b1d
+		if (strcmp(arg, mptcp_addr_flag_names[i].name))
359b1d
+			continue;
359b1d
+
359b1d
+		*flags |= mptcp_addr_flag_names[i].value;
359b1d
+		return 0;
359b1d
+	}
359b1d
+	return -1;
359b1d
+}
359b1d
+
359b1d
+static int mptcp_parse_opt(int argc, char **argv, struct nlmsghdr *n,
359b1d
+			 bool adding)
359b1d
+{
359b1d
+	struct rtattr *attr_addr;
359b1d
+	bool addr_set = false;
359b1d
+	inet_prefix address;
359b1d
+	bool id_set = false;
359b1d
+	__u32 index = 0;
359b1d
+	__u32 flags = 0;
359b1d
+	__u8 id = 0;
359b1d
+
359b1d
+	ll_init_map(&rth);
359b1d
+	while (argc > 0) {
359b1d
+		if (get_flags(*argv, &flags) == 0) {
359b1d
+		} else if (matches(*argv, "id") == 0) {
359b1d
+			NEXT_ARG();
359b1d
+
359b1d
+			if (get_u8(&id, *argv, 0))
359b1d
+				invarg("invalid ID\n", *argv);
359b1d
+			id_set = true;
359b1d
+		} else if (matches(*argv, "dev") == 0) {
359b1d
+			const char *ifname;
359b1d
+
359b1d
+			NEXT_ARG();
359b1d
+
359b1d
+			ifname = *argv;
359b1d
+
359b1d
+			if (check_ifname(ifname))
359b1d
+				invarg("invalid interface name\n", ifname);
359b1d
+
359b1d
+			index = ll_name_to_index(ifname);
359b1d
+
359b1d
+			if (!index)
359b1d
+				invarg("device does not exist\n", ifname);
359b1d
+
359b1d
+		} else if (get_addr(&address, *argv, AF_UNSPEC) == 0) {
359b1d
+			addr_set = true;
359b1d
+		} else {
359b1d
+			invarg("unknown argument", *argv);
359b1d
+		}
359b1d
+		NEXT_ARG_FWD();
359b1d
+	}
359b1d
+
359b1d
+	if (!addr_set && adding)
359b1d
+		missarg("ADDRESS");
359b1d
+
359b1d
+	if (!id_set && !adding)
359b1d
+		missarg("ID");
359b1d
+
359b1d
+	attr_addr = addattr_nest(n, MPTCP_BUFLEN,
359b1d
+				 MPTCP_PM_ATTR_ADDR | NLA_F_NESTED);
359b1d
+	if (id_set)
359b1d
+		addattr8(n, MPTCP_BUFLEN, MPTCP_PM_ADDR_ATTR_ID, id);
359b1d
+	if (flags)
359b1d
+		addattr32(n, MPTCP_BUFLEN, MPTCP_PM_ADDR_ATTR_FLAGS, flags);
359b1d
+	if (index)
359b1d
+		addattr32(n, MPTCP_BUFLEN, MPTCP_PM_ADDR_ATTR_IF_IDX, index);
359b1d
+	if (addr_set) {
359b1d
+		int type;
359b1d
+
359b1d
+		addattr16(n, MPTCP_BUFLEN, MPTCP_PM_ADDR_ATTR_FAMILY,
359b1d
+			  address.family);
359b1d
+		type = address.family == AF_INET ? MPTCP_PM_ADDR_ATTR_ADDR4 :
359b1d
+						   MPTCP_PM_ADDR_ATTR_ADDR6;
359b1d
+		addattr_l(n, MPTCP_BUFLEN, type, &address.data,
359b1d
+			  address.bytelen);
359b1d
+	}
359b1d
+
359b1d
+	addattr_nest_end(n, attr_addr);
359b1d
+	return 0;
359b1d
+}
359b1d
+
359b1d
+static int mptcp_addr_modify(int argc, char **argv, int cmd)
359b1d
+{
359b1d
+	MPTCP_REQUEST(req, cmd, NLM_F_REQUEST);
359b1d
+	int ret;
359b1d
+
359b1d
+	ret = mptcp_parse_opt(argc, argv, &req.n, cmd == MPTCP_PM_CMD_ADD_ADDR);
359b1d
+	if (ret)
359b1d
+		return ret;
359b1d
+
359b1d
+	if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
359b1d
+		return -2;
359b1d
+
359b1d
+	return 0;
359b1d
+}
359b1d
+
359b1d
+static int print_mptcp_addrinfo(struct rtattr *addrinfo)
359b1d
+{
359b1d
+	struct rtattr *tb[MPTCP_PM_ADDR_ATTR_MAX + 1];
359b1d
+	__u8 family = AF_UNSPEC, addr_attr_type;
359b1d
+	const char *ifname;
359b1d
+	unsigned int flags;
359b1d
+	int index;
359b1d
+	__u16 id;
359b1d
+
359b1d
+	parse_rtattr_nested(tb, MPTCP_PM_ADDR_ATTR_MAX, addrinfo);
359b1d
+
359b1d
+	open_json_object(NULL);
359b1d
+	if (tb[MPTCP_PM_ADDR_ATTR_FAMILY])
359b1d
+		family = rta_getattr_u8(tb[MPTCP_PM_ADDR_ATTR_FAMILY]);
359b1d
+
359b1d
+	addr_attr_type = family == AF_INET ? MPTCP_PM_ADDR_ATTR_ADDR4 :
359b1d
+					     MPTCP_PM_ADDR_ATTR_ADDR6;
359b1d
+	if (tb[addr_attr_type]) {
359b1d
+		print_string(PRINT_ANY, "address", "%s ",
359b1d
+			     format_host_rta(family, tb[addr_attr_type]));
359b1d
+	}
359b1d
+	if (tb[MPTCP_PM_ADDR_ATTR_ID]) {
359b1d
+		id = rta_getattr_u8(tb[MPTCP_PM_ADDR_ATTR_ID]);
359b1d
+		print_uint(PRINT_ANY, "id", "id %u ", id);
359b1d
+	}
359b1d
+	if (tb[MPTCP_PM_ADDR_ATTR_FLAGS]) {
359b1d
+		flags = rta_getattr_u32(tb[MPTCP_PM_ADDR_ATTR_FLAGS]);
359b1d
+		print_mptcp_addr_flags(flags);
359b1d
+	}
359b1d
+	if (tb[MPTCP_PM_ADDR_ATTR_IF_IDX]) {
359b1d
+		index = rta_getattr_s32(tb[MPTCP_PM_ADDR_ATTR_IF_IDX]);
359b1d
+		ifname = index ? ll_index_to_name(index) : NULL;
359b1d
+
359b1d
+		if (ifname)
359b1d
+			print_string(PRINT_ANY, "dev", "dev %s ", ifname);
359b1d
+	}
359b1d
+
359b1d
+	close_json_object();
359b1d
+	print_string(PRINT_FP, NULL, "\n", NULL);
359b1d
+	fflush(stdout);
359b1d
+
359b1d
+	return 0;
359b1d
+}
359b1d
+
359b1d
+static int print_mptcp_addr(struct nlmsghdr *n, void *arg)
359b1d
+{
359b1d
+	struct rtattr *tb[MPTCP_PM_ATTR_MAX + 1];
359b1d
+	struct genlmsghdr *ghdr;
359b1d
+	struct rtattr *addrinfo;
359b1d
+	int len = n->nlmsg_len;
359b1d
+
359b1d
+	if (n->nlmsg_type != genl_family)
359b1d
+		return 0;
359b1d
+
359b1d
+	len -= NLMSG_LENGTH(GENL_HDRLEN);
359b1d
+	if (len < 0)
359b1d
+		return -1;
359b1d
+
359b1d
+	ghdr = NLMSG_DATA(n);
359b1d
+	parse_rtattr_flags(tb, MPTCP_PM_ATTR_MAX, (void *) ghdr + GENL_HDRLEN,
359b1d
+			   len, NLA_F_NESTED);
359b1d
+	addrinfo = tb[MPTCP_PM_ATTR_ADDR];
359b1d
+	if (!addrinfo)
359b1d
+		return -1;
359b1d
+
359b1d
+	ll_init_map(&rth);
359b1d
+	return print_mptcp_addrinfo(addrinfo);
359b1d
+}
359b1d
+
359b1d
+static int mptcp_addr_dump(void)
359b1d
+{
359b1d
+	MPTCP_REQUEST(req, MPTCP_PM_CMD_GET_ADDR, NLM_F_REQUEST | NLM_F_DUMP);
359b1d
+
359b1d
+	if (rtnl_send(&genl_rth, &req.n, req.n.nlmsg_len) < 0) {
359b1d
+		perror("Cannot send show request");
359b1d
+		exit(1);
359b1d
+	}
359b1d
+
359b1d
+	new_json_obj(json);
359b1d
+
359b1d
+	if (rtnl_dump_filter(&genl_rth, print_mptcp_addr, stdout) < 0) {
359b1d
+		fprintf(stderr, "Dump terminated\n");
359b1d
+		delete_json_obj();
359b1d
+		fflush(stdout);
359b1d
+		return -2;
359b1d
+	}
359b1d
+
359b1d
+	close_json_object();
359b1d
+	fflush(stdout);
359b1d
+	return 0;
359b1d
+}
359b1d
+
359b1d
+static int mptcp_addr_show(int argc, char **argv)
359b1d
+{
359b1d
+	MPTCP_REQUEST(req, MPTCP_PM_CMD_GET_ADDR, NLM_F_REQUEST);
359b1d
+	struct nlmsghdr *answer;
359b1d
+	int ret;
359b1d
+
359b1d
+	if (!argv)
359b1d
+		return mptcp_addr_dump();
359b1d
+
359b1d
+	ret = mptcp_parse_opt(argc, argv, &req.n, false);
359b1d
+	if (ret)
359b1d
+		return ret;
359b1d
+
359b1d
+	if (rtnl_talk(&genl_rth, &req.n, &answer) < 0)
359b1d
+		return -2;
359b1d
+
359b1d
+	return print_mptcp_addr(answer, stdout);
359b1d
+}
359b1d
+
359b1d
+static int mptcp_addr_flush(int argc, char **argv)
359b1d
+{
359b1d
+	MPTCP_REQUEST(req, MPTCP_PM_CMD_FLUSH_ADDRS, NLM_F_REQUEST);
359b1d
+
359b1d
+	if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
359b1d
+		return -2;
359b1d
+
359b1d
+	return 0;
359b1d
+}
359b1d
+
359b1d
+static int mptcp_parse_limit(int argc, char **argv, struct nlmsghdr *n)
359b1d
+{
359b1d
+	bool set_rcv_add_addrs = false;
359b1d
+	bool set_subflows = false;
359b1d
+	__u32 rcv_add_addrs = 0;
359b1d
+	__u32 subflows = 0;
359b1d
+
359b1d
+	while (argc > 0) {
359b1d
+		if (matches(*argv, "subflows") == 0) {
359b1d
+			NEXT_ARG();
359b1d
+
359b1d
+			if (get_u32(&subflows, *argv, 0))
359b1d
+				invarg("invalid subflows\n", *argv);
359b1d
+			set_subflows = true;
359b1d
+		} else if (matches(*argv, "add_addr_accepted") == 0) {
359b1d
+			NEXT_ARG();
359b1d
+
359b1d
+			if (get_u32(&rcv_add_addrs, *argv, 0))
359b1d
+				invarg("invalid add_addr_accepted\n", *argv);
359b1d
+			set_rcv_add_addrs = true;
359b1d
+		} else {
359b1d
+			invarg("unknown limit", *argv);
359b1d
+		}
359b1d
+		NEXT_ARG_FWD();
359b1d
+	}
359b1d
+
359b1d
+	if (set_rcv_add_addrs)
359b1d
+		addattr32(n, MPTCP_BUFLEN, MPTCP_PM_ATTR_RCV_ADD_ADDRS,
359b1d
+			  rcv_add_addrs);
359b1d
+	if (set_subflows)
359b1d
+		addattr32(n, MPTCP_BUFLEN, MPTCP_PM_ATTR_SUBFLOWS, subflows);
359b1d
+	return set_rcv_add_addrs || set_subflows;
359b1d
+}
359b1d
+
359b1d
+static int print_mptcp_limit(struct nlmsghdr *n, void *arg)
359b1d
+{
359b1d
+	struct rtattr *tb[MPTCP_PM_ATTR_MAX + 1];
359b1d
+	struct genlmsghdr *ghdr;
359b1d
+	int len = n->nlmsg_len;
359b1d
+	__u32 val;
359b1d
+
359b1d
+	if (n->nlmsg_type != genl_family)
359b1d
+		return 0;
359b1d
+
359b1d
+	len -= NLMSG_LENGTH(GENL_HDRLEN);
359b1d
+	if (len < 0)
359b1d
+		return -1;
359b1d
+
359b1d
+	ghdr = NLMSG_DATA(n);
359b1d
+	parse_rtattr(tb, MPTCP_PM_ATTR_MAX, (void *) ghdr + GENL_HDRLEN, len);
359b1d
+
359b1d
+	open_json_object(NULL);
359b1d
+	if (tb[MPTCP_PM_ATTR_RCV_ADD_ADDRS]) {
359b1d
+		val = rta_getattr_u32(tb[MPTCP_PM_ATTR_RCV_ADD_ADDRS]);
359b1d
+
359b1d
+		print_uint(PRINT_ANY, "add_addr_accepted",
359b1d
+			   "add_addr_accepted %d ", val);
359b1d
+	}
359b1d
+
359b1d
+	if (tb[MPTCP_PM_ATTR_SUBFLOWS]) {
359b1d
+		val = rta_getattr_u32(tb[MPTCP_PM_ATTR_SUBFLOWS]);
359b1d
+
359b1d
+		print_uint(PRINT_ANY, "subflows", "subflows %d ", val);
359b1d
+	}
359b1d
+	print_string(PRINT_FP, NULL, "%s", "\n");
359b1d
+	fflush(stdout);
359b1d
+	close_json_object();
359b1d
+	return 0;
359b1d
+}
359b1d
+
359b1d
+static int mptcp_limit_get_set(int argc, char **argv, int cmd)
359b1d
+{
359b1d
+	bool do_get = cmd == MPTCP_PM_CMD_GET_LIMITS;
359b1d
+	MPTCP_REQUEST(req, cmd, NLM_F_REQUEST);
359b1d
+	struct nlmsghdr *answer;
359b1d
+	int ret;
359b1d
+
359b1d
+	ret = mptcp_parse_limit(argc, argv, &req.n);
359b1d
+	if (ret < 0)
359b1d
+		return -1;
359b1d
+
359b1d
+	if (rtnl_talk(&genl_rth, &req.n, do_get ? &answer : NULL) < 0)
359b1d
+		return -2;
359b1d
+
359b1d
+	if (do_get)
359b1d
+		return print_mptcp_limit(answer, stdout);
359b1d
+	return 0;
359b1d
+}
359b1d
+
359b1d
+int do_mptcp(int argc, char **argv)
359b1d
+{
359b1d
+	if (argc == 0)
359b1d
+		usage();
359b1d
+
359b1d
+	if (matches(*argv, "help") == 0)
359b1d
+		usage();
359b1d
+
359b1d
+	if (genl_init_handle(&genl_rth, MPTCP_PM_NAME, &genl_family))
359b1d
+		exit(1);
359b1d
+
359b1d
+	if (matches(*argv, "endpoint") == 0) {
359b1d
+		NEXT_ARG_FWD();
359b1d
+		if (argc == 0)
359b1d
+			return mptcp_addr_show(0, NULL);
359b1d
+
359b1d
+		if (matches(*argv, "add") == 0)
359b1d
+			return mptcp_addr_modify(argc-1, argv+1,
359b1d
+						 MPTCP_PM_CMD_ADD_ADDR);
359b1d
+		if (matches(*argv, "delete") == 0)
359b1d
+			return mptcp_addr_modify(argc-1, argv+1,
359b1d
+						 MPTCP_PM_CMD_DEL_ADDR);
359b1d
+		if (matches(*argv, "show") == 0)
359b1d
+			return mptcp_addr_show(argc-1, argv+1);
359b1d
+		if (matches(*argv, "flush") == 0)
359b1d
+			return mptcp_addr_flush(argc-1, argv+1);
359b1d
+
359b1d
+		goto unknown;
359b1d
+	}
359b1d
+
359b1d
+	if (matches(*argv, "limits") == 0) {
359b1d
+		NEXT_ARG_FWD();
359b1d
+		if (argc == 0)
359b1d
+			return mptcp_limit_get_set(0, NULL,
359b1d
+						   MPTCP_PM_CMD_GET_LIMITS);
359b1d
+
359b1d
+		if (matches(*argv, "set") == 0)
359b1d
+			return mptcp_limit_get_set(argc-1, argv+1,
359b1d
+						   MPTCP_PM_CMD_SET_LIMITS);
359b1d
+		if (matches(*argv, "show") == 0)
359b1d
+			return mptcp_limit_get_set(argc-1, argv+1,
359b1d
+						   MPTCP_PM_CMD_GET_LIMITS);
359b1d
+	}
359b1d
+
359b1d
+unknown:
359b1d
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip mptcp help\".\n",
359b1d
+		*argv);
359b1d
+	exit(-1);
359b1d
+}
359b1d
-- 
359b1d
2.26.2
359b1d