linux/tools/testing/selftests/net/mptcp/pm_nl_ctl.c
Geliang Tang d4a7726a79 selftests: mptcp: add port argument for pm_nl_ctl
This patch adds a new argument for pm_nl_ctl tool. We can use it like
this:

 # pm_nl_ctl add 10.0.2.1 flags signal port 10100
 # pm_nl_ctl dump
 id 1 flags signal 10.0.2.1 10100

Signed-off-by: Geliang Tang <geliangtang@gmail.com>
Signed-off-by: Mat Martineau <mathew.j.martineau@linux.intel.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2021-02-02 18:37:19 -08:00

722 lines
18 KiB
C

// 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|set|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, "\tset <ip> [flags backup|nobackup]\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_int32_t flags = 0;
u_int16_t family;
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 */
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 if (!strcmp(argv[arg], "port")) {
u_int16_t port;
if (++arg >= argc)
error(1, 0, " missing port value");
if (!(flags & MPTCP_PM_ADDR_FLAG_SIGNAL))
error(1, 0, " flags must be signal when using port");
port = atoi(argv[arg]);
rta = (void *)(data + off);
rta->rta_type = MPTCP_PM_ADDR_ATTR_PORT;
rta->rta_len = RTA_LENGTH(2);
memcpy(RTA_DATA(rta), &port, 2);
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;
uint16_t port = 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_PORT)
memcpy(&port, 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 (port)
printf(" %d", port);
}
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 (port)
printf(" %d", port);
}
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 set_flags(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_int32_t flags = 0;
u_int16_t family;
int nest_start;
int off = 0;
int arg;
memset(data, 0, sizeof(data));
nh = (void *)data;
off = init_genl_req(data, pm_family, MPTCP_PM_CMD_SET_FLAGS,
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 */
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, "backup"))
flags |= MPTCP_PM_ADDR_FLAG_BACKUP;
else if (strcmp(tok, "nobackup"))
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 {
error(1, 0, "unknown keyword %s", argv[arg]);
}
}
nest->rta_len = off - nest_start;
do_nl_req(fd, nh, off, 0);
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);
else if (!strcmp(argv[1], "set"))
return set_flags(fd, pm_family, argc, argv);
fprintf(stderr, "unknown sub-command: %s", argv[1]);
syntax(argv);
return 0;
}