Files
iproute2-next/ip/ipmptcp.c
Matthieu Baerts (NGI0) b176b9f403 ip: mptcp: 'id 0' is only for 'del'
Adding an endpoint with 'id 0' is not allowed. In this case, the kernel
will ignore this 'id 0' and set another one.

Similarly, because there are no endpoints with this 'id 0', changing an
attribute for such endpoint will not be possible.

To avoid some confusions, it sounds better to clearly report an error
that the ID cannot be 0 in these cases.

Acked-by: Mat Martineau <martineau@kernel.org>
Signed-off-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
2024-08-02 10:47:39 -07:00

618 lines
15 KiB
C

// SPDX-License-Identifier: GPL-2.0
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <linux/genetlink.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/mptcp.h>
#include "utils.h"
#include "ip_common.h"
#include "json_print.h"
#include "libgenl.h"
#include "libnetlink.h"
#include "ll_map.h"
static void usage(void)
{
fprintf(stderr,
"Usage: ip mptcp endpoint add ADDRESS [ dev NAME ] [ id ID ]\n"
" [ port NR ] [ FLAG-LIST ]\n"
" ip mptcp endpoint delete id ID [ ADDRESS ]\n"
" ip mptcp endpoint change [ id ID ] [ ADDRESS ] [ port NR ] CHANGE-OPT\n"
" ip mptcp endpoint show [ id ID ]\n"
" ip mptcp endpoint flush\n"
" ip mptcp limits set [ subflows NR ] [ add_addr_accepted NR ]\n"
" ip mptcp limits show\n"
" ip mptcp monitor\n"
"FLAG-LIST := [ FLAG-LIST ] FLAG\n"
"FLAG := [ signal | subflow | backup | fullmesh ]\n"
"CHANGE-OPT := [ backup | nobackup | fullmesh | nofullmesh ]\n");
exit(-1);
}
/* netlink socket */
static struct rtnl_handle genl_rth = { .fd = -1 };
static int genl_family = -1;
#define MPTCP_BUFLEN 4096
#define MPTCP_REQUEST(_req, _cmd, _flags) \
GENL_REQUEST(_req, MPTCP_BUFLEN, genl_family, 0, \
MPTCP_PM_VER, _cmd, _flags)
#define MPTCP_PM_ADDR_FLAG_NONE 0x0
/* Mapping from argument to address flag mask */
static const struct {
const char *name;
unsigned long value;
} mptcp_addr_flag_names[] = {
{ "signal", MPTCP_PM_ADDR_FLAG_SIGNAL },
{ "subflow", MPTCP_PM_ADDR_FLAG_SUBFLOW },
{ "backup", MPTCP_PM_ADDR_FLAG_BACKUP },
{ "fullmesh", MPTCP_PM_ADDR_FLAG_FULLMESH },
{ "implicit", MPTCP_PM_ADDR_FLAG_IMPLICIT },
{ "nobackup", MPTCP_PM_ADDR_FLAG_NONE },
{ "nofullmesh", MPTCP_PM_ADDR_FLAG_NONE }
};
static void print_mptcp_addr_flags(unsigned int flags)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(mptcp_addr_flag_names); i++) {
unsigned long mask = mptcp_addr_flag_names[i].value;
if (flags & mask) {
print_string(PRINT_FP, NULL, "%s ",
mptcp_addr_flag_names[i].name);
print_bool(PRINT_JSON,
mptcp_addr_flag_names[i].name, NULL, true);
}
flags &= ~mask;
}
if (flags) {
/* unknown flags */
SPRINT_BUF(b1);
snprintf(b1, sizeof(b1), "%02x", flags);
print_string(PRINT_ANY, "rawflags", "rawflags %s ", b1);
}
}
static int get_flags(const char *arg, __u32 *flags)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(mptcp_addr_flag_names); i++) {
if (strcmp(arg, mptcp_addr_flag_names[i].name))
continue;
*flags |= mptcp_addr_flag_names[i].value;
return 0;
}
return -1;
}
static int mptcp_parse_opt(int argc, char **argv, struct nlmsghdr *n, int cmd)
{
bool setting = cmd == MPTCP_PM_CMD_SET_FLAGS;
bool adding = cmd == MPTCP_PM_CMD_ADD_ADDR;
bool deling = cmd == MPTCP_PM_CMD_DEL_ADDR;
struct rtattr *attr_addr;
bool addr_set = false;
inet_prefix address;
bool id_set = false;
__u32 index = 0;
__u32 flags = 0;
__u16 port = 0;
__u8 id = 0;
ll_init_map(&rth);
while (argc > 0) {
if (get_flags(*argv, &flags) == 0) {
if (adding &&
(flags & MPTCP_PM_ADDR_FLAG_SIGNAL) &&
(flags & MPTCP_PM_ADDR_FLAG_FULLMESH))
invarg("flags mustn't have both signal and fullmesh", *argv);
/* allow changing the 'backup' and 'fullmesh' flags only */
if (setting &&
(flags & ~(MPTCP_PM_ADDR_FLAG_BACKUP |
MPTCP_PM_ADDR_FLAG_FULLMESH)))
invarg("invalid flags, backup and fullmesh only", *argv);
} else if (matches(*argv, "id") == 0) {
NEXT_ARG();
if (get_u8(&id, *argv, 0))
invarg("invalid ID\n", *argv);
id_set = true;
} else if (matches(*argv, "dev") == 0) {
const char *ifname;
NEXT_ARG();
ifname = *argv;
if (check_ifname(ifname))
invarg("invalid interface name\n", ifname);
index = ll_name_to_index(ifname);
if (!index)
invarg("device does not exist\n", ifname);
} else if (matches(*argv, "port") == 0) {
NEXT_ARG();
if (get_u16(&port, *argv, 0))
invarg("expected port", *argv);
} else if (get_addr(&address, *argv, AF_UNSPEC) == 0) {
addr_set = true;
} else {
invarg("unknown argument", *argv);
}
NEXT_ARG_FWD();
}
if (!addr_set && adding)
missarg("ADDRESS");
if (!id_set && deling) {
missarg("ID");
} else if (id_set && deling) {
if (id && addr_set)
invarg("invalid for non-zero id address\n", "ADDRESS");
else if (!id && !addr_set)
invarg("address is needed for deleting id 0 address\n", "ID");
} else if (id_set && !deling && !id) {
invarg("cannot be 0\n", "ID");
}
if (adding && port && !(flags & MPTCP_PM_ADDR_FLAG_SIGNAL))
invarg("flags must have signal when using port", "port");
if (setting && id_set && port)
invarg("port can't be used with id", "port");
attr_addr = addattr_nest(n, MPTCP_BUFLEN,
MPTCP_PM_ATTR_ADDR | NLA_F_NESTED);
if (id_set)
addattr8(n, MPTCP_BUFLEN, MPTCP_PM_ADDR_ATTR_ID, id);
if (flags)
addattr32(n, MPTCP_BUFLEN, MPTCP_PM_ADDR_ATTR_FLAGS, flags);
if (index)
addattr32(n, MPTCP_BUFLEN, MPTCP_PM_ADDR_ATTR_IF_IDX, index);
if (port)
addattr16(n, MPTCP_BUFLEN, MPTCP_PM_ADDR_ATTR_PORT, port);
if (addr_set) {
int type;
addattr16(n, MPTCP_BUFLEN, MPTCP_PM_ADDR_ATTR_FAMILY,
address.family);
type = address.family == AF_INET ? MPTCP_PM_ADDR_ATTR_ADDR4 :
MPTCP_PM_ADDR_ATTR_ADDR6;
addattr_l(n, MPTCP_BUFLEN, type, &address.data,
address.bytelen);
}
addattr_nest_end(n, attr_addr);
return 0;
}
static int mptcp_addr_modify(int argc, char **argv, int cmd)
{
MPTCP_REQUEST(req, cmd, NLM_F_REQUEST);
int ret;
ret = mptcp_parse_opt(argc, argv, &req.n, cmd);
if (ret)
return ret;
if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
return -2;
return 0;
}
static int print_mptcp_addrinfo(struct rtattr *addrinfo)
{
struct rtattr *tb[MPTCP_PM_ADDR_ATTR_MAX + 1];
__u8 family = AF_UNSPEC, addr_attr_type;
const char *ifname;
unsigned int flags;
__u16 id, port;
int index;
parse_rtattr_nested(tb, MPTCP_PM_ADDR_ATTR_MAX, addrinfo);
open_json_object(NULL);
if (tb[MPTCP_PM_ADDR_ATTR_FAMILY])
family = rta_getattr_u8(tb[MPTCP_PM_ADDR_ATTR_FAMILY]);
addr_attr_type = family == AF_INET ? MPTCP_PM_ADDR_ATTR_ADDR4 :
MPTCP_PM_ADDR_ATTR_ADDR6;
if (tb[addr_attr_type]) {
print_string(PRINT_ANY, "address", "%s ",
format_host_rta(family, tb[addr_attr_type]));
}
if (tb[MPTCP_PM_ADDR_ATTR_PORT]) {
port = rta_getattr_u16(tb[MPTCP_PM_ADDR_ATTR_PORT]);
if (port)
print_uint(PRINT_ANY, "port", "port %u ", port);
}
if (tb[MPTCP_PM_ADDR_ATTR_ID]) {
id = rta_getattr_u8(tb[MPTCP_PM_ADDR_ATTR_ID]);
print_uint(PRINT_ANY, "id", "id %u ", id);
}
if (tb[MPTCP_PM_ADDR_ATTR_FLAGS]) {
flags = rta_getattr_u32(tb[MPTCP_PM_ADDR_ATTR_FLAGS]);
print_mptcp_addr_flags(flags);
}
if (tb[MPTCP_PM_ADDR_ATTR_IF_IDX]) {
index = rta_getattr_s32(tb[MPTCP_PM_ADDR_ATTR_IF_IDX]);
ifname = index ? ll_index_to_name(index) : NULL;
if (ifname)
print_string(PRINT_ANY, "dev", "dev %s ", ifname);
}
close_json_object();
print_string(PRINT_FP, NULL, "\n", NULL);
fflush(stdout);
return 0;
}
static int print_mptcp_addr(struct nlmsghdr *n, void *arg)
{
struct rtattr *tb[MPTCP_PM_ATTR_MAX + 1];
struct genlmsghdr *ghdr;
struct rtattr *addrinfo;
int len = n->nlmsg_len;
if (n->nlmsg_type != genl_family)
return 0;
len -= NLMSG_LENGTH(GENL_HDRLEN);
if (len < 0)
return -1;
ghdr = NLMSG_DATA(n);
parse_rtattr_flags(tb, MPTCP_PM_ATTR_MAX, (void *) ghdr + GENL_HDRLEN,
len, NLA_F_NESTED);
addrinfo = tb[MPTCP_PM_ATTR_ADDR];
if (!addrinfo)
return -1;
ll_init_map(&rth);
return print_mptcp_addrinfo(addrinfo);
}
static int mptcp_addr_dump(void)
{
MPTCP_REQUEST(req, MPTCP_PM_CMD_GET_ADDR, NLM_F_REQUEST | NLM_F_DUMP);
if (rtnl_send(&genl_rth, &req.n, req.n.nlmsg_len) < 0) {
perror("Cannot send show request");
exit(1);
}
new_json_obj(json);
if (rtnl_dump_filter(&genl_rth, print_mptcp_addr, stdout) < 0) {
fprintf(stderr, "Dump terminated\n");
delete_json_obj();
fflush(stdout);
return -2;
}
delete_json_obj();
fflush(stdout);
return 0;
}
static int mptcp_addr_show(int argc, char **argv)
{
MPTCP_REQUEST(req, MPTCP_PM_CMD_GET_ADDR, NLM_F_REQUEST);
struct nlmsghdr *answer;
int ret;
if (argc <= 0)
return mptcp_addr_dump();
ret = mptcp_parse_opt(argc, argv, &req.n, MPTCP_PM_CMD_GET_ADDR);
if (ret)
return ret;
if (rtnl_talk(&genl_rth, &req.n, &answer) < 0)
return -2;
new_json_obj(json);
ret = print_mptcp_addr(answer, stdout);
delete_json_obj();
free(answer);
fflush(stdout);
return ret;
}
static int mptcp_addr_flush(int argc, char **argv)
{
MPTCP_REQUEST(req, MPTCP_PM_CMD_FLUSH_ADDRS, NLM_F_REQUEST);
if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
return -2;
return 0;
}
static int mptcp_parse_limit(int argc, char **argv, struct nlmsghdr *n)
{
bool set_rcv_add_addrs = false;
bool set_subflows = false;
__u32 rcv_add_addrs = 0;
__u32 subflows = 0;
while (argc > 0) {
if (matches(*argv, "subflows") == 0) {
NEXT_ARG();
if (get_u32(&subflows, *argv, 0))
invarg("invalid subflows\n", *argv);
set_subflows = true;
} else if (matches(*argv, "add_addr_accepted") == 0) {
NEXT_ARG();
if (get_u32(&rcv_add_addrs, *argv, 0))
invarg("invalid add_addr_accepted\n", *argv);
set_rcv_add_addrs = true;
} else {
invarg("unknown limit", *argv);
}
NEXT_ARG_FWD();
}
if (set_rcv_add_addrs)
addattr32(n, MPTCP_BUFLEN, MPTCP_PM_ATTR_RCV_ADD_ADDRS,
rcv_add_addrs);
if (set_subflows)
addattr32(n, MPTCP_BUFLEN, MPTCP_PM_ATTR_SUBFLOWS, subflows);
return set_rcv_add_addrs || set_subflows;
}
static int print_mptcp_limit(struct nlmsghdr *n, void *arg)
{
struct rtattr *tb[MPTCP_PM_ATTR_MAX + 1];
struct genlmsghdr *ghdr;
int len = n->nlmsg_len;
__u32 val;
if (n->nlmsg_type != genl_family)
return 0;
len -= NLMSG_LENGTH(GENL_HDRLEN);
if (len < 0)
return -1;
ghdr = NLMSG_DATA(n);
parse_rtattr(tb, MPTCP_PM_ATTR_MAX, (void *) ghdr + GENL_HDRLEN, len);
open_json_object(NULL);
if (tb[MPTCP_PM_ATTR_RCV_ADD_ADDRS]) {
val = rta_getattr_u32(tb[MPTCP_PM_ATTR_RCV_ADD_ADDRS]);
print_uint(PRINT_ANY, "add_addr_accepted",
"add_addr_accepted %d ", val);
}
if (tb[MPTCP_PM_ATTR_SUBFLOWS]) {
val = rta_getattr_u32(tb[MPTCP_PM_ATTR_SUBFLOWS]);
print_uint(PRINT_ANY, "subflows", "subflows %d ", val);
}
print_string(PRINT_FP, NULL, "%s", "\n");
fflush(stdout);
close_json_object();
return 0;
}
static int mptcp_limit_get_set(int argc, char **argv, int cmd)
{
bool do_get = cmd == MPTCP_PM_CMD_GET_LIMITS;
MPTCP_REQUEST(req, cmd, NLM_F_REQUEST);
struct nlmsghdr *answer;
int ret;
ret = mptcp_parse_limit(argc, argv, &req.n);
if (ret < 0)
return -1;
if (rtnl_talk(&genl_rth, &req.n, do_get ? &answer : NULL) < 0)
return -2;
ret = 0;
if (do_get) {
ret = print_mptcp_limit(answer, stdout);
free(answer);
}
return ret;
}
static const char * const event_to_str[] = {
[MPTCP_EVENT_CREATED] = "CREATED",
[MPTCP_EVENT_ESTABLISHED] = "ESTABLISHED",
[MPTCP_EVENT_CLOSED] = "CLOSED",
[MPTCP_EVENT_ANNOUNCED] = "ANNOUNCED",
[MPTCP_EVENT_REMOVED] = "REMOVED",
[MPTCP_EVENT_SUB_ESTABLISHED] = "SF_ESTABLISHED",
[MPTCP_EVENT_SUB_CLOSED] = "SF_CLOSED",
[MPTCP_EVENT_SUB_PRIORITY] = "SF_PRIO",
[MPTCP_EVENT_LISTENER_CREATED] = "LISTENER_CREATED",
[MPTCP_EVENT_LISTENER_CLOSED] = "LISTENER_CLOSED",
};
static void print_addr(const char *key, int af, struct rtattr *value)
{
void *data = RTA_DATA(value);
char str[INET6_ADDRSTRLEN];
if (inet_ntop(af, data, str, sizeof(str)))
printf(" %s=%s", key, str);
}
static int mptcp_monitor_msg(struct rtnl_ctrl_data *ctrl,
struct nlmsghdr *n, void *arg)
{
const struct genlmsghdr *ghdr = NLMSG_DATA(n);
struct rtattr *tb[MPTCP_ATTR_MAX + 1];
int len = n->nlmsg_len;
len -= NLMSG_LENGTH(GENL_HDRLEN);
if (len < 0)
return -1;
if (n->nlmsg_type != genl_family)
return 0;
if (timestamp)
print_timestamp(stdout);
if (ghdr->cmd >= ARRAY_SIZE(event_to_str)) {
printf("[UNKNOWN %u]\n", ghdr->cmd);
goto out;
}
if (event_to_str[ghdr->cmd] == NULL) {
printf("[UNKNOWN %u]\n", ghdr->cmd);
goto out;
}
printf("[%16s]", event_to_str[ghdr->cmd]);
parse_rtattr(tb, MPTCP_ATTR_MAX, (void *) ghdr + GENL_HDRLEN, len);
if (tb[MPTCP_ATTR_TOKEN])
printf(" token=%08x", rta_getattr_u32(tb[MPTCP_ATTR_TOKEN]));
if (tb[MPTCP_ATTR_REM_ID])
printf(" remid=%u", rta_getattr_u8(tb[MPTCP_ATTR_REM_ID]));
if (tb[MPTCP_ATTR_LOC_ID])
printf(" locid=%u", rta_getattr_u8(tb[MPTCP_ATTR_LOC_ID]));
if (tb[MPTCP_ATTR_SADDR4])
print_addr("saddr4", AF_INET, tb[MPTCP_ATTR_SADDR4]);
if (tb[MPTCP_ATTR_DADDR4])
print_addr("daddr4", AF_INET, tb[MPTCP_ATTR_DADDR4]);
if (tb[MPTCP_ATTR_SADDR6])
print_addr("saddr6", AF_INET6, tb[MPTCP_ATTR_SADDR6]);
if (tb[MPTCP_ATTR_DADDR6])
print_addr("daddr6", AF_INET6, tb[MPTCP_ATTR_DADDR6]);
if (tb[MPTCP_ATTR_SPORT])
printf(" sport=%u", rta_getattr_be16(tb[MPTCP_ATTR_SPORT]));
if (tb[MPTCP_ATTR_DPORT])
printf(" dport=%u", rta_getattr_be16(tb[MPTCP_ATTR_DPORT]));
if (tb[MPTCP_ATTR_BACKUP])
printf(" backup=%d", rta_getattr_u8(tb[MPTCP_ATTR_BACKUP]));
if (tb[MPTCP_ATTR_ERROR])
printf(" error=%d", rta_getattr_u8(tb[MPTCP_ATTR_ERROR]));
if (tb[MPTCP_ATTR_FLAGS])
printf(" flags=%x", rta_getattr_u16(tb[MPTCP_ATTR_FLAGS]));
if (tb[MPTCP_ATTR_TIMEOUT])
printf(" timeout=%u", rta_getattr_u32(tb[MPTCP_ATTR_TIMEOUT]));
if (tb[MPTCP_ATTR_IF_IDX])
printf(" ifindex=%d", rta_getattr_s32(tb[MPTCP_ATTR_IF_IDX]));
if (tb[MPTCP_ATTR_RESET_REASON])
printf(" reset_reason=%u", rta_getattr_u32(tb[MPTCP_ATTR_RESET_REASON]));
if (tb[MPTCP_ATTR_RESET_FLAGS])
printf(" reset_flags=0x%x", rta_getattr_u32(tb[MPTCP_ATTR_RESET_FLAGS]));
puts("");
out:
fflush(stdout);
return 0;
}
static int mptcp_monitor(void)
{
if (genl_add_mcast_grp(&genl_rth, genl_family, MPTCP_PM_EV_GRP_NAME) < 0) {
perror("can't subscribe to mptcp events");
return 1;
}
if (rtnl_listen(&genl_rth, mptcp_monitor_msg, stdout) < 0)
return 2;
return 0;
}
int do_mptcp(int argc, char **argv)
{
if (argc == 0)
usage();
if (matches(*argv, "help") == 0)
usage();
if (genl_init_handle(&genl_rth, MPTCP_PM_NAME, &genl_family))
exit(1);
if (matches(*argv, "endpoint") == 0) {
NEXT_ARG_FWD();
if (argc == 0)
return mptcp_addr_show(0, NULL);
if (matches(*argv, "add") == 0)
return mptcp_addr_modify(argc-1, argv+1,
MPTCP_PM_CMD_ADD_ADDR);
if (matches(*argv, "change") == 0)
return mptcp_addr_modify(argc-1, argv+1,
MPTCP_PM_CMD_SET_FLAGS);
if (matches(*argv, "delete") == 0)
return mptcp_addr_modify(argc-1, argv+1,
MPTCP_PM_CMD_DEL_ADDR);
if (matches(*argv, "show") == 0)
return mptcp_addr_show(argc-1, argv+1);
if (matches(*argv, "flush") == 0)
return mptcp_addr_flush(argc-1, argv+1);
goto unknown;
}
if (matches(*argv, "limits") == 0) {
NEXT_ARG_FWD();
if (argc == 0)
return mptcp_limit_get_set(0, NULL,
MPTCP_PM_CMD_GET_LIMITS);
if (matches(*argv, "set") == 0)
return mptcp_limit_get_set(argc-1, argv+1,
MPTCP_PM_CMD_SET_LIMITS);
if (matches(*argv, "show") == 0)
return mptcp_limit_get_set(argc-1, argv+1,
MPTCP_PM_CMD_GET_LIMITS);
}
if (matches(*argv, "monitor") == 0) {
NEXT_ARG_FWD();
if (argc == 0)
return mptcp_monitor();
goto unknown;
}
unknown:
fprintf(stderr, "Command \"%s\" is unknown, try \"ip mptcp help\".\n",
*argv);
exit(-1);
}