Subject: Re: [PATCH iproute2-next] ip: Add support for nexthop objects

From: Stephen Hemminger <[email-masked]>
Date: Sat, 1 Sep 2018 13:37:53 -0700

On Fri, 31 Aug 2018 17:49:54 -0700
[email-masked] wrote:

> From: David Ahern <[email-masked]>
> 
> Signed-off-by: David Ahern <[email-masked]>
> ---
>  include/uapi/linux/nexthop.h   |  56 ++++
>  include/uapi/linux/rtnetlink.h |   8 +
>  ip/Makefile                    |   3 +-
>  ip/ip.c                        |   3 +-
>  ip/ip_common.h                 |   7 +-
>  ip/ipmonitor.c                 |   6 +
>  ip/ipnexthop.c                 | 652 +++++++++++++++++++++++++++++++++++++++++
>  ip/iproute.c                   |  19 +-
>  8 files changed, 747 insertions(+), 7 deletions(-)
>  create mode 100644 include/uapi/linux/nexthop.h
>  create mode 100644 ip/ipnexthop.c
> 
> diff --git a/include/uapi/linux/nexthop.h b/include/uapi/linux/nexthop.h
> new file mode 100644
> index 000000000000..335182e8229a
> --- /dev/null
> +++ b/include/uapi/linux/nexthop.h
> @@ -0,0 +1,56 @@
> +#ifndef __LINUX_NEXTHOP_H
> +#define __LINUX_NEXTHOP_H
> +
> +#include <linux/types.h>
> +
> +struct nhmsg {
> +	unsigned char	nh_family;
> +	unsigned char	nh_scope;     /* one of RT_SCOPE */
> +	unsigned char	nh_protocol;  /* Routing protocol that installed nh */
> +	unsigned char	resvd;
> +	unsigned int	nh_flags;     /* RTNH_F flags */
> +};

Why not use __u8 and __u32 for these?

> +struct nexthop_grp {
> +	__u32	id;
> +	__u32	weight;
> +};
> +
> +enum {
> +	NEXTHOP_GRP_TYPE_MPATH,  /* default type if not specified */
> +	__NEXTHOP_GRP_TYPE_MAX,
> +};
> +
> +#define NEXTHOP_GRP_TYPE_MAX (__NEXTHOP_GRP_TYPE_MAX - 1)
> +
> +
> +/* NHA_ID	32-bit id for nexthop. id must be greater than 0.
> + *		id == 0 means assign an unused id.
> + */

Don't use dave's preferred comment style in this file.
The reset of the file uses standard comments.
...

> diff --git a/ip/ip_common.h b/ip/ip_common.h
> index 200be5e23dd1..2971c1586c4e 100644
> --- a/ip/ip_common.h
> +++ b/ip/ip_common.h
> @@ -56,6 +56,8 @@ int print_rule(const struct sockaddr_nl *who,
>  int print_netconf(const struct sockaddr_nl *who,
>  		  struct rtnl_ctrl_data *ctrl,
>  		  struct nlmsghdr *n, void *arg);
> +int print_nexthop(const struct sockaddr_nl *who,
> +		  struct nlmsghdr *n, void *arg);
>  void netns_map_init(void);
>  void netns_nsid_socket_init(void);
>  int print_nsid(const struct sockaddr_nl *who,
> @@ -90,6 +92,7 @@ int do_ipvrf(int argc, char **argv);
>  void vrf_reset(void);
>  int netns_identify_pid(const char *pidstr, char *name, int len);
>  int do_seg6(int argc, char **argv);
> +int do_ipnh(int argc, char **argv);
>  
>  int iplink_get(unsigned int flags, char *name, __u32 filt_mask);
>  int iplink_ifla_xstats(int argc, char **argv);
> @@ -165,5 +168,7 @@ int name_is_vrf(const char *name);
>  #endif
>  
>  void print_num(FILE *fp, unsigned int width, uint64_t count);
> -
> +void print_rta_flow(FILE *fp, const struct rtattr *rta);
> +void print_rt_flags(FILE *fp, unsigned int flags);
> +void print_rta_if(FILE *fp, const struct rtattr *rta, const char *prefix);
>  #endif /* _IP_COMMON_H_ */
> diff --git a/ip/ipmonitor.c b/ip/ipmonitor.c
> index a93b62cd6624..de129626683b 100644
> --- a/ip/ipmonitor.c
> +++ b/ip/ipmonitor.c
> @@ -84,6 +84,12 @@ static int accept_msg(const struct sockaddr_nl *who,
>  		}
>  	}
>  
> +	case RTM_NEWNEXTHOP:
> +	case RTM_DELNEXTHOP:
> +		print_headers(fp, "[NEXTHOP]", ctrl);
> +		print_nexthop(who, n, arg);
> +		return 0;
> +
>  	case RTM_NEWLINK:
>  	case RTM_DELLINK:
>  		ll_remember_index(who, n, NULL);
> diff --git a/ip/ipnexthop.c b/ip/ipnexthop.c
> new file mode 100644
> index 000000000000..9fa4b7292426
> --- /dev/null
> +++ b/ip/ipnexthop.c
> @@ -0,0 +1,652 @@
> +/*
> + * ip nexthop
> + *
> + * Copyright (C) 2017 Cumulus Networks
> + * Copyright (c) 2017 David Ahern <[email-masked]>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + */
>

Please use SPDX and not GPL boilerplate in new files.

> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <string.h>
> +#include <sys/socket.h>
> +#include <netinet/in.h>
> +#include <netinet/ip.h>
> +#include <errno.h>
> +#include <linux/nexthop.h>
> +#include <libmnl/libmnl.h>

Is this code really using libmnl?

> +#include <rt_names.h>
> +
> +#include "utils.h"
> +#include "ip_common.h"
> +
> +static struct
> +{
> +	unsigned int flushed;
> +	unsigned int groups;
> +	char *flushb;
> +	int flushp;
> +	int flushe;
> +} filter;
> +
> +enum {
> +	IPNH_LIST,
> +	IPNH_FLUSH,
> +};
> +
> +#define RTM_NHA(h)  ((struct rtattr *)(((char *)(h)) + \
> +			NLMSG_ALIGN(sizeof(struct nhmsg))))
> +
> +static void usage(void) __attribute__((noreturn));
> +
> +static void usage(void)
> +{
> +	fprintf(stderr, "Usage: ip nexthop { list | flush } SELECTOR\n");
> +	fprintf(stderr, "       ip nexthop get [ id ID ]\n");
> +	fprintf(stderr, "       ip nexthop { add | del | change | replace } NH\n");
> +	fprintf(stderr, "SELECTOR := [ id ID ] [ dev DEV ] [ table TABLE ] [ vrf NAME ]\n");
> +	fprintf(stderr, "NH := [ encap ENCAPTYPE ENCAPHDR ] [ via [ FAMILY ] ADDRESS ]\n");
> +	fprintf(stderr, "      [ id ID ] [ dev STRING ] [ weight NUMBER ]\n");
> +	fprintf(stderr, "      [ table TABLE ] [ vrf VRF ] NHFLAGS\n");
> +	fprintf(stderr, "NHFLAGS := [ onlink | pervasive ]\n");
> +	fprintf(stderr, "ENCAPTYPE := [ mpls | ip | ip6 ]\n");
> +	fprintf(stderr, "ENCAPHDR := [ MPLSLABEL ]\n");
> +	exit(-1);
> +}
> +
> +static int delete_nexthop(__u32 id)
> +{
> +	struct {
> +		struct nlmsghdr	n;
> +		struct nhmsg	nhm;
> +		char		buf[64];
> +	} req = {
> +		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)),
> +		.n.nlmsg_flags = NLM_F_REQUEST,
> +		.n.nlmsg_type = RTM_DELNEXTHOP,
> +		.nhm.nh_family = AF_UNSPEC,
> +	};
> +
> +	req.n.nlmsg_seq = ++rth.seq;
> +
> +	addattr32(&req.n, sizeof(req), NHA_ID, id);
> +
> +	if (rtnl_talk(&rth, &req.n, NULL) < 0)
> +		return -1;
> +
> +	filter.flushed++;
> +	return 0;
> +}
> +
> +struct nh_entry {
> +	__u32 id;
> +	unsigned int group;
> +	struct nh_entry *next;
> +};
> +
> +struct nh_entry *first, *last;
> +
> +static int flush_nexthop(const struct sockaddr_nl *who,
> +			 struct nlmsghdr *nlh, void *arg)
> +{
> +	struct nhmsg *nhm = NLMSG_DATA(nlh);
> +	struct rtattr *tb[NHA_MAX+1];
> +	struct nh_entry *nh;
> +	__u32 id = 0;
> +	int len;
> +
> +	len = nlh->nlmsg_len - NLMSG_SPACE(sizeof(*nhm));
> +	if (len < 0) {
> +		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
> +		return -1;
> +	}
> +
> +	parse_rtattr(tb, NHA_MAX, RTM_NHA(nhm), len);
> +	if (tb[NHA_ID])
> +		id = rta_getattr_u32(tb[NHA_ID]);
> +
> +	if (!id)
> +		return 0;
> +
> +	nh = malloc(sizeof(*nh));
> +	if (!nh)
> +		return -1;
> +
> +	nh->id = id;
> +	nh->group = tb[NHA_GROUP] != NULL;
> +	nh->next = NULL;
> +	if (!first)
> +		first = nh;
> +	else
> +		last->next = nh;
> +
> +	last = nh;
> +	return 0;
> +}
> +
> +static int ipnh_flush(void *req, __u32 len, unsigned int all)
> +{
> +	struct nh_entry *nh;
> +
> +	if (send(rth.fd, req, len, 0) < 0) {
> +		perror("Cannot send dump request");
> +		return -2;
> +	}
> +
> +	if (rtnl_dump_filter(&rth, flush_nexthop, stdout) < 0) {
> +		fprintf(stderr, "Dump terminated\n");
> +		return -2;
> +	}
> +
> +	/* if deleting all, then remove groups first */
> +	if (all) {
> +		nh = first;
> +		while (nh) {
> +			if (nh->group)
> +				delete_nexthop(nh->id);
> +			nh = nh->next;
> +		}
> +	}
> +
> +	nh = first;
> +	while (nh) {
> +		if (!all || !nh->group)
> +			delete_nexthop(nh->id);
> +		nh = nh->next;
> +	}
> +
> +	if (!filter.flushed)
> +		printf("Nothing to flush\n");
> +	else
> +		printf("Flushed %d nexthops\n", filter.flushed);
> +
> +	return 0;
> +}
> +
> +static char *nh_group_type_to_str(__u16 group_type, char *buf, size_t len)
> +{
> +	static const char *typestr[NEXTHOP_GRP_TYPE_MAX + 1] = {
> +		"multipath",   /* NEXTHOP_GRP_TYPE_MPATH */
> +	};
> +
> +	if (group_type < ARRAY_SIZE(typestr))
> +		snprintf(buf, len-1, "%s", typestr[group_type]);
> +	else
> +		snprintf(buf, len-1, "<%u>", group_type);
> +
> +	buf[len-1] = '\0';
> +
> +	return buf;
> +}
> +
> +static void print_nh_group(FILE *fp, const struct rtattr *grps_attr,
> +			   const struct rtattr *gtype)
> +{
> +	struct nexthop_grp *nhg = RTA_DATA(grps_attr);
> +	int num = RTA_PAYLOAD(grps_attr) / sizeof(*nhg);
> +	__u16 group_type = NEXTHOP_GRP_TYPE_MPATH;
> +	int i;
> +
> +	SPRINT_BUF(b1);
> +
> +	if (!num || num * sizeof(*nhg) != RTA_PAYLOAD(grps_attr)) {
> +		fprintf(fp, "<invalid nexthop group>");
> +		return;
> +	}
> +
> +	if (gtype)
> +		group_type = rta_getattr_u16(gtype);
> +
> +	if (is_json_context()) {
> +		open_json_array(PRINT_JSON, "group");
> +		for (i = 0; i < num; ++i) {
> +			open_json_object(NULL);
> +			print_uint(PRINT_ANY, "id", "id %u ", nhg[i].id);
> +			print_uint(PRINT_ANY, "weight", "weight %u ", nhg[i].weight);
> +			close_json_object();
> +		}
> +		close_json_array(PRINT_JSON, NULL);
> +		print_string(PRINT_ANY, "type", "type %s ",
> +			     nh_group_type_to_str(group_type, b1, sizeof(b1)));
> +	} else {
> +		fprintf(fp, "group ");
> +		for (i = 0; i < num; ++i) {
> +			if (i)
> +				fprintf(fp, "/");
> +			fprintf(fp, "%u", nhg[i].id);
> +			if (num > 1 && nhg[i].weight > 1)
> +				fprintf(fp, ",%u", nhg[i].weight);
> +		}
> +	}
> +}

I think this could be done by using json_print cleverly rather than having
to use is_json_contex(). That would avoid repeating code.

You are only decoding group type in the json version, why not both?