summaryrefslogtreecommitdiffstats
path: root/ip
diff options
context:
space:
mode:
Diffstat (limited to 'ip')
-rw-r--r--ip/.gitignore2
-rw-r--r--ip/Makefile57
-rw-r--r--ip/ila_common.h106
-rw-r--r--ip/ip.c327
-rw-r--r--ip/ip6tunnel.c437
-rw-r--r--ip/ip_common.h234
-rw-r--r--ip/ipaddress.c2685
-rw-r--r--ip/ipaddrlabel.c260
-rw-r--r--ip/ipfou.c351
-rw-r--r--ip/ipila.c310
-rw-r--r--ip/ipioam6.c333
-rw-r--r--ip/ipl2tp.c846
-rw-r--r--ip/iplink.c1463
-rw-r--r--ip/iplink_amt.c196
-rw-r--r--ip/iplink_bareudp.c152
-rw-r--r--ip/iplink_batadv.c65
-rw-r--r--ip/iplink_bond.c964
-rw-r--r--ip/iplink_bond_slave.c195
-rw-r--r--ip/iplink_bridge.c1040
-rw-r--r--ip/iplink_bridge_slave.c488
-rw-r--r--ip/iplink_can.c670
-rw-r--r--ip/iplink_dsa.c68
-rw-r--r--ip/iplink_dummy.c17
-rw-r--r--ip/iplink_geneve.c391
-rw-r--r--ip/iplink_gtp.c140
-rw-r--r--ip/iplink_hsr.c166
-rw-r--r--ip/iplink_ifb.c17
-rw-r--r--ip/iplink_ipoib.c141
-rw-r--r--ip/iplink_ipvlan.c129
-rw-r--r--ip/iplink_macvlan.c329
-rw-r--r--ip/iplink_netdevsim.c17
-rw-r--r--ip/iplink_netkit.c183
-rw-r--r--ip/iplink_nlmon.c17
-rw-r--r--ip/iplink_rmnet.c81
-rw-r--r--ip/iplink_team.c26
-rw-r--r--ip/iplink_vcan.c17
-rw-r--r--ip/iplink_virt_wifi.c25
-rw-r--r--ip/iplink_vlan.c276
-rw-r--r--ip/iplink_vrf.c222
-rw-r--r--ip/iplink_vxcan.c84
-rw-r--r--ip/iplink_vxlan.c634
-rw-r--r--ip/iplink_wwan.c72
-rw-r--r--ip/iplink_xdp.c195
-rw-r--r--ip/iplink_xstats.c79
-rw-r--r--ip/ipmacsec.c1544
-rw-r--r--ip/ipmaddr.c387
-rw-r--r--ip/ipmonitor.c345
-rw-r--r--ip/ipmptcp.c615
-rw-r--r--ip/ipmroute.c325
-rw-r--r--ip/ipneigh.c767
-rw-r--r--ip/ipnetconf.c247
-rw-r--r--ip/ipnetns.c1052
-rw-r--r--ip/ipnexthop.c1321
-rw-r--r--ip/ipntable.c687
-rw-r--r--ip/ipprefix.c85
-rw-r--r--ip/iproute.c2409
-rw-r--r--ip/iproute_lwtunnel.c2285
-rw-r--r--ip/iprule.c1074
-rw-r--r--ip/ipseg6.c250
-rw-r--r--ip/ipstats.c1361
-rw-r--r--ip/iptoken.c204
-rw-r--r--ip/iptunnel.c602
-rw-r--r--ip/iptuntap.c562
-rw-r--r--ip/ipvrf.c654
-rw-r--r--ip/ipxfrm.c1553
-rw-r--r--ip/link_gre.c576
-rw-r--r--ip/link_gre6.c634
-rw-r--r--ip/link_ip6tnl.c461
-rw-r--r--ip/link_iptnl.c492
-rw-r--r--ip/link_veth.c81
-rw-r--r--ip/link_vti.c213
-rw-r--r--ip/link_vti6.c215
-rw-r--r--ip/link_xfrm.c101
-rw-r--r--ip/nh_common.h53
-rwxr-xr-xip/routel62
-rw-r--r--ip/rtm_map.c124
-rw-r--r--ip/rtmon.c179
-rw-r--r--ip/static-syms.c15
-rw-r--r--ip/tcp_metrics.c539
-rw-r--r--ip/tunnel.c434
-rw-r--r--ip/tunnel.h49
-rw-r--r--ip/xfrm.h130
-rw-r--r--ip/xfrm_monitor.c413
-rw-r--r--ip/xfrm_policy.c1348
-rw-r--r--ip/xfrm_state.c1486
85 files changed, 40441 insertions, 0 deletions
diff --git a/ip/.gitignore b/ip/.gitignore
new file mode 100644
index 0000000..b95b232
--- /dev/null
+++ b/ip/.gitignore
@@ -0,0 +1,2 @@
+ip
+rtmon
diff --git a/ip/Makefile b/ip/Makefile
new file mode 100644
index 0000000..3535ba7
--- /dev/null
+++ b/ip/Makefile
@@ -0,0 +1,57 @@
+# SPDX-License-Identifier: GPL-2.0
+IPOBJ=ip.o ipaddress.o ipaddrlabel.o iproute.o iprule.o ipnetns.o \
+ rtm_map.o iptunnel.o ip6tunnel.o tunnel.o ipneigh.o ipntable.o iplink.o \
+ ipmaddr.o ipmonitor.o ipmroute.o ipprefix.o iptuntap.o iptoken.o \
+ ipxfrm.o xfrm_state.o xfrm_policy.o xfrm_monitor.o iplink_dummy.o \
+ iplink_ifb.o iplink_nlmon.o iplink_team.o iplink_vcan.o iplink_vxcan.o \
+ iplink_vlan.o link_veth.o link_gre.o iplink_can.o iplink_xdp.o \
+ iplink_macvlan.o ipl2tp.o link_vti.o link_vti6.o link_xfrm.o \
+ iplink_vxlan.o tcp_metrics.o iplink_ipoib.o ipnetconf.o link_ip6tnl.o \
+ link_iptnl.o link_gre6.o iplink_bond.o iplink_bond_slave.o iplink_hsr.o \
+ iplink_bridge.o iplink_bridge_slave.o iplink_dsa.o ipfou.o iplink_ipvlan.o \
+ iplink_geneve.o iplink_vrf.o iproute_lwtunnel.o ipmacsec.o ipila.o \
+ ipvrf.o iplink_xstats.o ipseg6.o iplink_netdevsim.o iplink_rmnet.o \
+ ipnexthop.o ipmptcp.o iplink_bareudp.o iplink_wwan.o ipioam6.o \
+ iplink_amt.o iplink_batadv.o iplink_gtp.o iplink_virt_wifi.o \
+ iplink_netkit.o ipstats.o
+
+RTMONOBJ=rtmon.o
+
+include ../config.mk
+
+ALLOBJ=$(IPOBJ) $(RTMONOBJ)
+SCRIPTS=routel
+TARGETS=ip rtmon
+
+all: $(TARGETS) $(SCRIPTS)
+
+ip: $(IPOBJ) $(LIBNETLINK)
+ $(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
+
+rtmon: $(RTMONOBJ)
+ $(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
+
+install: all
+ install -m 0755 $(TARGETS) $(DESTDIR)$(SBINDIR)
+ install -m 0755 $(SCRIPTS) $(DESTDIR)$(SBINDIR)
+
+clean:
+ rm -f $(ALLOBJ) $(TARGETS)
+
+SHARED_LIBS ?= y
+ifeq ($(SHARED_LIBS),y)
+
+LDLIBS += -ldl
+LDFLAGS += -Wl,-export-dynamic
+
+else
+
+ip: static-syms.o
+static-syms.o: static-syms.h
+static-syms.h: $(wildcard *.c)
+ files="$^" ; \
+ for s in `grep -B 3 '\<dlsym' $$files | sed -n '/snprintf/{s:.*"\([^"]*\)".*:\1:;s:%s::;p}'` ; do \
+ sed -n '/'$$s'[^ ]* =/{s:.* \([^ ]*'$$s'[^ ]*\) .*:extern char \1[] __attribute__((weak)); if (!strcmp(sym, "\1")) return \1;:;p}' $$files ; \
+ done > $@
+
+endif
diff --git a/ip/ila_common.h b/ip/ila_common.h
new file mode 100644
index 0000000..f99c267
--- /dev/null
+++ b/ip/ila_common.h
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ILA_COMMON_H_
+#define _ILA_COMMON_H_
+
+#include <linux/ila.h>
+#include <string.h>
+
+static inline char *ila_csum_mode2name(__u8 csum_mode)
+{
+ switch (csum_mode) {
+ case ILA_CSUM_ADJUST_TRANSPORT:
+ return "adj-transport";
+ case ILA_CSUM_NEUTRAL_MAP:
+ return "neutral-map";
+ case ILA_CSUM_NO_ACTION:
+ return "no-action";
+ case ILA_CSUM_NEUTRAL_MAP_AUTO:
+ return "neutral-map-auto";
+ default:
+ return "unknown";
+ }
+}
+
+static inline int ila_csum_name2mode(char *name)
+{
+ if (strcmp(name, "adj-transport") == 0)
+ return ILA_CSUM_ADJUST_TRANSPORT;
+ else if (strcmp(name, "neutral-map") == 0)
+ return ILA_CSUM_NEUTRAL_MAP;
+ else if (strcmp(name, "neutral-map-auto") == 0)
+ return ILA_CSUM_NEUTRAL_MAP_AUTO;
+ else if (strcmp(name, "no-action") == 0)
+ return ILA_CSUM_NO_ACTION;
+ else if (strcmp(name, "neutral-map-auto") == 0)
+ return ILA_CSUM_NEUTRAL_MAP_AUTO;
+ else
+ return -1;
+}
+
+static inline char *ila_ident_type2name(__u8 ident_type)
+{
+ switch (ident_type) {
+ case ILA_ATYPE_IID:
+ return "iid";
+ case ILA_ATYPE_LUID:
+ return "luid";
+ case ILA_ATYPE_VIRT_V4:
+ return "virt-v4";
+ case ILA_ATYPE_VIRT_UNI_V6:
+ return "virt-uni-v6";
+ case ILA_ATYPE_VIRT_MULTI_V6:
+ return "virt-multi-v6";
+ case ILA_ATYPE_NONLOCAL_ADDR:
+ return "nonlocal-addr";
+ case ILA_ATYPE_USE_FORMAT:
+ return "use-format";
+ default:
+ return "unknown";
+ }
+}
+
+static inline int ila_ident_name2type(char *name)
+{
+ if (!strcmp(name, "luid"))
+ return ILA_ATYPE_LUID;
+ else if (!strcmp(name, "use-format"))
+ return ILA_ATYPE_USE_FORMAT;
+#if 0 /* No kernel support for configuring these yet */
+ else if (!strcmp(name, "iid"))
+ return ILA_ATYPE_IID;
+ else if (!strcmp(name, "virt-v4"))
+ return ILA_ATYPE_VIRT_V4;
+ else if (!strcmp(name, "virt-uni-v6"))
+ return ILA_ATYPE_VIRT_UNI_V6;
+ else if (!strcmp(name, "virt-multi-v6"))
+ return ILA_ATYPE_VIRT_MULTI_V6;
+ else if (!strcmp(name, "nonlocal-addr"))
+ return ILA_ATYPE_NONLOCAL_ADDR;
+#endif
+ else
+ return -1;
+}
+
+static inline char *ila_hook_type2name(__u8 hook_type)
+{
+ switch (hook_type) {
+ case ILA_HOOK_ROUTE_OUTPUT:
+ return "output";
+ case ILA_HOOK_ROUTE_INPUT:
+ return "input";
+ default:
+ return "unknown";
+ }
+}
+
+static inline int ila_hook_name2type(char *name)
+{
+ if (!strcmp(name, "output"))
+ return ILA_HOOK_ROUTE_OUTPUT;
+ else if (!strcmp(name, "input"))
+ return ILA_HOOK_ROUTE_INPUT;
+ else
+ return -1;
+}
+
+#endif /* _ILA_COMMON_H_ */
diff --git a/ip/ip.c b/ip/ip.c
new file mode 100644
index 0000000..e51fa20
--- /dev/null
+++ b/ip/ip.c
@@ -0,0 +1,327 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * ip.c "ip" utility frontend.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <errno.h>
+
+#include "version.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "namespace.h"
+#include "color.h"
+#include "rt_names.h"
+#include "bpf_util.h"
+
+#ifndef LIBDIR
+#define LIBDIR "/usr/lib"
+#endif
+
+int preferred_family = AF_UNSPEC;
+int human_readable;
+int use_iec;
+int show_stats;
+int show_details;
+int oneline;
+int brief;
+int json;
+int timestamp;
+int echo_request;
+int force;
+int max_flush_loops = 10;
+int batch_mode;
+bool do_all;
+
+struct rtnl_handle rth = { .fd = -1 };
+
+const char *get_ip_lib_dir(void)
+{
+ const char *lib_dir;
+
+ lib_dir = getenv("IP_LIB_DIR");
+ if (!lib_dir)
+ lib_dir = LIBDIR "/ip";
+
+ return lib_dir;
+}
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }\n"
+ " ip [ -force ] -batch filename\n"
+ "where OBJECT := { address | addrlabel | fou | help | ila | ioam | l2tp | link |\n"
+ " macsec | maddress | monitor | mptcp | mroute | mrule |\n"
+ " neighbor | neighbour | netconf | netns | nexthop | ntable |\n"
+ " ntbl | route | rule | sr | stats | tap | tcpmetrics |\n"
+ " token | tunnel | tuntap | vrf | xfrm }\n"
+ " OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |\n"
+ " -h[uman-readable] | -iec | -j[son] | -p[retty] |\n"
+ " -f[amily] { inet | inet6 | mpls | bridge | link } |\n"
+ " -4 | -6 | -M | -B | -0 |\n"
+ " -l[oops] { maximum-addr-flush-attempts } | -echo | -br[ief] |\n"
+ " -o[neline] | -t[imestamp] | -ts[hort] | -b[atch] [filename] |\n"
+ " -rc[vbuf] [size] | -n[etns] name | -N[umeric] | -a[ll] |\n"
+ " -c[olor]}\n");
+ exit(-1);
+}
+
+static int do_help(int argc, char **argv)
+{
+ usage();
+ return 0;
+}
+
+static const struct cmd {
+ const char *cmd;
+ int (*func)(int argc, char **argv);
+} cmds[] = {
+ { "address", do_ipaddr },
+ { "addrlabel", do_ipaddrlabel },
+ { "maddress", do_multiaddr },
+ { "route", do_iproute },
+ { "rule", do_iprule },
+ { "neighbor", do_ipneigh },
+ { "neighbour", do_ipneigh },
+ { "ntable", do_ipntable },
+ { "ntbl", do_ipntable },
+ { "link", do_iplink },
+ { "l2tp", do_ipl2tp },
+ { "fou", do_ipfou },
+ { "ila", do_ipila },
+ { "macsec", do_ipmacsec },
+ { "tunnel", do_iptunnel },
+ { "tunl", do_iptunnel },
+ { "tuntap", do_iptuntap },
+ { "tap", do_iptuntap },
+ { "token", do_iptoken },
+ { "tcpmetrics", do_tcp_metrics },
+ { "tcp_metrics", do_tcp_metrics },
+ { "monitor", do_ipmonitor },
+ { "xfrm", do_xfrm },
+ { "mroute", do_multiroute },
+ { "mrule", do_multirule },
+ { "netns", do_netns },
+ { "netconf", do_ipnetconf },
+ { "vrf", do_ipvrf},
+ { "sr", do_seg6 },
+ { "nexthop", do_ipnh },
+ { "mptcp", do_mptcp },
+ { "ioam", do_ioam6 },
+ { "help", do_help },
+ { "stats", do_ipstats },
+ { 0 }
+};
+
+static int do_cmd(const char *argv0, int argc, char **argv, bool final)
+{
+ const struct cmd *c;
+
+ for (c = cmds; c->cmd; ++c) {
+ if (matches(argv0, c->cmd) == 0)
+ return -(c->func(argc-1, argv+1));
+ }
+
+ if (final)
+ fprintf(stderr, "Object \"%s\" is unknown, try \"ip help\".\n", argv0);
+ return EXIT_FAILURE;
+}
+
+static int ip_batch_cmd(int argc, char *argv[], void *data)
+{
+ const int *orig_family = data;
+
+ preferred_family = *orig_family;
+ return do_cmd(argv[0], argc, argv, true);
+}
+
+static int batch(const char *name)
+{
+ int orig_family = preferred_family;
+ int ret;
+
+ if (rtnl_open(&rth, 0) < 0) {
+ fprintf(stderr, "Cannot open rtnetlink\n");
+ return EXIT_FAILURE;
+ }
+
+ batch_mode = 1;
+ ret = do_batch(name, force, ip_batch_cmd, &orig_family);
+
+ rtnl_close(&rth);
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+ const char *libbpf_version;
+ char *batch_file = NULL;
+ char *basename;
+ int color = CONF_COLOR;
+
+ /* to run vrf exec without root, capabilities might be set, drop them
+ * if not needed as the first thing.
+ * execv will drop them for the child command.
+ * vrf exec requires:
+ * - cap_dac_override to create the cgroup subdir in /sys
+ * - cap_bpf to load the BPF program
+ * - cap_net_admin to set the socket into the cgroup
+ */
+ if (argc < 3 || strcmp(argv[1], "vrf") != 0 ||
+ strcmp(argv[2], "exec") != 0)
+ drop_cap();
+
+ basename = strrchr(argv[0], '/');
+ if (basename == NULL)
+ basename = argv[0];
+ else
+ basename++;
+
+ while (argc > 1) {
+ char *opt = argv[1];
+
+ if (strcmp(opt, "--") == 0) {
+ argc--; argv++;
+ break;
+ }
+ if (opt[0] != '-')
+ break;
+ if (opt[1] == '-')
+ opt++;
+ if (matches(opt, "-loops") == 0) {
+ argc--;
+ argv++;
+ if (argc <= 1)
+ usage();
+ max_flush_loops = atoi(argv[1]);
+ } else if (matches(opt, "-family") == 0) {
+ argc--;
+ argv++;
+ if (argc <= 1)
+ usage();
+ if (strcmp(argv[1], "help") == 0)
+ usage();
+ else
+ preferred_family = read_family(argv[1]);
+ if (preferred_family == AF_UNSPEC)
+ invarg("invalid protocol family", argv[1]);
+ } else if (strcmp(opt, "-4") == 0) {
+ preferred_family = AF_INET;
+ } else if (strcmp(opt, "-6") == 0) {
+ preferred_family = AF_INET6;
+ } else if (strcmp(opt, "-0") == 0) {
+ preferred_family = AF_PACKET;
+ } else if (strcmp(opt, "-M") == 0) {
+ preferred_family = AF_MPLS;
+ } else if (strcmp(opt, "-B") == 0) {
+ preferred_family = AF_BRIDGE;
+ } else if (matches(opt, "-human") == 0 ||
+ matches(opt, "-human-readable") == 0) {
+ ++human_readable;
+ } else if (matches(opt, "-iec") == 0) {
+ ++use_iec;
+ } else if (matches(opt, "-stats") == 0 ||
+ matches(opt, "-statistics") == 0) {
+ ++show_stats;
+ } else if (matches(opt, "-details") == 0) {
+ ++show_details;
+ } else if (matches(opt, "-resolve") == 0) {
+ ++resolve_hosts;
+ } else if (matches(opt, "-oneline") == 0) {
+ ++oneline;
+ } else if (matches(opt, "-timestamp") == 0) {
+ ++timestamp;
+ } else if (matches(opt, "-tshort") == 0) {
+ ++timestamp;
+ ++timestamp_short;
+ } else if (matches(opt, "-Version") == 0) {
+ printf("ip utility, iproute2-%s", version);
+ libbpf_version = get_libbpf_version();
+ if (libbpf_version)
+ printf(", libbpf %s", libbpf_version);
+ printf("\n");
+ exit(0);
+ } else if (matches(opt, "-force") == 0) {
+ ++force;
+ } else if (matches(opt, "-batch") == 0) {
+ argc--;
+ argv++;
+ if (argc <= 1)
+ usage();
+ batch_file = argv[1];
+ } else if (matches(opt, "-brief") == 0) {
+ ++brief;
+ } else if (matches(opt, "-json") == 0) {
+ ++json;
+ } else if (matches(opt, "-pretty") == 0) {
+ ++pretty;
+ } else if (matches(opt, "-rcvbuf") == 0) {
+ unsigned int size;
+
+ argc--;
+ argv++;
+ if (argc <= 1)
+ usage();
+ if (get_unsigned(&size, argv[1], 0)) {
+ fprintf(stderr, "Invalid rcvbuf size '%s'\n",
+ argv[1]);
+ exit(-1);
+ }
+ rcvbuf = size;
+ } else if (matches_color(opt, &color)) {
+ } else if (matches(opt, "-help") == 0) {
+ usage();
+ } else if (matches(opt, "-netns") == 0) {
+ NEXT_ARG();
+ if (netns_switch(argv[1]))
+ exit(-1);
+ } else if (matches(opt, "-Numeric") == 0) {
+ ++numeric;
+ } else if (matches(opt, "-all") == 0) {
+ do_all = true;
+ } else if (strcmp(opt, "-echo") == 0) {
+ ++echo_request;
+ } else {
+ fprintf(stderr,
+ "Option \"%s\" is unknown, try \"ip -help\".\n",
+ opt);
+ exit(-1);
+ }
+ argc--; argv++;
+ }
+
+ _SL_ = oneline ? "\\" : "\n";
+
+ check_enable_color(color, json);
+
+ if (batch_file)
+ return batch(batch_file);
+
+ if (rtnl_open(&rth, 0) < 0)
+ exit(1);
+
+ rtnl_set_strict_dump(&rth);
+
+ if (strlen(basename) > 2) {
+ int ret = do_cmd(basename+2, argc, argv, false);
+ if (ret != EXIT_FAILURE)
+ return ret;
+ }
+
+ if (argc > 1)
+ return do_cmd(argv[1], argc-1, argv+1, true);
+
+ rtnl_close(&rth);
+ usage();
+}
diff --git a/ip/ip6tunnel.c b/ip/ip6tunnel.c
new file mode 100644
index 0000000..347bd46
--- /dev/null
+++ b/ip/ip6tunnel.c
@@ -0,0 +1,437 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C)2006 USAGI/WIDE Project
+ *
+ * Author:
+ * Masahide NAKAMURA @USAGI
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <linux/ip.h>
+#include <linux/if.h>
+#include <linux/if_arp.h>
+#include <linux/if_tunnel.h>
+#include <linux/ip6_tunnel.h>
+
+#include "utils.h"
+#include "tunnel.h"
+#include "ip_common.h"
+
+#define IP6_FLOWINFO_TCLASS htonl(0x0FF00000)
+#define IP6_FLOWINFO_FLOWLABEL htonl(0x000FFFFF)
+
+#define DEFAULT_TNL_HOP_LIMIT (64)
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip -f inet6 tunnel { add | change | del | show } [ NAME ]\n"
+ " [ mode { ip6ip6 | ipip6 | ip6gre | vti6 | any } ]\n"
+ " [ remote ADDR local ADDR ] [ dev PHYS_DEV ]\n"
+ " [ encaplimit ELIM ]\n"
+ " [ hoplimit TTL ] [ tclass TCLASS ] [ flowlabel FLOWLABEL ]\n"
+ " [ dscp inherit ]\n"
+ " [ [no]allow-localremote ]\n"
+ " [ [i|o]seq ] [ [i|o]key KEY ] [ [i|o]csum ]\n"
+ "\n"
+ "Where: NAME := STRING\n"
+ " ADDR := IPV6_ADDRESS\n"
+ " ELIM := { none | 0..255 }(default=%d)\n"
+ " TTL := 0..255 (default=%d)\n"
+ " TCLASS := { 0x0..0xff | inherit }\n"
+ " FLOWLABEL := { 0x0..0xfffff | inherit }\n"
+ " KEY := { DOTTED_QUAD | NUMBER }\n",
+ IPV6_DEFAULT_TNL_ENCAP_LIMIT,
+ DEFAULT_TNL_HOP_LIMIT);
+ exit(-1);
+}
+
+static void print_tunnel(const void *t)
+{
+ const struct ip6_tnl_parm2 *p = t;
+ SPRINT_BUF(b1);
+
+ /* Do not use format_host() for local addr,
+ * symbolic name will not be useful.
+ */
+ open_json_object(NULL);
+ print_color_string(PRINT_ANY, COLOR_IFNAME, "ifname", "%s: ", p->name);
+ snprintf(b1, sizeof(b1), "%s/ipv6", tnl_strproto(p->proto));
+ print_string(PRINT_ANY, "mode", "%s ", b1);
+ print_string(PRINT_FP, NULL, "%s", "remote ");
+ print_color_string(PRINT_ANY, COLOR_INET6, "remote", "%s ",
+ format_host_r(AF_INET6, 16, &p->raddr, b1, sizeof(b1)));
+ print_string(PRINT_FP, NULL, "%s", "local ");
+ print_color_string(PRINT_ANY, COLOR_INET6, "local", "%s",
+ rt_addr_n2a_r(AF_INET6, 16, &p->laddr, b1, sizeof(b1)));
+
+ if (p->link) {
+ const char *n = ll_index_to_name(p->link);
+
+ if (n)
+ print_string(PRINT_ANY, "link", " dev %s", n);
+ }
+
+ if (p->flags & IP6_TNL_F_IGN_ENCAP_LIMIT)
+ print_null(PRINT_ANY, "ip6_tnl_f_ign_encap_limit",
+ " encaplimit none", NULL);
+ else
+ print_uint(PRINT_ANY, "encap_limit", " encaplimit %u",
+ p->encap_limit);
+
+ if (p->hop_limit)
+ print_uint(PRINT_ANY, "hoplimit", " hoplimit %u", p->hop_limit);
+ else
+ print_string(PRINT_FP, "hoplimit", " hoplimit %s", "inherit");
+
+ if (p->flags & IP6_TNL_F_USE_ORIG_TCLASS) {
+ print_null(PRINT_ANY, "ip6_tnl_f_use_orig_tclass",
+ " tclass inherit", NULL);
+ } else {
+ __u32 val = ntohl(p->flowinfo & IP6_FLOWINFO_TCLASS);
+
+ snprintf(b1, sizeof(b1), "0x%02x", (__u8)(val >> 20));
+ print_string(PRINT_ANY, "tclass", " tclass %s", b1);
+ }
+
+ if (p->flags & IP6_TNL_F_USE_ORIG_FLOWLABEL) {
+ print_null(PRINT_ANY, "ip6_tnl_f_use_orig_flowlabel",
+ " flowlabel inherit", NULL);
+ } else {
+ __u32 val = ntohl(p->flowinfo & IP6_FLOWINFO_FLOWLABEL);
+
+ snprintf(b1, sizeof(b1), "0x%05x", val);
+ print_string(PRINT_ANY, "flowlabel", " flowlabel %s", b1);
+ }
+
+ snprintf(b1, sizeof(b1), "0x%08x", ntohl(p->flowinfo));
+ print_string(PRINT_ANY, "flowinfo", " (flowinfo %s)", b1);
+
+ if (p->flags & IP6_TNL_F_RCV_DSCP_COPY)
+ print_null(PRINT_ANY, "ip6_tnl_f_rcv_dscp_copy",
+ " dscp inherit", NULL);
+
+ if (p->flags & IP6_TNL_F_ALLOW_LOCAL_REMOTE)
+ print_null(PRINT_ANY, "ip6_tnl_f_allow_local_remote",
+ " allow-localremote", NULL);
+
+ tnl_print_gre_flags(p->proto, p->i_flags, p->o_flags,
+ p->i_key, p->o_key);
+
+ close_json_object();
+}
+
+static int parse_args(int argc, char **argv, int cmd, struct ip6_tnl_parm2 *p)
+{
+ int count = 0;
+ const char *medium = NULL;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "mode") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "ipv6/ipv6") == 0 ||
+ strcmp(*argv, "ip6ip6") == 0)
+ p->proto = IPPROTO_IPV6;
+ else if (strcmp(*argv, "vti6") == 0) {
+ p->proto = IPPROTO_IPV6;
+ p->i_flags |= VTI_ISVTI;
+ } else if (strcmp(*argv, "ip/ipv6") == 0 ||
+ strcmp(*argv, "ipv4/ipv6") == 0 ||
+ strcmp(*argv, "ipip6") == 0 ||
+ strcmp(*argv, "ip4ip6") == 0)
+ p->proto = IPPROTO_IPIP;
+ else if (strcmp(*argv, "ip6gre") == 0 ||
+ strcmp(*argv, "gre/ipv6") == 0)
+ p->proto = IPPROTO_GRE;
+ else if (strcmp(*argv, "any/ipv6") == 0 ||
+ strcmp(*argv, "any") == 0)
+ p->proto = 0;
+ else {
+ fprintf(stderr, "Unknown tunnel mode \"%s\"\n", *argv);
+ exit(-1);
+ }
+ } else if (strcmp(*argv, "remote") == 0) {
+ inet_prefix raddr;
+
+ NEXT_ARG();
+ get_addr(&raddr, *argv, AF_INET6);
+ memcpy(&p->raddr, &raddr.data, sizeof(p->raddr));
+ } else if (strcmp(*argv, "local") == 0) {
+ inet_prefix laddr;
+
+ NEXT_ARG();
+ get_addr(&laddr, *argv, AF_INET6);
+ memcpy(&p->laddr, &laddr.data, sizeof(p->laddr));
+ } else if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ medium = *argv;
+ } else if (strcmp(*argv, "encaplimit") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "none") == 0) {
+ p->flags |= IP6_TNL_F_IGN_ENCAP_LIMIT;
+ } else {
+ __u8 uval;
+
+ if (get_u8(&uval, *argv, 0) < -1)
+ invarg("invalid ELIM", *argv);
+ p->encap_limit = uval;
+ p->flags &= ~IP6_TNL_F_IGN_ENCAP_LIMIT;
+ }
+ } else if (strcmp(*argv, "hoplimit") == 0 ||
+ strcmp(*argv, "ttl") == 0 ||
+ strcmp(*argv, "hlim") == 0) {
+ __u8 uval;
+
+ NEXT_ARG();
+ if (get_u8(&uval, *argv, 0))
+ invarg("invalid TTL", *argv);
+ p->hop_limit = uval;
+ } else if (strcmp(*argv, "tclass") == 0 ||
+ strcmp(*argv, "tc") == 0 ||
+ strcmp(*argv, "tos") == 0 ||
+ matches(*argv, "dsfield") == 0) {
+ __u8 uval;
+
+ NEXT_ARG();
+ p->flowinfo &= ~IP6_FLOWINFO_TCLASS;
+ if (strcmp(*argv, "inherit") == 0)
+ p->flags |= IP6_TNL_F_USE_ORIG_TCLASS;
+ else {
+ if (get_u8(&uval, *argv, 16))
+ invarg("invalid TClass", *argv);
+ p->flowinfo |= htonl((__u32)uval << 20) & IP6_FLOWINFO_TCLASS;
+ p->flags &= ~IP6_TNL_F_USE_ORIG_TCLASS;
+ }
+ } else if (strcmp(*argv, "flowlabel") == 0 ||
+ strcmp(*argv, "fl") == 0) {
+ __u32 uval;
+
+ NEXT_ARG();
+ p->flowinfo &= ~IP6_FLOWINFO_FLOWLABEL;
+ if (strcmp(*argv, "inherit") == 0)
+ p->flags |= IP6_TNL_F_USE_ORIG_FLOWLABEL;
+ else {
+ if (get_u32(&uval, *argv, 16))
+ invarg("invalid Flowlabel", *argv);
+ if (uval > 0xFFFFF)
+ invarg("invalid Flowlabel", *argv);
+ p->flowinfo |= htonl(uval) & IP6_FLOWINFO_FLOWLABEL;
+ p->flags &= ~IP6_TNL_F_USE_ORIG_FLOWLABEL;
+ }
+ } else if (strcmp(*argv, "dscp") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "inherit") != 0)
+ invarg("not inherit", *argv);
+ p->flags |= IP6_TNL_F_RCV_DSCP_COPY;
+ } else if (strcmp(*argv, "allow-localremote") == 0) {
+ p->flags |= IP6_TNL_F_ALLOW_LOCAL_REMOTE;
+ } else if (strcmp(*argv, "noallow-localremote") == 0) {
+ p->flags &= ~IP6_TNL_F_ALLOW_LOCAL_REMOTE;
+ } else if (strcmp(*argv, "key") == 0) {
+ NEXT_ARG();
+ p->i_flags |= GRE_KEY;
+ p->o_flags |= GRE_KEY;
+ p->i_key = p->o_key = tnl_parse_key("key", *argv);
+ } else if (strcmp(*argv, "ikey") == 0) {
+ NEXT_ARG();
+ p->i_flags |= GRE_KEY;
+ p->i_key = tnl_parse_key("ikey", *argv);
+ } else if (strcmp(*argv, "okey") == 0) {
+ NEXT_ARG();
+ p->o_flags |= GRE_KEY;
+ p->o_key = tnl_parse_key("okey", *argv);
+ } else if (strcmp(*argv, "seq") == 0) {
+ p->i_flags |= GRE_SEQ;
+ p->o_flags |= GRE_SEQ;
+ } else if (strcmp(*argv, "iseq") == 0) {
+ p->i_flags |= GRE_SEQ;
+ } else if (strcmp(*argv, "oseq") == 0) {
+ p->o_flags |= GRE_SEQ;
+ } else if (strcmp(*argv, "csum") == 0) {
+ p->i_flags |= GRE_CSUM;
+ p->o_flags |= GRE_CSUM;
+ } else if (strcmp(*argv, "icsum") == 0) {
+ p->i_flags |= GRE_CSUM;
+ } else if (strcmp(*argv, "ocsum") == 0) {
+ p->o_flags |= GRE_CSUM;
+ } else {
+ if (strcmp(*argv, "name") == 0) {
+ NEXT_ARG();
+ } else if (matches(*argv, "help") == 0)
+ usage();
+ if (p->name[0])
+ duparg2("name", *argv);
+ if (get_ifname(p->name, *argv))
+ invarg("\"name\" not a valid ifname", *argv);
+ if (cmd == SIOCCHGTUNNEL && count == 0) {
+ struct ip6_tnl_parm2 old_p = {};
+
+ if (tnl_get_ioctl(*argv, &old_p))
+ return -1;
+ *p = old_p;
+ }
+ }
+ count++;
+ argc--; argv++;
+ }
+ if (medium) {
+ p->link = ll_name_to_index(medium);
+ if (!p->link)
+ return nodev(medium);
+ }
+ return 0;
+}
+
+static void ip6_tnl_parm_init(struct ip6_tnl_parm2 *p, int apply_default)
+{
+ memset(p, 0, sizeof(*p));
+ p->proto = IPPROTO_IPV6;
+ if (apply_default) {
+ p->hop_limit = DEFAULT_TNL_HOP_LIMIT;
+ p->encap_limit = IPV6_DEFAULT_TNL_ENCAP_LIMIT;
+ }
+}
+
+static void ip6_tnl_parm_initialize(const struct tnl_print_nlmsg_info *info)
+{
+ const struct ifinfomsg *ifi = info->ifi;
+ const struct ip6_tnl_parm2 *p1 = info->p1;
+ struct ip6_tnl_parm2 *p2 = info->p2;
+
+ ip6_tnl_parm_init(p2, 0);
+ if (ifi->ifi_type == ARPHRD_IP6GRE)
+ p2->proto = IPPROTO_GRE;
+ p2->link = ifi->ifi_index;
+ strcpy(p2->name, p1->name);
+}
+
+static bool ip6_tnl_parm_match(const struct tnl_print_nlmsg_info *info)
+{
+ const struct ip6_tnl_parm2 *p1 = info->p1;
+ const struct ip6_tnl_parm2 *p2 = info->p2;
+
+ return ((!p1->link || p1->link == p2->link) &&
+ (!p1->name[0] || strcmp(p1->name, p2->name) == 0) &&
+ (IN6_IS_ADDR_UNSPECIFIED(&p1->laddr) ||
+ IN6_ARE_ADDR_EQUAL(&p1->laddr, &p2->laddr)) &&
+ (IN6_IS_ADDR_UNSPECIFIED(&p1->raddr) ||
+ IN6_ARE_ADDR_EQUAL(&p1->raddr, &p2->raddr)) &&
+ (!p1->proto || !p2->proto || p1->proto == p2->proto) &&
+ (!p1->encap_limit || p1->encap_limit == p2->encap_limit) &&
+ (!p1->hop_limit || p1->hop_limit == p2->hop_limit) &&
+ (!(p1->flowinfo & IP6_FLOWINFO_TCLASS) ||
+ !((p1->flowinfo ^ p2->flowinfo) & IP6_FLOWINFO_TCLASS)) &&
+ (!(p1->flowinfo & IP6_FLOWINFO_FLOWLABEL) ||
+ !((p1->flowinfo ^ p2->flowinfo) & IP6_FLOWINFO_FLOWLABEL)) &&
+ (!p1->flags || (p1->flags & p2->flags)));
+}
+
+static int do_show(int argc, char **argv)
+{
+ struct ip6_tnl_parm2 p, p1;
+
+ ip6_tnl_parm_init(&p, 0);
+ p.proto = 0; /* default to any */
+
+ if (parse_args(argc, argv, SIOCGETTUNNEL, &p) < 0)
+ return -1;
+
+ if (!p.name[0] || show_stats) {
+ struct tnl_print_nlmsg_info info = {
+ .p1 = &p,
+ .p2 = &p1,
+ .init = ip6_tnl_parm_initialize,
+ .match = ip6_tnl_parm_match,
+ .print = print_tunnel,
+ };
+
+ return do_tunnels_list(&info);
+ }
+
+ if (tnl_get_ioctl(p.name, &p))
+ return -1;
+
+ print_tunnel(&p);
+ return 0;
+}
+
+static int do_add(int cmd, int argc, char **argv)
+{
+ struct ip6_tnl_parm2 p;
+ const char *basedev = "ip6tnl0";
+
+ ip6_tnl_parm_init(&p, 1);
+
+ if (parse_args(argc, argv, cmd, &p) < 0)
+ return -1;
+
+ if (!*p.name)
+ fprintf(stderr, "Tunnel interface name not specified\n");
+
+ if (p.proto == IPPROTO_GRE)
+ basedev = "ip6gre0";
+ else if (p.i_flags & VTI_ISVTI)
+ basedev = "ip6_vti0";
+
+ return tnl_add_ioctl(cmd, basedev, p.name, &p);
+}
+
+static int do_del(int argc, char **argv)
+{
+ struct ip6_tnl_parm2 p;
+ const char *basedev = "ip6tnl0";
+
+ ip6_tnl_parm_init(&p, 1);
+
+ if (parse_args(argc, argv, SIOCDELTUNNEL, &p) < 0)
+ return -1;
+
+ if (p.proto == IPPROTO_GRE)
+ basedev = "ip6gre0";
+ else if (p.i_flags & VTI_ISVTI)
+ basedev = "ip6_vti0";
+
+ return tnl_del_ioctl(basedev, p.name, &p);
+}
+
+int do_ip6tunnel(int argc, char **argv)
+{
+ switch (preferred_family) {
+ case AF_UNSPEC:
+ preferred_family = AF_INET6;
+ break;
+ case AF_INET6:
+ break;
+ default:
+ fprintf(stderr, "Unsupported protocol family: %d\n", preferred_family);
+ exit(-1);
+ }
+
+ if (argc > 0) {
+ if (matches(*argv, "add") == 0)
+ return do_add(SIOCADDTUNNEL, argc - 1, argv + 1);
+ if (matches(*argv, "change") == 0)
+ return do_add(SIOCCHGTUNNEL, argc - 1, argv + 1);
+ if (matches(*argv, "delete") == 0)
+ return do_del(argc - 1, argv + 1);
+ if (matches(*argv, "show") == 0 ||
+ matches(*argv, "lst") == 0 ||
+ matches(*argv, "list") == 0)
+ return do_show(argc - 1, argv + 1);
+ if (matches(*argv, "help") == 0)
+ usage();
+ } else
+ return do_show(0, NULL);
+
+ fprintf(stderr, "Command \"%s\" is unknown, try \"ip -f inet6 tunnel help\".\n", *argv);
+ exit(-1);
+}
diff --git a/ip/ip_common.h b/ip/ip_common.h
new file mode 100644
index 0000000..b65c2b4
--- /dev/null
+++ b/ip/ip_common.h
@@ -0,0 +1,234 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _IP_COMMON_H_
+#define _IP_COMMON_H_
+
+#include <stdbool.h>
+#include <linux/mpls.h>
+
+#include "json_print.h"
+
+extern int use_iec;
+
+struct link_filter {
+ int ifindex;
+ int family;
+ int oneline;
+ int showqueue;
+ inet_prefix pfx;
+ int scope, scopemask;
+ int flags, flagmask;
+ int up;
+ char *label;
+ int flushed;
+ char *flushb;
+ int flushp;
+ int flushe;
+ int group;
+ int master;
+ char *kind;
+ char *slave_kind;
+ int target_nsid;
+ bool have_proto;
+ int proto;
+};
+
+const char *get_ip_lib_dir(void);
+
+int get_operstate(const char *name);
+int print_linkinfo(struct nlmsghdr *n, void *arg);
+int print_addrinfo(struct nlmsghdr *n, void *arg);
+int print_addrlabel(struct nlmsghdr *n, void *arg);
+int print_neigh(struct nlmsghdr *n, void *arg);
+int ipaddr_list_link(int argc, char **argv);
+void ipaddr_get_vf_rate(int, int *, int *, const char *);
+void iplink_usage(void) __attribute__((noreturn));
+void iplink_types_usage(void);
+
+void iproute_reset_filter(int ifindex);
+void ipmroute_reset_filter(int ifindex);
+void ipaddr_reset_filter(int oneline, int ifindex);
+void ipneigh_reset_filter(int ifindex);
+void ipnetconf_reset_filter(int ifindex);
+
+int print_route(struct nlmsghdr *n, void *arg);
+int print_mroute(struct nlmsghdr *n, void *arg);
+int print_prefix(struct nlmsghdr *n, void *arg);
+int print_rule(struct nlmsghdr *n, void *arg);
+int print_netconf(struct rtnl_ctrl_data *ctrl,
+ struct nlmsghdr *n, void *arg);
+int print_nexthop_bucket(struct nlmsghdr *n, void *arg);
+void netns_map_init(void);
+void netns_nsid_socket_init(void);
+int print_nsid(struct nlmsghdr *n, void *arg);
+int ipstats_print(struct nlmsghdr *n, void *arg);
+char *get_name_from_nsid(int nsid);
+int get_netnsid_from_name(const char *name);
+int set_netnsid_from_name(const char *name, int nsid);
+int do_ipaddr(int argc, char **argv);
+int do_ipaddrlabel(int argc, char **argv);
+int do_iproute(int argc, char **argv);
+int do_iprule(int argc, char **argv);
+int do_ipneigh(int argc, char **argv);
+int do_ipntable(int argc, char **argv);
+int do_iptunnel(int argc, char **argv);
+int do_ip6tunnel(int argc, char **argv);
+int do_iptuntap(int argc, char **argv);
+int do_iplink(int argc, char **argv);
+int do_ipmacsec(int argc, char **argv);
+int do_ipmonitor(int argc, char **argv);
+int do_multiaddr(int argc, char **argv);
+int do_multiroute(int argc, char **argv);
+int do_multirule(int argc, char **argv);
+int do_netns(int argc, char **argv);
+int do_xfrm(int argc, char **argv);
+int do_ipl2tp(int argc, char **argv);
+int do_ipfou(int argc, char **argv);
+int do_ipila(int argc, char **argv);
+int do_tcp_metrics(int argc, char **argv);
+int do_ipnetconf(int argc, char **argv);
+int do_iptoken(int argc, char **argv);
+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 do_mptcp(int argc, char **argv);
+int do_ioam6(int argc, char **argv);
+int do_ipstats(int argc, char **argv);
+
+int iplink_get(char *name, __u32 filt_mask);
+int iplink_ifla_xstats(int argc, char **argv);
+
+int ip_link_list(req_filter_fn_t filter_fn, struct nlmsg_chain *linfo);
+void free_nlmsg_chain(struct nlmsg_chain *info);
+
+static inline int rtm_get_table(struct rtmsg *r, struct rtattr **tb)
+{
+ __u32 table = r->rtm_table;
+
+ if (tb[RTA_TABLE])
+ table = rta_getattr_u32(tb[RTA_TABLE]);
+ return table;
+}
+
+extern struct rtnl_handle rth;
+
+struct iplink_req {
+ struct nlmsghdr n;
+ struct ifinfomsg i;
+ char buf[1024];
+};
+
+struct link_util {
+ struct link_util *next;
+ const char *id;
+ int maxattr;
+ int (*parse_opt)(struct link_util *, int, char **,
+ struct nlmsghdr *);
+ void (*print_opt)(struct link_util *, FILE *,
+ struct rtattr *[]);
+ void (*print_xstats)(struct link_util *, FILE *,
+ struct rtattr *);
+ void (*print_help)(struct link_util *, int, char **,
+ FILE *);
+ int (*parse_ifla_xstats)(struct link_util *,
+ int, char **);
+ int (*print_ifla_xstats)(struct nlmsghdr *, void *);
+};
+
+struct link_util *get_link_kind(const char *kind);
+
+int iplink_parse(int argc, char **argv, struct iplink_req *req, char **type);
+
+/* iplink_bridge.c */
+void br_dump_bridge_id(const struct ifla_bridge_id *id, char *buf, size_t len);
+int bridge_parse_xstats(struct link_util *lu, int argc, char **argv);
+int bridge_print_xstats(struct nlmsghdr *n, void *arg);
+extern const struct ipstats_stat_desc ipstats_stat_desc_xstats_bridge_group;
+extern const struct ipstats_stat_desc ipstats_stat_desc_xstats_slave_bridge_group;
+
+/* iplink_bond.c */
+int bond_parse_xstats(struct link_util *lu, int argc, char **argv);
+int bond_print_xstats(struct nlmsghdr *n, void *arg);
+extern const struct ipstats_stat_desc ipstats_stat_desc_xstats_bond_group;
+extern const struct ipstats_stat_desc ipstats_stat_desc_xstats_slave_bond_group;
+
+/* iproute_lwtunnel.c */
+int lwt_parse_encap(struct rtattr *rta, size_t len, int *argcp, char ***argvp,
+ int encap_attr, int encap_type_attr);
+void lwt_print_encap(FILE *fp, struct rtattr *encap_type, struct rtattr *encap);
+
+/* iplink_xdp.c */
+int xdp_parse(int *argc, char ***argv, struct iplink_req *req, const char *ifname,
+ bool generic, bool drv, bool offload);
+void xdp_dump(FILE *fp, struct rtattr *tb, bool link, bool details);
+
+/* iplink_vrf.c */
+__u32 ipvrf_get_table(const char *name);
+int name_is_vrf(const char *name);
+
+/* ipstats.c */
+enum ipstats_stat_desc_kind {
+ IPSTATS_STAT_DESC_KIND_LEAF,
+ IPSTATS_STAT_DESC_KIND_GROUP,
+};
+
+struct ipstats_stat_dump_filters;
+struct ipstats_stat_show_attrs;
+
+struct ipstats_stat_desc {
+ const char *name;
+ enum ipstats_stat_desc_kind kind;
+ union {
+ struct {
+ const struct ipstats_stat_desc **subs;
+ size_t nsubs;
+ };
+ struct {
+ void (*pack)(struct ipstats_stat_dump_filters *filters,
+ const struct ipstats_stat_desc *desc);
+ int (*show)(struct ipstats_stat_show_attrs *attrs,
+ const struct ipstats_stat_desc *desc);
+ };
+ };
+};
+
+struct ipstats_stat_desc_xstats {
+ const struct ipstats_stat_desc desc;
+ int xstats_at;
+ int link_type_at;
+ int inner_max;
+ int inner_at;
+ void (*show_cb)(const struct rtattr *at);
+};
+
+void ipstats_stat_desc_pack_xstats(struct ipstats_stat_dump_filters *filters,
+ const struct ipstats_stat_desc *desc);
+int ipstats_stat_desc_show_xstats(struct ipstats_stat_show_attrs *attrs,
+ const struct ipstats_stat_desc *desc);
+
+#define IPSTATS_STAT_DESC_XSTATS_LEAF(NAME) { \
+ .name = (NAME), \
+ .kind = IPSTATS_STAT_DESC_KIND_LEAF, \
+ .show = &ipstats_stat_desc_show_xstats, \
+ .pack = &ipstats_stat_desc_pack_xstats, \
+ }
+
+#ifndef INFINITY_LIFE_TIME
+#define INFINITY_LIFE_TIME 0xFFFFFFFFU
+#endif
+
+#ifndef LABEL_MAX_MASK
+#define LABEL_MAX_MASK 0xFFFFFU
+#endif
+
+void print_num(FILE *fp, unsigned int width, uint64_t count);
+void print_rt_flags(FILE *fp, unsigned int flags);
+void print_rta_ifidx(FILE *fp, __u32 ifidx, const char *prefix);
+void __print_rta_gateway(FILE *fp, unsigned char family, const char *gateway);
+void size_columns(unsigned int cols[], unsigned int n, ...);
+void print_stats64(FILE *fp, struct rtnl_link_stats64 *s,
+ const struct rtattr *carrier_changes, const char *what);
+void print_mpls_link_stats(FILE *fp, const struct mpls_link_stats *stats,
+ const char *indent);
+#endif /* _IP_COMMON_H_ */
diff --git a/ip/ipaddress.c b/ip/ipaddress.c
new file mode 100644
index 0000000..e536912
--- /dev/null
+++ b/ip/ipaddress.c
@@ -0,0 +1,2685 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * ipaddress.c "ip address".
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <fnmatch.h>
+
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/if_infiniband.h>
+#include <linux/sockios.h>
+#include <linux/net_namespace.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ll_map.h"
+#include "ip_common.h"
+#include "color.h"
+
+enum {
+ IPADD_LIST,
+ IPADD_FLUSH,
+ IPADD_SAVE,
+};
+
+static struct link_filter filter;
+static int do_link;
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ if (do_link)
+ iplink_usage();
+
+ fprintf(stderr,
+ "Usage: ip address {add|change|replace} IFADDR dev IFNAME [ LIFETIME ]\n"
+ " [ CONFFLAG-LIST ]\n"
+ " ip address del IFADDR dev IFNAME [mngtmpaddr]\n"
+ " ip address {save|flush} [ dev IFNAME ] [ scope SCOPE-ID ]\n"
+ " [ to PREFIX ] [ FLAG-LIST ] [ label LABEL ] [up]\n"
+ " ip address [ show [ dev IFNAME ] [ scope SCOPE-ID ] [ master DEVICE ]\n"
+ " [ nomaster ]\n"
+ " [ type TYPE ] [ to PREFIX ] [ FLAG-LIST ]\n"
+ " [ label LABEL ] [up] [ vrf NAME ]\n"
+ " [ proto ADDRPROTO ] ]\n"
+ " ip address {showdump|restore}\n"
+ "IFADDR := PREFIX | ADDR peer PREFIX\n"
+ " [ broadcast ADDR ] [ anycast ADDR ]\n"
+ " [ label IFNAME ] [ scope SCOPE-ID ] [ metric METRIC ]\n"
+ " [ proto ADDRPROTO ]\n"
+ "SCOPE-ID := [ host | link | global | NUMBER ]\n"
+ "FLAG-LIST := [ FLAG-LIST ] FLAG\n"
+ "FLAG := [ permanent | dynamic | secondary | primary |\n"
+ " [-]tentative | [-]deprecated | [-]dadfailed | temporary |\n"
+ " CONFFLAG-LIST ]\n"
+ "CONFFLAG-LIST := [ CONFFLAG-LIST ] CONFFLAG\n"
+ "CONFFLAG := [ home | nodad | mngtmpaddr | noprefixroute | autojoin ]\n"
+ "LIFETIME := [ valid_lft LFT ] [ preferred_lft LFT ]\n"
+ "LFT := forever | SECONDS\n"
+ "ADDRPROTO := [ NAME | NUMBER ]\n"
+ );
+ iplink_types_usage();
+
+ exit(-1);
+}
+
+static void print_link_flags(FILE *fp, unsigned int flags, unsigned int mdown)
+{
+ open_json_array(PRINT_ANY, is_json_context() ? "flags" : "<");
+ if (flags & IFF_UP && !(flags & IFF_RUNNING))
+ print_string(PRINT_ANY, NULL,
+ flags ? "%s," : "%s", "NO-CARRIER");
+ flags &= ~IFF_RUNNING;
+#define _PF(f) if (flags&IFF_##f) { \
+ flags &= ~IFF_##f ; \
+ print_string(PRINT_ANY, NULL, flags ? "%s," : "%s", #f); }
+ _PF(LOOPBACK);
+ _PF(BROADCAST);
+ _PF(POINTOPOINT);
+ _PF(MULTICAST);
+ _PF(NOARP);
+ _PF(ALLMULTI);
+ _PF(PROMISC);
+ _PF(MASTER);
+ _PF(SLAVE);
+ _PF(DEBUG);
+ _PF(DYNAMIC);
+ _PF(AUTOMEDIA);
+ _PF(PORTSEL);
+ _PF(NOTRAILERS);
+ _PF(UP);
+ _PF(LOWER_UP);
+ _PF(DORMANT);
+ _PF(ECHO);
+#undef _PF
+ if (flags)
+ print_hex(PRINT_ANY, NULL, "%x", flags);
+ if (mdown)
+ print_string(PRINT_ANY, NULL, ",%s", "M-DOWN");
+ close_json_array(PRINT_ANY, "> ");
+}
+
+static const char *oper_states[] = {
+ "UNKNOWN", "NOTPRESENT", "DOWN", "LOWERLAYERDOWN",
+ "TESTING", "DORMANT", "UP"
+};
+
+static void print_operstate(FILE *f, __u8 state)
+{
+ if (state >= ARRAY_SIZE(oper_states)) {
+ if (is_json_context())
+ print_uint(PRINT_JSON, "operstate_index", NULL, state);
+ else
+ print_0xhex(PRINT_FP, NULL, "state %#llx", state);
+ } else if (brief) {
+ print_color_string(PRINT_ANY,
+ oper_state_color(state),
+ "operstate",
+ "%-14s ",
+ oper_states[state]);
+ } else {
+ if (is_json_context())
+ print_string(PRINT_JSON,
+ "operstate",
+ NULL, oper_states[state]);
+ else {
+ fprintf(f, "state ");
+ color_fprintf(f, oper_state_color(state),
+ "%s ", oper_states[state]);
+ }
+ }
+}
+
+int get_operstate(const char *name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(oper_states); i++)
+ if (strcasecmp(name, oper_states[i]) == 0)
+ return i;
+ return -1;
+}
+
+static void print_queuelen(FILE *f, struct rtattr *tb[IFLA_MAX + 1])
+{
+ int qlen;
+
+ if (tb[IFLA_TXQLEN])
+ qlen = rta_getattr_u32(tb[IFLA_TXQLEN]);
+ else {
+ struct ifreq ifr = {};
+ int s = socket(AF_INET, SOCK_STREAM, 0);
+
+ if (s < 0)
+ return;
+
+ strcpy(ifr.ifr_name, rta_getattr_str(tb[IFLA_IFNAME]));
+ if (ioctl(s, SIOCGIFTXQLEN, &ifr) < 0) {
+ fprintf(stderr,
+ "ioctl(SIOCGIFTXQLEN) failed: %s\n",
+ strerror(errno));
+ close(s);
+ return;
+ }
+ close(s);
+ qlen = ifr.ifr_qlen;
+ }
+ if (qlen)
+ print_int(PRINT_ANY, "txqlen", "qlen %d", qlen);
+}
+
+static const char *link_modes[] = {
+ "DEFAULT", "DORMANT"
+};
+
+static void print_linkmode(FILE *f, struct rtattr *tb)
+{
+ unsigned int mode = rta_getattr_u8(tb);
+
+ if (mode >= ARRAY_SIZE(link_modes))
+ print_int(PRINT_ANY,
+ "linkmode_index",
+ "mode %d ",
+ mode);
+ else
+ print_string(PRINT_ANY,
+ "linkmode",
+ "mode %s "
+ , link_modes[mode]);
+}
+
+static char *parse_link_kind(struct rtattr *tb, bool slave)
+{
+ struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+ int attr = slave ? IFLA_INFO_SLAVE_KIND : IFLA_INFO_KIND;
+
+ parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb);
+
+ if (linkinfo[attr])
+ return RTA_DATA(linkinfo[attr]);
+
+ return "";
+}
+
+static int match_link_kind(struct rtattr **tb, const char *kind, bool slave)
+{
+ if (!tb[IFLA_LINKINFO])
+ return -1;
+
+ return strcmp(parse_link_kind(tb[IFLA_LINKINFO], slave), kind);
+}
+
+static void print_linktype(FILE *fp, struct rtattr *tb)
+{
+ struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+ struct link_util *lu;
+ struct link_util *slave_lu;
+ char slave[32];
+
+ parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb);
+ open_json_object("linkinfo");
+
+ if (linkinfo[IFLA_INFO_KIND]) {
+ const char *kind
+ = rta_getattr_str(linkinfo[IFLA_INFO_KIND]);
+
+ print_nl();
+ print_string(PRINT_ANY, "info_kind", " %s ", kind);
+
+ lu = get_link_kind(kind);
+ if (lu && lu->print_opt) {
+ struct rtattr *attr[lu->maxattr+1], **data = NULL;
+
+ if (linkinfo[IFLA_INFO_DATA]) {
+ parse_rtattr_nested(attr, lu->maxattr,
+ linkinfo[IFLA_INFO_DATA]);
+ data = attr;
+ }
+ open_json_object("info_data");
+ lu->print_opt(lu, fp, data);
+ close_json_object();
+
+ if (linkinfo[IFLA_INFO_XSTATS] && show_stats &&
+ lu->print_xstats) {
+ open_json_object("info_xstats");
+ lu->print_xstats(lu, fp, linkinfo[IFLA_INFO_XSTATS]);
+ close_json_object();
+ }
+ }
+ }
+
+ if (linkinfo[IFLA_INFO_SLAVE_KIND]) {
+ const char *slave_kind
+ = rta_getattr_str(linkinfo[IFLA_INFO_SLAVE_KIND]);
+
+ print_nl();
+ print_string(PRINT_ANY,
+ "info_slave_kind",
+ " %s_slave ",
+ slave_kind);
+
+ snprintf(slave, sizeof(slave), "%s_slave", slave_kind);
+
+ slave_lu = get_link_kind(slave);
+ if (slave_lu && slave_lu->print_opt) {
+ struct rtattr *attr[slave_lu->maxattr+1], **data = NULL;
+
+ if (linkinfo[IFLA_INFO_SLAVE_DATA]) {
+ parse_rtattr_nested(attr, slave_lu->maxattr,
+ linkinfo[IFLA_INFO_SLAVE_DATA]);
+ data = attr;
+ }
+ open_json_object("info_slave_data");
+ slave_lu->print_opt(slave_lu, fp, data);
+ close_json_object();
+ }
+ }
+ close_json_object();
+}
+
+static void print_af_spec(FILE *fp, struct rtattr *af_spec_attr)
+{
+ struct rtattr *inet6_attr;
+ struct rtattr *tb[IFLA_INET6_MAX + 1];
+
+ inet6_attr = parse_rtattr_one_nested(AF_INET6, af_spec_attr);
+ if (!inet6_attr)
+ return;
+
+ parse_rtattr_nested(tb, IFLA_INET6_MAX, inet6_attr);
+
+ if (tb[IFLA_INET6_ADDR_GEN_MODE]) {
+ __u8 mode = rta_getattr_u8(tb[IFLA_INET6_ADDR_GEN_MODE]);
+ SPRINT_BUF(b1);
+
+ switch (mode) {
+ case IN6_ADDR_GEN_MODE_EUI64:
+ print_string(PRINT_ANY,
+ "inet6_addr_gen_mode",
+ "addrgenmode %s ",
+ "eui64");
+ break;
+ case IN6_ADDR_GEN_MODE_NONE:
+ print_string(PRINT_ANY,
+ "inet6_addr_gen_mode",
+ "addrgenmode %s ",
+ "none");
+ break;
+ case IN6_ADDR_GEN_MODE_STABLE_PRIVACY:
+ print_string(PRINT_ANY,
+ "inet6_addr_gen_mode",
+ "addrgenmode %s ",
+ "stable_secret");
+ break;
+ case IN6_ADDR_GEN_MODE_RANDOM:
+ print_string(PRINT_ANY,
+ "inet6_addr_gen_mode",
+ "addrgenmode %s ",
+ "random");
+ break;
+ default:
+ snprintf(b1, sizeof(b1), "%#.2hhx", mode);
+ print_string(PRINT_ANY,
+ "inet6_addr_gen_mode",
+ "addrgenmode %s ",
+ b1);
+ break;
+ }
+ }
+}
+
+static void print_vf_stats64(FILE *fp, struct rtattr *vfstats);
+
+static void print_vfinfo(FILE *fp, struct ifinfomsg *ifi, struct rtattr *vfinfo)
+{
+ struct ifla_vf_mac *vf_mac;
+ struct ifla_vf_broadcast *vf_broadcast;
+ struct ifla_vf_tx_rate *vf_tx_rate;
+ struct rtattr *vf[IFLA_VF_MAX + 1] = {};
+
+ SPRINT_BUF(b1);
+
+ if (vfinfo->rta_type != IFLA_VF_INFO) {
+ fprintf(stderr, "BUG: rta type is %d\n", vfinfo->rta_type);
+ return;
+ }
+
+ parse_rtattr_nested(vf, IFLA_VF_MAX, vfinfo);
+
+ vf_mac = RTA_DATA(vf[IFLA_VF_MAC]);
+ vf_broadcast = RTA_DATA(vf[IFLA_VF_BROADCAST]);
+ vf_tx_rate = RTA_DATA(vf[IFLA_VF_TX_RATE]);
+
+ print_string(PRINT_FP, NULL, "%s ", _SL_);
+ print_int(PRINT_ANY, "vf", "vf %d ", vf_mac->vf);
+
+ print_string(PRINT_ANY,
+ "link_type",
+ " link/%s ",
+ ll_type_n2a(ifi->ifi_type, b1, sizeof(b1)));
+
+ print_color_string(PRINT_ANY, COLOR_MAC,
+ "address", "%s",
+ ll_addr_n2a((unsigned char *) &vf_mac->mac,
+ ifi->ifi_type == ARPHRD_ETHER ?
+ ETH_ALEN : INFINIBAND_ALEN,
+ ifi->ifi_type,
+ b1, sizeof(b1)));
+
+ if (vf[IFLA_VF_BROADCAST]) {
+ if (ifi->ifi_flags&IFF_POINTOPOINT) {
+ print_string(PRINT_FP, NULL, " peer ", NULL);
+ print_bool(PRINT_JSON,
+ "link_pointtopoint", NULL, true);
+ } else
+ print_string(PRINT_FP, NULL, " brd ", NULL);
+
+ print_color_string(PRINT_ANY, COLOR_MAC,
+ "broadcast", "%s",
+ ll_addr_n2a((unsigned char *) &vf_broadcast->broadcast,
+ ifi->ifi_type == ARPHRD_ETHER ?
+ ETH_ALEN : INFINIBAND_ALEN,
+ ifi->ifi_type,
+ b1, sizeof(b1)));
+ }
+
+ if (vf[IFLA_VF_VLAN_LIST]) {
+ struct rtattr *i, *vfvlanlist = vf[IFLA_VF_VLAN_LIST];
+ int rem = RTA_PAYLOAD(vfvlanlist);
+
+ open_json_array(PRINT_JSON, "vlan_list");
+ for (i = RTA_DATA(vfvlanlist);
+ RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+ struct ifla_vf_vlan_info *vf_vlan_info = RTA_DATA(i);
+ SPRINT_BUF(b2);
+
+ open_json_object(NULL);
+ if (vf_vlan_info->vlan)
+ print_int(PRINT_ANY,
+ "vlan",
+ ", vlan %d",
+ vf_vlan_info->vlan);
+ if (vf_vlan_info->qos)
+ print_int(PRINT_ANY,
+ "qos",
+ ", qos %d",
+ vf_vlan_info->qos);
+ if (vf_vlan_info->vlan_proto &&
+ vf_vlan_info->vlan_proto != htons(ETH_P_8021Q))
+ print_string(PRINT_ANY,
+ "protocol",
+ ", vlan protocol %s",
+ ll_proto_n2a(
+ vf_vlan_info->vlan_proto,
+ b2, sizeof(b2)));
+ close_json_object();
+ }
+ close_json_array(PRINT_JSON, NULL);
+ } else {
+ struct ifla_vf_vlan *vf_vlan = RTA_DATA(vf[IFLA_VF_VLAN]);
+
+ if (vf_vlan->vlan)
+ print_int(PRINT_ANY,
+ "vlan",
+ ", vlan %d",
+ vf_vlan->vlan);
+ if (vf_vlan->qos)
+ print_int(PRINT_ANY, "qos", ", qos %d", vf_vlan->qos);
+ }
+
+ if (vf_tx_rate->rate)
+ print_uint(PRINT_ANY,
+ "tx_rate",
+ ", tx rate %u (Mbps)",
+ vf_tx_rate->rate);
+
+ if (vf[IFLA_VF_RATE]) {
+ struct ifla_vf_rate *vf_rate = RTA_DATA(vf[IFLA_VF_RATE]);
+ int max_tx = vf_rate->max_tx_rate;
+ int min_tx = vf_rate->min_tx_rate;
+
+ if (is_json_context()) {
+ open_json_object("rate");
+ print_uint(PRINT_JSON, "max_tx", NULL, max_tx);
+ print_uint(PRINT_ANY, "min_tx", NULL, min_tx);
+ close_json_object();
+ } else {
+ if (max_tx)
+ fprintf(fp, ", max_tx_rate %uMbps", max_tx);
+ if (min_tx)
+ fprintf(fp, ", min_tx_rate %uMbps", min_tx);
+ }
+ }
+
+ if (vf[IFLA_VF_SPOOFCHK]) {
+ struct ifla_vf_spoofchk *vf_spoofchk =
+ RTA_DATA(vf[IFLA_VF_SPOOFCHK]);
+
+ if (vf_spoofchk->setting != -1)
+ print_bool(PRINT_ANY,
+ "spoofchk",
+ vf_spoofchk->setting ?
+ ", spoof checking on" : ", spoof checking off",
+ vf_spoofchk->setting);
+ }
+
+ if (vf[IFLA_VF_IB_NODE_GUID]) {
+ struct ifla_vf_guid *guid = RTA_DATA(vf[IFLA_VF_IB_NODE_GUID]);
+ uint64_t node_guid = ntohll(guid->guid);
+
+ print_string(PRINT_ANY, "node guid", ", NODE_GUID %s",
+ ll_addr_n2a((const unsigned char *)&node_guid,
+ sizeof(node_guid), ARPHRD_INFINIBAND,
+ b1, sizeof(b1)));
+ }
+ if (vf[IFLA_VF_IB_PORT_GUID]) {
+ struct ifla_vf_guid *guid = RTA_DATA(vf[IFLA_VF_IB_PORT_GUID]);
+ uint64_t port_guid = ntohll(guid->guid);
+
+ print_string(PRINT_ANY, "port guid", ", PORT_GUID %s",
+ ll_addr_n2a((const unsigned char *)&port_guid,
+ sizeof(port_guid), ARPHRD_INFINIBAND,
+ b1, sizeof(b1)));
+ }
+ if (vf[IFLA_VF_LINK_STATE]) {
+ struct ifla_vf_link_state *vf_linkstate =
+ RTA_DATA(vf[IFLA_VF_LINK_STATE]);
+
+ if (vf_linkstate->link_state == IFLA_VF_LINK_STATE_AUTO)
+ print_string(PRINT_ANY,
+ "link_state",
+ ", link-state %s",
+ "auto");
+ else if (vf_linkstate->link_state == IFLA_VF_LINK_STATE_ENABLE)
+ print_string(PRINT_ANY,
+ "link_state",
+ ", link-state %s",
+ "enable");
+ else
+ print_string(PRINT_ANY,
+ "link_state",
+ ", link-state %s",
+ "disable");
+ }
+
+ if (vf[IFLA_VF_TRUST]) {
+ struct ifla_vf_trust *vf_trust = RTA_DATA(vf[IFLA_VF_TRUST]);
+
+ if (vf_trust->setting != -1)
+ print_bool(PRINT_ANY,
+ "trust",
+ vf_trust->setting ? ", trust on" : ", trust off",
+ vf_trust->setting);
+ }
+
+ if (vf[IFLA_VF_RSS_QUERY_EN]) {
+ struct ifla_vf_rss_query_en *rss_query =
+ RTA_DATA(vf[IFLA_VF_RSS_QUERY_EN]);
+
+ if (rss_query->setting != -1)
+ print_bool(PRINT_ANY,
+ "query_rss_en",
+ rss_query->setting ? ", query_rss on"
+ : ", query_rss off",
+ rss_query->setting);
+ }
+
+ if (vf[IFLA_VF_STATS] && show_stats)
+ print_vf_stats64(fp, vf[IFLA_VF_STATS]);
+}
+
+void size_columns(unsigned int cols[], unsigned int n, ...)
+{
+ unsigned int i, len;
+ uint64_t val;
+ va_list args;
+
+ va_start(args, n);
+
+ for (i = 0; i < n; i++) {
+ val = va_arg(args, unsigned long long);
+
+ if (human_readable)
+ continue;
+
+ for (len = 1; val > 9; len++, val /= 10)
+ /* nothing */;
+ if (len > cols[i])
+ cols[i] = len;
+ }
+
+ va_end(args);
+}
+
+void print_num(FILE *fp, unsigned int width, uint64_t count)
+{
+ const char *prefix = "kMGTPE";
+ const unsigned int base = use_iec ? 1024 : 1000;
+ uint64_t powi = 1;
+ uint16_t powj = 1;
+ uint8_t precision = 2;
+ char buf[64];
+
+ if (!human_readable || count < base) {
+ fprintf(fp, "%*"PRIu64" ", width, count);
+ return;
+ }
+
+ /* increase value by a factor of 1000/1024 and print
+ * if result is something a human can read
+ */
+ for (;;) {
+ powi *= base;
+ if (count / base < powi)
+ break;
+
+ if (!prefix[1])
+ break;
+ ++prefix;
+ }
+
+ /* try to guess a good number of digits for precision */
+ for (; precision > 0; precision--) {
+ powj *= 10;
+ if (count / powi < powj)
+ break;
+ }
+
+ snprintf(buf, sizeof(buf), "%.*f%c%s", precision,
+ (double) count / powi, *prefix, use_iec ? "i" : "");
+
+ fprintf(fp, "%*s ", width, buf);
+}
+
+static void print_vf_stats64(FILE *fp, struct rtattr *vfstats)
+{
+ struct rtattr *vf[IFLA_VF_STATS_MAX + 1];
+
+ if (vfstats->rta_type != IFLA_VF_STATS) {
+ fprintf(stderr, "BUG: rta type is %d\n", vfstats->rta_type);
+ return;
+ }
+
+ parse_rtattr_nested(vf, IFLA_VF_STATS_MAX, vfstats);
+
+ if (is_json_context()) {
+ open_json_object("stats");
+
+ /* RX stats */
+ open_json_object("rx");
+ print_u64(PRINT_JSON, "bytes", NULL,
+ rta_getattr_u64(vf[IFLA_VF_STATS_RX_BYTES]));
+ print_u64(PRINT_JSON, "packets", NULL,
+ rta_getattr_u64(vf[IFLA_VF_STATS_RX_PACKETS]));
+ print_u64(PRINT_JSON, "multicast", NULL,
+ rta_getattr_u64(vf[IFLA_VF_STATS_MULTICAST]));
+ print_u64(PRINT_JSON, "broadcast", NULL,
+ rta_getattr_u64(vf[IFLA_VF_STATS_BROADCAST]));
+ if (vf[IFLA_VF_STATS_RX_DROPPED])
+ print_u64(PRINT_JSON, "dropped", NULL,
+ rta_getattr_u64(vf[IFLA_VF_STATS_RX_DROPPED]));
+ close_json_object();
+
+ /* TX stats */
+ open_json_object("tx");
+ print_u64(PRINT_JSON, "tx_bytes", NULL,
+ rta_getattr_u64(vf[IFLA_VF_STATS_TX_BYTES]));
+ print_u64(PRINT_JSON, "tx_packets", NULL,
+ rta_getattr_u64(vf[IFLA_VF_STATS_TX_PACKETS]));
+ if (vf[IFLA_VF_STATS_TX_DROPPED])
+ print_u64(PRINT_JSON, "dropped", NULL,
+ rta_getattr_u64(vf[IFLA_VF_STATS_TX_DROPPED]));
+ close_json_object();
+ close_json_object();
+ } else {
+ /* RX stats */
+ fprintf(fp, "%s", _SL_);
+ fprintf(fp, " RX: bytes packets mcast bcast ");
+ if (vf[IFLA_VF_STATS_RX_DROPPED])
+ fprintf(fp, " dropped ");
+ fprintf(fp, "%s", _SL_);
+ fprintf(fp, " ");
+
+ print_num(fp, 10, rta_getattr_u64(vf[IFLA_VF_STATS_RX_BYTES]));
+ print_num(fp, 8, rta_getattr_u64(vf[IFLA_VF_STATS_RX_PACKETS]));
+ print_num(fp, 7, rta_getattr_u64(vf[IFLA_VF_STATS_MULTICAST]));
+ print_num(fp, 7, rta_getattr_u64(vf[IFLA_VF_STATS_BROADCAST]));
+ if (vf[IFLA_VF_STATS_RX_DROPPED])
+ print_num(fp, 8, rta_getattr_u64(vf[IFLA_VF_STATS_RX_DROPPED]));
+
+ /* TX stats */
+ fprintf(fp, "%s", _SL_);
+ fprintf(fp, " TX: bytes packets ");
+ if (vf[IFLA_VF_STATS_TX_DROPPED])
+ fprintf(fp, " dropped ");
+ fprintf(fp, "%s", _SL_);
+ fprintf(fp, " ");
+
+ print_num(fp, 10, rta_getattr_u64(vf[IFLA_VF_STATS_TX_BYTES]));
+ print_num(fp, 8, rta_getattr_u64(vf[IFLA_VF_STATS_TX_PACKETS]));
+ if (vf[IFLA_VF_STATS_TX_DROPPED])
+ print_num(fp, 8, rta_getattr_u64(vf[IFLA_VF_STATS_TX_DROPPED]));
+ }
+}
+
+void print_stats64(FILE *fp, struct rtnl_link_stats64 *s,
+ const struct rtattr *carrier_changes,
+ const char *what)
+{
+ unsigned int cols[] = {
+ strlen("*X errors:"),
+ strlen("packets"),
+ strlen("errors"),
+ strlen("dropped"),
+ strlen("heartbt"),
+ strlen("overrun"),
+ strlen("compressed"),
+ strlen("otherhost"),
+ };
+
+ if (is_json_context()) {
+ if (what)
+ open_json_object(what);
+
+ /* RX stats */
+ open_json_object("rx");
+ print_u64(PRINT_JSON, "bytes", NULL, s->rx_bytes);
+ print_u64(PRINT_JSON, "packets", NULL, s->rx_packets);
+ print_u64(PRINT_JSON, "errors", NULL, s->rx_errors);
+ print_u64(PRINT_JSON, "dropped", NULL, s->rx_dropped);
+ print_u64(PRINT_JSON, "over_errors", NULL, s->rx_over_errors);
+ print_u64(PRINT_JSON, "multicast", NULL, s->multicast);
+ if (s->rx_compressed)
+ print_u64(PRINT_JSON,
+ "compressed", NULL, s->rx_compressed);
+
+ /* RX error stats */
+ if (show_stats > 1) {
+ print_u64(PRINT_JSON,
+ "length_errors",
+ NULL, s->rx_length_errors);
+ print_u64(PRINT_JSON,
+ "crc_errors",
+ NULL, s->rx_crc_errors);
+ print_u64(PRINT_JSON,
+ "frame_errors",
+ NULL, s->rx_frame_errors);
+ print_u64(PRINT_JSON,
+ "fifo_errors",
+ NULL, s->rx_fifo_errors);
+ print_u64(PRINT_JSON,
+ "missed_errors",
+ NULL, s->rx_missed_errors);
+ if (s->rx_nohandler)
+ print_u64(PRINT_JSON,
+ "nohandler", NULL, s->rx_nohandler);
+ if (s->rx_otherhost_dropped)
+ print_u64(PRINT_JSON,
+ "otherhost", NULL,
+ s->rx_otherhost_dropped);
+ }
+ close_json_object();
+
+ /* TX stats */
+ open_json_object("tx");
+ print_u64(PRINT_JSON, "bytes", NULL, s->tx_bytes);
+ print_u64(PRINT_JSON, "packets", NULL, s->tx_packets);
+ print_u64(PRINT_JSON, "errors", NULL, s->tx_errors);
+ print_u64(PRINT_JSON, "dropped", NULL, s->tx_dropped);
+ print_u64(PRINT_JSON,
+ "carrier_errors",
+ NULL, s->tx_carrier_errors);
+ print_u64(PRINT_JSON, "collisions", NULL, s->collisions);
+ if (s->tx_compressed)
+ print_u64(PRINT_JSON,
+ "compressed", NULL, s->tx_compressed);
+
+ /* TX error stats */
+ if (show_stats > 1) {
+ print_u64(PRINT_JSON,
+ "aborted_errors",
+ NULL, s->tx_aborted_errors);
+ print_u64(PRINT_JSON,
+ "fifo_errors",
+ NULL, s->tx_fifo_errors);
+ print_u64(PRINT_JSON,
+ "window_errors",
+ NULL, s->tx_window_errors);
+ print_u64(PRINT_JSON,
+ "heartbeat_errors",
+ NULL, s->tx_heartbeat_errors);
+ if (carrier_changes)
+ print_u64(PRINT_JSON, "carrier_changes", NULL,
+ rta_getattr_u32(carrier_changes));
+ }
+
+ close_json_object();
+ if (what)
+ close_json_object();
+ } else {
+ uint64_t zero64 = 0;
+
+ size_columns(cols, ARRAY_SIZE(cols),
+ s->rx_bytes, s->rx_packets, s->rx_errors,
+ s->rx_dropped, s->rx_missed_errors,
+ s->multicast, s->rx_compressed, zero64);
+ if (show_stats > 1)
+ size_columns(cols, ARRAY_SIZE(cols), 0,
+ s->rx_length_errors, s->rx_crc_errors,
+ s->rx_frame_errors, s->rx_fifo_errors,
+ s->rx_over_errors, s->rx_nohandler,
+ s->rx_otherhost_dropped);
+ size_columns(cols, ARRAY_SIZE(cols),
+ s->tx_bytes, s->tx_packets, s->tx_errors,
+ s->tx_dropped, s->tx_carrier_errors,
+ s->collisions, s->tx_compressed, zero64);
+ if (show_stats > 1) {
+ uint64_t cc = carrier_changes ?
+ rta_getattr_u32(carrier_changes) : 0;
+
+ size_columns(cols, ARRAY_SIZE(cols), 0, 0,
+ s->tx_aborted_errors, s->tx_fifo_errors,
+ s->tx_window_errors,
+ s->tx_heartbeat_errors, cc, zero64);
+ }
+
+ /* RX stats */
+ fprintf(fp, " RX: %*s %*s %*s %*s %*s %*s %*s%s",
+ cols[0] - 4, "bytes", cols[1], "packets",
+ cols[2], "errors", cols[3], "dropped",
+ cols[4], "missed", cols[5], "mcast",
+ cols[6], s->rx_compressed ? "compressed" : "", _SL_);
+
+ fprintf(fp, " ");
+ print_num(fp, cols[0], s->rx_bytes);
+ print_num(fp, cols[1], s->rx_packets);
+ print_num(fp, cols[2], s->rx_errors);
+ print_num(fp, cols[3], s->rx_dropped);
+ print_num(fp, cols[4], s->rx_missed_errors);
+ print_num(fp, cols[5], s->multicast);
+ if (s->rx_compressed)
+ print_num(fp, cols[6], s->rx_compressed);
+
+ /* RX error stats */
+ if (show_stats > 1) {
+ fprintf(fp, "%s", _SL_);
+ fprintf(fp, " RX errors:%*s %*s %*s %*s %*s %*s%*s%*s%s",
+ cols[0] - 10, "", cols[1], "length",
+ cols[2], "crc", cols[3], "frame",
+ cols[4], "fifo", cols[5], "overrun",
+ s->rx_nohandler ? cols[6] + 1 : 0,
+ s->rx_nohandler ? " nohandler" : "",
+ s->rx_otherhost_dropped ? cols[7] + 1 : 0,
+ s->rx_otherhost_dropped ? " otherhost" : "",
+ _SL_);
+ fprintf(fp, "%*s", cols[0] + 5, "");
+ print_num(fp, cols[1], s->rx_length_errors);
+ print_num(fp, cols[2], s->rx_crc_errors);
+ print_num(fp, cols[3], s->rx_frame_errors);
+ print_num(fp, cols[4], s->rx_fifo_errors);
+ print_num(fp, cols[5], s->rx_over_errors);
+ if (s->rx_nohandler)
+ print_num(fp, cols[6], s->rx_nohandler);
+ if (s->rx_otherhost_dropped)
+ print_num(fp, cols[7], s->rx_otherhost_dropped);
+ }
+ fprintf(fp, "%s", _SL_);
+
+ /* TX stats */
+ fprintf(fp, " TX: %*s %*s %*s %*s %*s %*s %*s%s",
+ cols[0] - 4, "bytes", cols[1], "packets",
+ cols[2], "errors", cols[3], "dropped",
+ cols[4], "carrier", cols[5], "collsns",
+ cols[6], s->tx_compressed ? "compressed" : "", _SL_);
+
+ fprintf(fp, " ");
+ print_num(fp, cols[0], s->tx_bytes);
+ print_num(fp, cols[1], s->tx_packets);
+ print_num(fp, cols[2], s->tx_errors);
+ print_num(fp, cols[3], s->tx_dropped);
+ print_num(fp, cols[4], s->tx_carrier_errors);
+ print_num(fp, cols[5], s->collisions);
+ if (s->tx_compressed)
+ print_num(fp, cols[6], s->tx_compressed);
+
+ /* TX error stats */
+ if (show_stats > 1) {
+ fprintf(fp, "%s", _SL_);
+ fprintf(fp, " TX errors:%*s %*s %*s %*s %*s %*s%s",
+ cols[0] - 10, "", cols[1], "aborted",
+ cols[2], "fifo", cols[3], "window",
+ cols[4], "heartbt",
+ cols[5], carrier_changes ? "transns" : "",
+ _SL_);
+
+ fprintf(fp, "%*s", cols[0] + 5, "");
+ print_num(fp, cols[1], s->tx_aborted_errors);
+ print_num(fp, cols[2], s->tx_fifo_errors);
+ print_num(fp, cols[3], s->tx_window_errors);
+ print_num(fp, cols[4], s->tx_heartbeat_errors);
+ if (carrier_changes)
+ print_num(fp, cols[5],
+ rta_getattr_u32(carrier_changes));
+ }
+ }
+}
+
+static void __print_link_stats(FILE *fp, struct rtattr *tb[])
+{
+ const struct rtattr *carrier_changes = tb[IFLA_CARRIER_CHANGES];
+ struct rtnl_link_stats64 _s, *s = &_s;
+ int ret;
+
+ ret = get_rtnl_link_stats_rta(s, tb);
+ if (ret < 0)
+ return;
+
+ print_stats64(fp, s, carrier_changes,
+ (ret == sizeof(*s)) ? "stats64" : "stats");
+}
+
+static void print_link_stats(FILE *fp, struct nlmsghdr *n)
+{
+ struct ifinfomsg *ifi = NLMSG_DATA(n);
+ struct rtattr *tb[IFLA_MAX+1];
+
+ parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi),
+ n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi)));
+ __print_link_stats(fp, tb);
+ print_nl();
+}
+
+static int print_linkinfo_brief(FILE *fp, const char *name,
+ const struct ifinfomsg *ifi,
+ struct rtattr *tb[])
+{
+ unsigned int m_flag = 0;
+
+ m_flag = print_name_and_link("%-16s ", name, tb);
+
+ if (tb[IFLA_OPERSTATE])
+ print_operstate(fp, rta_getattr_u8(tb[IFLA_OPERSTATE]));
+
+ if (filter.family == AF_PACKET) {
+ SPRINT_BUF(b1);
+
+ if (tb[IFLA_ADDRESS]) {
+ print_color_string(PRINT_ANY, COLOR_MAC,
+ "address", "%s ",
+ ll_addr_n2a(
+ RTA_DATA(tb[IFLA_ADDRESS]),
+ RTA_PAYLOAD(tb[IFLA_ADDRESS]),
+ ifi->ifi_type,
+ b1, sizeof(b1)));
+ }
+
+ print_link_flags(fp, ifi->ifi_flags, m_flag);
+ print_string(PRINT_FP, NULL, "%s", "\n");
+ }
+
+ fflush(fp);
+ return 0;
+}
+
+static const char *link_events[] = {
+ [IFLA_EVENT_NONE] = "NONE",
+ [IFLA_EVENT_REBOOT] = "REBOOT",
+ [IFLA_EVENT_FEATURES] = "FEATURE CHANGE",
+ [IFLA_EVENT_BONDING_FAILOVER] = "BONDING FAILOVER",
+ [IFLA_EVENT_NOTIFY_PEERS] = "NOTIFY PEERS",
+ [IFLA_EVENT_IGMP_RESEND] = "RESEND IGMP",
+ [IFLA_EVENT_BONDING_OPTIONS] = "BONDING OPTION"
+};
+
+static void print_link_event(FILE *f, __u32 event)
+{
+ if (event >= ARRAY_SIZE(link_events))
+ print_int(PRINT_ANY, "event", "event %d ", event);
+ else {
+ if (event)
+ print_string(PRINT_ANY,
+ "event", "event %s ",
+ link_events[event]);
+ }
+}
+
+static void print_proto_down(FILE *f, struct rtattr *tb[])
+{
+ struct rtattr *preason[IFLA_PROTO_DOWN_REASON_MAX+1];
+
+ if (tb[IFLA_PROTO_DOWN]) {
+ if (rta_getattr_u8(tb[IFLA_PROTO_DOWN]))
+ print_bool(PRINT_ANY,
+ "proto_down", " protodown on ", true);
+ }
+
+ if (tb[IFLA_PROTO_DOWN_REASON]) {
+ char buf[255];
+ __u32 reason;
+ int i, start = 1;
+
+ parse_rtattr_nested(preason, IFLA_PROTO_DOWN_REASON_MAX,
+ tb[IFLA_PROTO_DOWN_REASON]);
+ if (!tb[IFLA_PROTO_DOWN_REASON_VALUE])
+ return;
+
+ reason = rta_getattr_u8(preason[IFLA_PROTO_DOWN_REASON_VALUE]);
+ if (!reason)
+ return;
+
+ open_json_array(PRINT_ANY,
+ is_json_context() ? "proto_down_reason" : "protodown_reason <");
+ for (i = 0; reason; i++, reason >>= 1) {
+ if (reason & 0x1) {
+ if (protodown_reason_n2a(i, buf, sizeof(buf)))
+ break;
+ print_string(PRINT_ANY, NULL,
+ start ? "%s" : ",%s", buf);
+ start = 0;
+ }
+ }
+ close_json_array(PRINT_ANY, ">");
+ }
+}
+
+int print_linkinfo(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+ struct ifinfomsg *ifi = NLMSG_DATA(n);
+ struct rtattr *tb[IFLA_MAX+1];
+ int len = n->nlmsg_len;
+ const char *name;
+ unsigned int m_flag = 0;
+ SPRINT_BUF(b1);
+ bool truncated_vfs = false;
+
+ if (n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK)
+ return 0;
+
+ len -= NLMSG_LENGTH(sizeof(*ifi));
+ if (len < 0)
+ return -1;
+
+ if (filter.ifindex && ifi->ifi_index != filter.ifindex)
+ return -1;
+ if (filter.up && !(ifi->ifi_flags&IFF_UP))
+ return -1;
+
+ parse_rtattr_flags(tb, IFLA_MAX, IFLA_RTA(ifi), len, NLA_F_NESTED);
+
+ name = get_ifname_rta(ifi->ifi_index, tb[IFLA_IFNAME]);
+ if (!name)
+ return -1;
+
+ if (filter.label)
+ return 0;
+
+ if (tb[IFLA_GROUP]) {
+ int group = rta_getattr_u32(tb[IFLA_GROUP]);
+
+ if (filter.group != -1 && group != filter.group)
+ return -1;
+ }
+
+ if (tb[IFLA_MASTER]) {
+ int master = rta_getattr_u32(tb[IFLA_MASTER]);
+
+ if (filter.master > 0 && master != filter.master)
+ return -1;
+ } else if (filter.master > 0)
+ return -1;
+
+ if (filter.kind && match_link_kind(tb, filter.kind, 0))
+ return -1;
+
+ if (filter.slave_kind && match_link_kind(tb, filter.slave_kind, 1))
+ return -1;
+
+ if (n->nlmsg_type == RTM_DELLINK)
+ print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+ if (brief)
+ return print_linkinfo_brief(fp, name, ifi, tb);
+
+ print_int(PRINT_ANY, "ifindex", "%d: ", ifi->ifi_index);
+
+ m_flag = print_name_and_link("%s: ", name, tb);
+ print_link_flags(fp, ifi->ifi_flags, m_flag);
+
+ if (tb[IFLA_MTU])
+ print_int(PRINT_ANY,
+ "mtu", "mtu %u ",
+ rta_getattr_u32(tb[IFLA_MTU]));
+ if (tb[IFLA_XDP])
+ xdp_dump(fp, tb[IFLA_XDP], do_link, false);
+ if (tb[IFLA_QDISC])
+ print_string(PRINT_ANY,
+ "qdisc",
+ "qdisc %s ",
+ rta_getattr_str(tb[IFLA_QDISC]));
+ if (tb[IFLA_MASTER]) {
+ int master = rta_getattr_u32(tb[IFLA_MASTER]);
+
+ print_string(PRINT_ANY,
+ "master", "master %s ",
+ ll_index_to_name(master));
+ }
+
+ if (tb[IFLA_OPERSTATE])
+ print_operstate(fp, rta_getattr_u8(tb[IFLA_OPERSTATE]));
+
+ if (do_link && tb[IFLA_LINKMODE])
+ print_linkmode(fp, tb[IFLA_LINKMODE]);
+
+ if (tb[IFLA_GROUP]) {
+ int group = rta_getattr_u32(tb[IFLA_GROUP]);
+
+ print_string(PRINT_ANY,
+ "group",
+ "group %s ",
+ rtnl_group_n2a(group, b1, sizeof(b1)));
+ }
+
+ if (filter.showqueue)
+ print_queuelen(fp, tb);
+
+ if (tb[IFLA_EVENT])
+ print_link_event(fp, rta_getattr_u32(tb[IFLA_EVENT]));
+
+ if (!filter.family || filter.family == AF_PACKET || show_details) {
+ print_nl();
+ print_string(PRINT_ANY,
+ "link_type",
+ " link/%s ",
+ ll_type_n2a(ifi->ifi_type, b1, sizeof(b1)));
+ if (tb[IFLA_ADDRESS]) {
+ print_color_string(PRINT_ANY,
+ COLOR_MAC,
+ "address",
+ "%s",
+ ll_addr_n2a(RTA_DATA(tb[IFLA_ADDRESS]),
+ RTA_PAYLOAD(tb[IFLA_ADDRESS]),
+ ifi->ifi_type,
+ b1, sizeof(b1)));
+ }
+ if (tb[IFLA_BROADCAST]) {
+ if (ifi->ifi_flags&IFF_POINTOPOINT) {
+ print_string(PRINT_FP, NULL, " peer ", NULL);
+ print_bool(PRINT_JSON,
+ "link_pointtopoint", NULL, true);
+ } else {
+ print_string(PRINT_FP, NULL, " brd ", NULL);
+ }
+ print_color_string(PRINT_ANY,
+ COLOR_MAC,
+ "broadcast",
+ "%s",
+ ll_addr_n2a(RTA_DATA(tb[IFLA_BROADCAST]),
+ RTA_PAYLOAD(tb[IFLA_BROADCAST]),
+ ifi->ifi_type,
+ b1, sizeof(b1)));
+ }
+ if (tb[IFLA_PERM_ADDRESS]) {
+ unsigned int len = RTA_PAYLOAD(tb[IFLA_PERM_ADDRESS]);
+
+ if (!tb[IFLA_ADDRESS] ||
+ RTA_PAYLOAD(tb[IFLA_ADDRESS]) != len ||
+ memcmp(RTA_DATA(tb[IFLA_PERM_ADDRESS]),
+ RTA_DATA(tb[IFLA_ADDRESS]), len)) {
+ print_string(PRINT_FP, NULL, " permaddr ", NULL);
+ print_color_string(PRINT_ANY,
+ COLOR_MAC,
+ "permaddr",
+ "%s",
+ ll_addr_n2a(RTA_DATA(tb[IFLA_PERM_ADDRESS]),
+ RTA_PAYLOAD(tb[IFLA_PERM_ADDRESS]),
+ ifi->ifi_type,
+ b1, sizeof(b1)));
+ }
+ }
+ }
+
+ if (tb[IFLA_LINK_NETNSID]) {
+ int id = rta_getattr_u32(tb[IFLA_LINK_NETNSID]);
+
+ if (is_json_context()) {
+ print_int(PRINT_JSON, "link_netnsid", NULL, id);
+ } else {
+ if (id >= 0) {
+ char *name = get_name_from_nsid(id);
+
+ if (name)
+ print_string(PRINT_FP, NULL,
+ " link-netns %s", name);
+ else
+ print_int(PRINT_FP, NULL,
+ " link-netnsid %d", id);
+ } else
+ print_string(PRINT_FP, NULL,
+ " link-netnsid %s", "unknown");
+ }
+ }
+
+ if (tb[IFLA_NEW_NETNSID]) {
+ int id = rta_getattr_u32(tb[IFLA_NEW_NETNSID]);
+ char *name = get_name_from_nsid(id);
+
+ if (name)
+ print_string(PRINT_FP, NULL, " new-netns %s", name);
+ else
+ print_int(PRINT_FP, NULL, " new-netnsid %d", id);
+ }
+ if (tb[IFLA_NEW_IFINDEX]) {
+ int id = rta_getattr_u32(tb[IFLA_NEW_IFINDEX]);
+
+ print_int(PRINT_FP, NULL, " new-ifindex %d", id);
+ }
+
+ if (tb[IFLA_PROTO_DOWN])
+ print_proto_down(fp, tb);
+
+ if (show_details) {
+ if (tb[IFLA_PROMISCUITY])
+ print_uint(PRINT_ANY,
+ "promiscuity",
+ " promiscuity %u ",
+ rta_getattr_u32(tb[IFLA_PROMISCUITY]));
+
+ if (tb[IFLA_ALLMULTI])
+ print_uint(PRINT_ANY,
+ "allmulti",
+ "allmulti %u ",
+ rta_getattr_u32(tb[IFLA_ALLMULTI]));
+
+ if (tb[IFLA_MIN_MTU])
+ print_uint(PRINT_ANY,
+ "min_mtu", "minmtu %u ",
+ rta_getattr_u32(tb[IFLA_MIN_MTU]));
+
+ if (tb[IFLA_MAX_MTU])
+ print_uint(PRINT_ANY,
+ "max_mtu", "maxmtu %u ",
+ rta_getattr_u32(tb[IFLA_MAX_MTU]));
+
+ if (tb[IFLA_LINKINFO])
+ print_linktype(fp, tb[IFLA_LINKINFO]);
+
+ if (do_link && tb[IFLA_AF_SPEC])
+ print_af_spec(fp, tb[IFLA_AF_SPEC]);
+
+ if (tb[IFLA_NUM_TX_QUEUES])
+ print_uint(PRINT_ANY,
+ "num_tx_queues",
+ "numtxqueues %u ",
+ rta_getattr_u32(tb[IFLA_NUM_TX_QUEUES]));
+
+ if (tb[IFLA_NUM_RX_QUEUES])
+ print_uint(PRINT_ANY,
+ "num_rx_queues",
+ "numrxqueues %u ",
+ rta_getattr_u32(tb[IFLA_NUM_RX_QUEUES]));
+
+ if (tb[IFLA_GSO_MAX_SIZE])
+ print_uint(PRINT_ANY,
+ "gso_max_size",
+ "gso_max_size %u ",
+ rta_getattr_u32(tb[IFLA_GSO_MAX_SIZE]));
+
+ if (tb[IFLA_GSO_MAX_SEGS])
+ print_uint(PRINT_ANY,
+ "gso_max_segs",
+ "gso_max_segs %u ",
+ rta_getattr_u32(tb[IFLA_GSO_MAX_SEGS]));
+
+ if (tb[IFLA_TSO_MAX_SIZE])
+ print_uint(PRINT_ANY,
+ "tso_max_size",
+ "tso_max_size %u ",
+ rta_getattr_u32(tb[IFLA_TSO_MAX_SIZE]));
+
+ if (tb[IFLA_TSO_MAX_SEGS])
+ print_uint(PRINT_ANY,
+ "tso_max_segs",
+ "tso_max_segs %u ",
+ rta_getattr_u32(tb[IFLA_TSO_MAX_SEGS]));
+
+ if (tb[IFLA_GRO_MAX_SIZE])
+ print_uint(PRINT_ANY,
+ "gro_max_size",
+ "gro_max_size %u ",
+ rta_getattr_u32(tb[IFLA_GRO_MAX_SIZE]));
+
+ if (tb[IFLA_GSO_IPV4_MAX_SIZE])
+ print_uint(PRINT_ANY,
+ "gso_ipv4_max_size",
+ "gso_ipv4_max_size %u ",
+ rta_getattr_u32(tb[IFLA_GSO_IPV4_MAX_SIZE]));
+
+ if (tb[IFLA_GRO_IPV4_MAX_SIZE])
+ print_uint(PRINT_ANY,
+ "gro_ipv4_max_size",
+ "gro_ipv4_max_size %u ",
+ rta_getattr_u32(tb[IFLA_GRO_IPV4_MAX_SIZE]));
+
+ if (tb[IFLA_PHYS_PORT_NAME])
+ print_string(PRINT_ANY,
+ "phys_port_name",
+ "portname %s ",
+ rta_getattr_str(tb[IFLA_PHYS_PORT_NAME]));
+
+ if (tb[IFLA_PHYS_PORT_ID]) {
+ print_string(PRINT_ANY,
+ "phys_port_id",
+ "portid %s ",
+ hexstring_n2a(
+ RTA_DATA(tb[IFLA_PHYS_PORT_ID]),
+ RTA_PAYLOAD(tb[IFLA_PHYS_PORT_ID]),
+ b1, sizeof(b1)));
+ }
+
+ if (tb[IFLA_PHYS_SWITCH_ID]) {
+ print_string(PRINT_ANY,
+ "phys_switch_id",
+ "switchid %s ",
+ hexstring_n2a(RTA_DATA(tb[IFLA_PHYS_SWITCH_ID]),
+ RTA_PAYLOAD(tb[IFLA_PHYS_SWITCH_ID]),
+ b1, sizeof(b1)));
+ }
+
+ if (tb[IFLA_PARENT_DEV_BUS_NAME]) {
+ print_string(PRINT_ANY,
+ "parentbus",
+ "parentbus %s ",
+ rta_getattr_str(tb[IFLA_PARENT_DEV_BUS_NAME]));
+ }
+
+ if (tb[IFLA_PARENT_DEV_NAME]) {
+ print_string(PRINT_ANY,
+ "parentdev",
+ "parentdev %s ",
+ rta_getattr_str(tb[IFLA_PARENT_DEV_NAME]));
+ }
+ }
+
+ if ((do_link || show_details) && tb[IFLA_IFALIAS]) {
+ print_string(PRINT_FP, NULL, "%s ", _SL_);
+ print_string(PRINT_ANY,
+ "ifalias",
+ "alias %s",
+ rta_getattr_str(tb[IFLA_IFALIAS]));
+ }
+
+ if ((do_link || show_details) && tb[IFLA_XDP])
+ xdp_dump(fp, tb[IFLA_XDP], true, true);
+
+ if (do_link && show_stats) {
+ print_nl();
+ __print_link_stats(fp, tb);
+ }
+
+ if ((do_link || show_details) && tb[IFLA_VFINFO_LIST] && tb[IFLA_NUM_VF]) {
+ struct rtattr *i, *vflist = tb[IFLA_VFINFO_LIST];
+ int rem = RTA_PAYLOAD(vflist), count = 0;
+
+ open_json_array(PRINT_JSON, "vfinfo_list");
+ for (i = RTA_DATA(vflist); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+ open_json_object(NULL);
+ print_vfinfo(fp, ifi, i);
+ close_json_object();
+ count++;
+ }
+ close_json_array(PRINT_JSON, NULL);
+ if (count != rta_getattr_u32(tb[IFLA_NUM_VF]))
+ truncated_vfs = true;
+ }
+
+ if (tb[IFLA_PROP_LIST]) {
+ struct rtattr *i, *proplist = tb[IFLA_PROP_LIST];
+ int rem = RTA_PAYLOAD(proplist);
+
+ open_json_array(PRINT_JSON, "altnames");
+ for (i = RTA_DATA(proplist); RTA_OK(i, rem);
+ i = RTA_NEXT(i, rem)) {
+ if (i->rta_type != IFLA_ALT_IFNAME)
+ continue;
+ print_string(PRINT_FP, NULL, "%s altname ", _SL_);
+ print_string(PRINT_ANY, NULL,
+ "%s", rta_getattr_str(i));
+ }
+ close_json_array(PRINT_JSON, NULL);
+ }
+
+ print_string(PRINT_FP, NULL, "%s", "\n");
+ fflush(fp);
+ /* prettier here if stderr and stdout go to the same place */
+ if (truncated_vfs)
+ fprintf(stderr, "Truncated VF list: %s\n", name);
+ return 1;
+}
+
+static int flush_update(void)
+{
+
+ /*
+ * Note that the kernel may delete multiple addresses for one
+ * delete request (e.g. if ipv4 address promotion is disabled).
+ * Since a flush operation is really a series of delete requests
+ * its possible that we may request an address delete that has
+ * already been done by the kernel. Therefore, ignore EADDRNOTAVAIL
+ * errors returned from a flush request
+ */
+ if ((rtnl_send_check(&rth, filter.flushb, filter.flushp) < 0) &&
+ (errno != EADDRNOTAVAIL)) {
+ perror("Failed to send flush request");
+ return -1;
+ }
+ filter.flushp = 0;
+ return 0;
+}
+
+static int set_lifetime(unsigned int *lifetime, char *argv)
+{
+ if (strcmp(argv, "forever") == 0)
+ *lifetime = INFINITY_LIFE_TIME;
+ else if (get_u32(lifetime, argv, 0))
+ return -1;
+
+ return 0;
+}
+
+static unsigned int get_ifa_flags(struct ifaddrmsg *ifa,
+ struct rtattr *ifa_flags_attr)
+{
+ return ifa_flags_attr ? rta_getattr_u32(ifa_flags_attr) :
+ ifa->ifa_flags;
+}
+
+/* Mapping from argument to address flag mask and attributes */
+static const struct ifa_flag_data_t {
+ const char *name;
+ unsigned long mask;
+ bool readonly;
+ bool v6only;
+} ifa_flag_data[] = {
+ { .name = "secondary", .mask = IFA_F_SECONDARY, .readonly = true, .v6only = false},
+ { .name = "temporary", .mask = IFA_F_SECONDARY, .readonly = true, .v6only = false},
+ { .name = "nodad", .mask = IFA_F_NODAD, .readonly = false, .v6only = true},
+ { .name = "optimistic", .mask = IFA_F_OPTIMISTIC, .readonly = false, .v6only = true},
+ { .name = "dadfailed", .mask = IFA_F_DADFAILED, .readonly = true, .v6only = true},
+ { .name = "home", .mask = IFA_F_HOMEADDRESS, .readonly = false, .v6only = true},
+ { .name = "deprecated", .mask = IFA_F_DEPRECATED, .readonly = true, .v6only = true},
+ { .name = "tentative", .mask = IFA_F_TENTATIVE, .readonly = true, .v6only = true},
+ { .name = "permanent", .mask = IFA_F_PERMANENT, .readonly = true, .v6only = true},
+ { .name = "mngtmpaddr", .mask = IFA_F_MANAGETEMPADDR, .readonly = false, .v6only = true},
+ { .name = "noprefixroute", .mask = IFA_F_NOPREFIXROUTE, .readonly = false, .v6only = false},
+ { .name = "autojoin", .mask = IFA_F_MCAUTOJOIN, .readonly = false, .v6only = false},
+ { .name = "stable-privacy", .mask = IFA_F_STABLE_PRIVACY, .readonly = true, .v6only = true},
+};
+
+/* Returns a pointer to the data structure for a particular interface flag, or null if no flag could be found */
+static const struct ifa_flag_data_t* lookup_flag_data_by_name(const char* flag_name) {
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(ifa_flag_data); ++i) {
+ if (strcmp(flag_name, ifa_flag_data[i].name) == 0)
+ return &ifa_flag_data[i];
+ }
+ return NULL;
+}
+
+static void print_ifa_flags(FILE *fp, const struct ifaddrmsg *ifa,
+ unsigned int flags)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(ifa_flag_data); i++) {
+ const struct ifa_flag_data_t* flag_data = &ifa_flag_data[i];
+
+ if (flag_data->mask == IFA_F_PERMANENT) {
+ if (!(flags & flag_data->mask))
+ print_bool(PRINT_ANY,
+ "dynamic", "dynamic ", true);
+ } else if (flags & flag_data->mask) {
+ if (flag_data->mask == IFA_F_SECONDARY &&
+ ifa->ifa_family == AF_INET6) {
+ print_bool(PRINT_ANY,
+ "temporary", "temporary ", true);
+ } else {
+ print_string(PRINT_FP, NULL,
+ "%s ", flag_data->name);
+ print_bool(PRINT_JSON,
+ flag_data->name, NULL, true);
+ }
+ }
+
+ flags &= ~flag_data->mask;
+ }
+
+ if (flags) {
+ if (is_json_context()) {
+ SPRINT_BUF(b1);
+
+ snprintf(b1, sizeof(b1), "%02x", flags);
+ print_string(PRINT_JSON, "ifa_flags", NULL, b1);
+ } else {
+ fprintf(fp, "flags %02x ", flags);
+ }
+ }
+
+}
+
+static int get_filter(const char *arg)
+{
+ bool inv = false;
+
+ if (arg[0] == '-') {
+ inv = true;
+ arg++;
+ }
+
+ /* Special cases */
+ if (strcmp(arg, "dynamic") == 0) {
+ inv = !inv;
+ arg = "permanent";
+ } else if (strcmp(arg, "primary") == 0) {
+ inv = !inv;
+ arg = "secondary";
+ }
+
+ const struct ifa_flag_data_t* flag_data = lookup_flag_data_by_name(arg);
+ if (flag_data == NULL)
+ return -1;
+
+ if (inv)
+ filter.flags &= ~flag_data->mask;
+ else
+ filter.flags |= flag_data->mask;
+ filter.flagmask |= flag_data->mask;
+ return 0;
+}
+
+static int ifa_label_match_rta(int ifindex, const struct rtattr *rta)
+{
+ const char *label;
+
+ if (!filter.label)
+ return 0;
+
+ if (rta)
+ label = RTA_DATA(rta);
+ else
+ label = ll_index_to_name(ifindex);
+
+ return fnmatch(filter.label, label, 0);
+}
+
+int print_addrinfo(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = arg;
+ struct ifaddrmsg *ifa = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ unsigned int ifa_flags;
+ struct rtattr *rta_tb[IFA_MAX+1];
+
+ SPRINT_BUF(b1);
+
+ if (n->nlmsg_type != RTM_NEWADDR && n->nlmsg_type != RTM_DELADDR)
+ return 0;
+ len -= NLMSG_LENGTH(sizeof(*ifa));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ if (filter.flushb && n->nlmsg_type != RTM_NEWADDR)
+ return 0;
+
+ parse_rtattr(rta_tb, IFA_MAX, IFA_RTA(ifa),
+ n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)));
+
+ ifa_flags = get_ifa_flags(ifa, rta_tb[IFA_FLAGS]);
+
+ if (!rta_tb[IFA_LOCAL])
+ rta_tb[IFA_LOCAL] = rta_tb[IFA_ADDRESS];
+ if (!rta_tb[IFA_ADDRESS])
+ rta_tb[IFA_ADDRESS] = rta_tb[IFA_LOCAL];
+
+ if (filter.ifindex && filter.ifindex != ifa->ifa_index)
+ return 0;
+ if ((filter.scope^ifa->ifa_scope)&filter.scopemask)
+ return 0;
+ if ((filter.flags ^ ifa_flags) & filter.flagmask)
+ return 0;
+
+ if (filter.family && filter.family != ifa->ifa_family)
+ return 0;
+ if (filter.have_proto && rta_tb[IFA_PROTO] &&
+ filter.proto != rta_getattr_u8(rta_tb[IFA_PROTO]))
+ return 0;
+
+ if (ifa_label_match_rta(ifa->ifa_index, rta_tb[IFA_LABEL]))
+ return 0;
+
+ if (inet_addr_match_rta(&filter.pfx, rta_tb[IFA_LOCAL]))
+ return 0;
+
+ if (filter.flushb) {
+ struct nlmsghdr *fn;
+
+ if (NLMSG_ALIGN(filter.flushp) + n->nlmsg_len > filter.flushe) {
+ if (flush_update())
+ return -1;
+ }
+ fn = (struct nlmsghdr *)(filter.flushb + NLMSG_ALIGN(filter.flushp));
+ memcpy(fn, n, n->nlmsg_len);
+ fn->nlmsg_type = RTM_DELADDR;
+ fn->nlmsg_flags = NLM_F_REQUEST;
+ fn->nlmsg_seq = ++rth.seq;
+ filter.flushp = (((char *)fn) + n->nlmsg_len) - filter.flushb;
+ filter.flushed++;
+ if (show_stats < 2)
+ return 0;
+ }
+
+ if (n->nlmsg_type == RTM_DELADDR)
+ print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+ if (!brief) {
+ const char *name;
+
+ if (filter.oneline || filter.flushb || echo_request) {
+ const char *dev = ll_index_to_name(ifa->ifa_index);
+
+ if (is_json_context()) {
+ print_int(PRINT_JSON,
+ "index", NULL, ifa->ifa_index);
+ print_string(PRINT_JSON, "dev", NULL, dev);
+ } else {
+ fprintf(fp, "%u: %s", ifa->ifa_index, dev);
+ }
+ }
+
+ name = family_name(ifa->ifa_family);
+ if (*name != '?') {
+ print_string(PRINT_ANY, "family", " %s ", name);
+ } else {
+ print_int(PRINT_ANY, "family_index", " family %d ",
+ ifa->ifa_family);
+ }
+ }
+
+ if (rta_tb[IFA_LOCAL]) {
+ print_color_string(PRINT_ANY,
+ ifa_family_color(ifa->ifa_family),
+ "local", "%s",
+ format_host_rta(ifa->ifa_family,
+ rta_tb[IFA_LOCAL]));
+ if (rta_tb[IFA_ADDRESS] &&
+ memcmp(RTA_DATA(rta_tb[IFA_ADDRESS]),
+ RTA_DATA(rta_tb[IFA_LOCAL]),
+ ifa->ifa_family == AF_INET ? 4 : 16)) {
+ print_string(PRINT_FP, NULL, " %s ", "peer");
+ print_color_string(PRINT_ANY,
+ ifa_family_color(ifa->ifa_family),
+ "address",
+ "%s",
+ format_host_rta(ifa->ifa_family,
+ rta_tb[IFA_ADDRESS]));
+ }
+ print_int(PRINT_ANY, "prefixlen", "/%d ", ifa->ifa_prefixlen);
+
+ if (rta_tb[IFA_RT_PRIORITY])
+ print_uint(PRINT_ANY, "metric", "metric %u ",
+ rta_getattr_u32(rta_tb[IFA_RT_PRIORITY]));
+ }
+
+ if (brief)
+ goto brief_exit;
+
+ if (rta_tb[IFA_BROADCAST]) {
+ print_string(PRINT_FP, NULL, "%s ", "brd");
+ print_color_string(PRINT_ANY,
+ ifa_family_color(ifa->ifa_family),
+ "broadcast",
+ "%s ",
+ format_host_rta(ifa->ifa_family,
+ rta_tb[IFA_BROADCAST]));
+ }
+
+ if (rta_tb[IFA_ANYCAST]) {
+ print_string(PRINT_FP, NULL, "%s ", "any");
+ print_color_string(PRINT_ANY,
+ ifa_family_color(ifa->ifa_family),
+ "anycast",
+ "%s ",
+ format_host_rta(ifa->ifa_family,
+ rta_tb[IFA_ANYCAST]));
+ }
+
+ print_string(PRINT_ANY,
+ "scope",
+ "scope %s ",
+ rtnl_rtscope_n2a(ifa->ifa_scope, b1, sizeof(b1)));
+
+ print_ifa_flags(fp, ifa, ifa_flags);
+
+ if (rta_tb[IFA_PROTO]) {
+ __u8 proto = rta_getattr_u8(rta_tb[IFA_PROTO]);
+
+ if (proto || is_json_context())
+ print_string(PRINT_ANY, "protocol", "proto %s ",
+ rtnl_addrprot_n2a(proto, b1, sizeof(b1)));
+ }
+
+ if (rta_tb[IFA_LABEL])
+ print_string(PRINT_ANY,
+ "label",
+ "%s",
+ rta_getattr_str(rta_tb[IFA_LABEL]));
+
+ if (rta_tb[IFA_CACHEINFO]) {
+ struct ifa_cacheinfo *ci = RTA_DATA(rta_tb[IFA_CACHEINFO]);
+
+ print_nl();
+ print_string(PRINT_FP, NULL, " valid_lft ", NULL);
+
+ if (ci->ifa_valid == INFINITY_LIFE_TIME) {
+ print_uint(PRINT_JSON,
+ "valid_life_time",
+ NULL, INFINITY_LIFE_TIME);
+ print_string(PRINT_FP, NULL, "%s", "forever");
+ } else {
+ print_uint(PRINT_ANY,
+ "valid_life_time", "%usec", ci->ifa_valid);
+ }
+
+ print_string(PRINT_FP, NULL, " preferred_lft ", NULL);
+ if (ci->ifa_prefered == INFINITY_LIFE_TIME) {
+ print_uint(PRINT_JSON,
+ "preferred_life_time",
+ NULL, INFINITY_LIFE_TIME);
+ print_string(PRINT_FP, NULL, "%s", "forever");
+ } else {
+ if (ifa_flags & IFA_F_DEPRECATED)
+ print_int(PRINT_ANY,
+ "preferred_life_time",
+ "%dsec",
+ ci->ifa_prefered);
+ else
+ print_uint(PRINT_ANY,
+ "preferred_life_time",
+ "%usec",
+ ci->ifa_prefered);
+ }
+ }
+ print_string(PRINT_FP, NULL, "%s", "\n");
+brief_exit:
+ fflush(fp);
+ return 0;
+}
+
+static int print_selected_addrinfo(struct ifinfomsg *ifi,
+ struct nlmsg_list *ainfo, FILE *fp)
+{
+ open_json_array(PRINT_JSON, "addr_info");
+ for ( ; ainfo ; ainfo = ainfo->next) {
+ struct nlmsghdr *n = &ainfo->h;
+ struct ifaddrmsg *ifa = NLMSG_DATA(n);
+
+ if (n->nlmsg_type != RTM_NEWADDR)
+ continue;
+
+ if (n->nlmsg_len < NLMSG_LENGTH(sizeof(*ifa)))
+ return -1;
+
+ if (ifa->ifa_index != ifi->ifi_index ||
+ (filter.family && filter.family != ifa->ifa_family))
+ continue;
+
+ if (filter.up && !(ifi->ifi_flags&IFF_UP))
+ continue;
+
+ open_json_object(NULL);
+ print_addrinfo(n, fp);
+ close_json_object();
+ }
+ close_json_array(PRINT_JSON, NULL);
+
+ if (brief) {
+ print_string(PRINT_FP, NULL, "%s", "\n");
+ fflush(fp);
+ }
+ return 0;
+}
+
+
+static int store_nlmsg(struct nlmsghdr *n, void *arg)
+{
+ struct nlmsg_chain *lchain = (struct nlmsg_chain *)arg;
+ struct nlmsg_list *h;
+
+ h = malloc(n->nlmsg_len+sizeof(void *));
+ if (h == NULL)
+ return -1;
+
+ memcpy(&h->h, n, n->nlmsg_len);
+ h->next = NULL;
+
+ if (lchain->tail)
+ lchain->tail->next = h;
+ else
+ lchain->head = h;
+ lchain->tail = h;
+
+ ll_remember_index(n, NULL);
+ return 0;
+}
+
+static __u32 ipadd_dump_magic = 0x47361222;
+
+static int ipadd_save_prep(void)
+{
+ int ret;
+
+ if (isatty(STDOUT_FILENO)) {
+ fprintf(stderr, "Not sending a binary stream to stdout\n");
+ return -1;
+ }
+
+ ret = write(STDOUT_FILENO, &ipadd_dump_magic, sizeof(ipadd_dump_magic));
+ if (ret != sizeof(ipadd_dump_magic)) {
+ fprintf(stderr, "Can't write magic to dump file\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int ipadd_dump_check_magic(void)
+{
+ int ret;
+ __u32 magic = 0;
+
+ if (isatty(STDIN_FILENO)) {
+ fprintf(stderr, "Can't restore address dump from a terminal\n");
+ return -1;
+ }
+
+ ret = fread(&magic, sizeof(magic), 1, stdin);
+ if (magic != ipadd_dump_magic) {
+ fprintf(stderr, "Magic mismatch (%d elems, %x magic)\n", ret, magic);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int save_nlmsg(struct nlmsghdr *n, void *arg)
+{
+ int ret;
+
+ ret = write(STDOUT_FILENO, n, n->nlmsg_len);
+ if ((ret > 0) && (ret != n->nlmsg_len)) {
+ fprintf(stderr, "Short write while saving nlmsg\n");
+ ret = -EIO;
+ }
+
+ return ret == n->nlmsg_len ? 0 : ret;
+}
+
+static int show_handler(struct rtnl_ctrl_data *ctrl,
+ struct nlmsghdr *n, void *arg)
+{
+ struct ifaddrmsg *ifa = NLMSG_DATA(n);
+
+ open_json_object(NULL);
+ print_int(PRINT_ANY, "index", "if%d:", ifa->ifa_index);
+ print_nl();
+ print_addrinfo(n, stdout);
+ close_json_object();
+ return 0;
+}
+
+static int ipaddr_showdump(void)
+{
+ int err;
+
+ if (ipadd_dump_check_magic())
+ exit(-1);
+
+ new_json_obj(json);
+ open_json_object(NULL);
+ open_json_array(PRINT_JSON, "addr_info");
+
+ err = rtnl_from_file(stdin, &show_handler, NULL);
+
+ close_json_array(PRINT_JSON, NULL);
+ close_json_object();
+ delete_json_obj();
+
+ exit(err);
+}
+
+static int restore_handler(struct rtnl_ctrl_data *ctrl,
+ struct nlmsghdr *n, void *arg)
+{
+ int ret;
+
+ n->nlmsg_flags |= NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK;
+
+ ll_init_map(&rth);
+
+ ret = rtnl_talk(&rth, n, NULL);
+ if ((ret < 0) && (errno == EEXIST))
+ ret = 0;
+
+ return ret;
+}
+
+static int ipaddr_restore(void)
+{
+ if (ipadd_dump_check_magic())
+ exit(-1);
+
+ exit(rtnl_from_file(stdin, &restore_handler, NULL));
+}
+
+void free_nlmsg_chain(struct nlmsg_chain *info)
+{
+ struct nlmsg_list *l, *n;
+
+ for (l = info->head; l; l = n) {
+ n = l->next;
+ free(l);
+ }
+}
+
+static void ipaddr_filter(struct nlmsg_chain *linfo, struct nlmsg_chain *ainfo)
+{
+ struct nlmsg_list *l, **lp;
+
+ lp = &linfo->head;
+ while ((l = *lp) != NULL) {
+ int ok = 0;
+ int missing_net_address = 1;
+ struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
+ struct nlmsg_list *a;
+
+ for (a = ainfo->head; a; a = a->next) {
+ struct nlmsghdr *n = &a->h;
+ struct ifaddrmsg *ifa = NLMSG_DATA(n);
+ struct rtattr *tb[IFA_MAX + 1];
+ unsigned int ifa_flags;
+
+ if (ifa->ifa_index != ifi->ifi_index)
+ continue;
+ missing_net_address = 0;
+ if (filter.family && filter.family != ifa->ifa_family)
+ continue;
+ if ((filter.scope^ifa->ifa_scope)&filter.scopemask)
+ continue;
+
+ parse_rtattr(tb, IFA_MAX, IFA_RTA(ifa), IFA_PAYLOAD(n));
+ ifa_flags = get_ifa_flags(ifa, tb[IFA_FLAGS]);
+
+ if ((filter.flags ^ ifa_flags) & filter.flagmask)
+ continue;
+
+ if (ifa_label_match_rta(ifa->ifa_index, tb[IFA_LABEL]))
+ continue;
+
+ if (!tb[IFA_LOCAL])
+ tb[IFA_LOCAL] = tb[IFA_ADDRESS];
+ if (inet_addr_match_rta(&filter.pfx, tb[IFA_LOCAL]))
+ continue;
+
+ ok = 1;
+ break;
+ }
+ if (missing_net_address &&
+ (filter.family == AF_UNSPEC || filter.family == AF_PACKET))
+ ok = 1;
+ if (!ok) {
+ *lp = l->next;
+ free(l);
+ } else
+ lp = &l->next;
+ }
+}
+
+static int ipaddr_dump_filter(struct nlmsghdr *nlh, int reqlen)
+{
+ struct ifaddrmsg *ifa = NLMSG_DATA(nlh);
+
+ ifa->ifa_index = filter.ifindex;
+
+ return 0;
+}
+
+static int ipaddr_flush(void)
+{
+ int round = 0;
+ char flushb[4096-512];
+
+ filter.flushb = flushb;
+ filter.flushp = 0;
+ filter.flushe = sizeof(flushb);
+
+ while ((max_flush_loops == 0) || (round < max_flush_loops)) {
+ if (rtnl_addrdump_req(&rth, filter.family,
+ ipaddr_dump_filter) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+ filter.flushed = 0;
+ if (rtnl_dump_filter_nc(&rth, print_addrinfo,
+ stdout, NLM_F_DUMP_INTR) < 0) {
+ fprintf(stderr, "Flush terminated\n");
+ exit(1);
+ }
+ if (filter.flushed == 0) {
+ flush_done:
+ if (show_stats) {
+ if (round == 0)
+ printf("Nothing to flush.\n");
+ else
+ printf("*** Flush is complete after %d round%s ***\n", round, round > 1?"s":"");
+ }
+ fflush(stdout);
+ return 0;
+ }
+ round++;
+ if (flush_update() < 0)
+ return 1;
+
+ if (show_stats) {
+ printf("\n*** Round %d, deleting %d addresses ***\n", round, filter.flushed);
+ fflush(stdout);
+ }
+
+ /* If we are flushing, and specifying primary, then we
+ * want to flush only a single round. Otherwise, we'll
+ * start flushing secondaries that were promoted to
+ * primaries.
+ */
+ if (!(filter.flags & IFA_F_SECONDARY) && (filter.flagmask & IFA_F_SECONDARY))
+ goto flush_done;
+ }
+ fprintf(stderr, "*** Flush remains incomplete after %d rounds. ***\n", max_flush_loops);
+ fflush(stderr);
+ return 1;
+}
+
+static int iplink_filter_req(struct nlmsghdr *nlh, int reqlen)
+{
+ __u32 filt_mask;
+ int err;
+
+ filt_mask = RTEXT_FILTER_VF;
+ if (!show_stats)
+ filt_mask |= RTEXT_FILTER_SKIP_STATS;
+ err = addattr32(nlh, reqlen, IFLA_EXT_MASK, filt_mask);
+ if (err)
+ return err;
+
+ if (filter.master) {
+ err = addattr32(nlh, reqlen, IFLA_MASTER, filter.master);
+ if (err)
+ return err;
+ }
+
+ if (filter.kind) {
+ struct rtattr *linkinfo;
+
+ linkinfo = addattr_nest(nlh, reqlen, IFLA_LINKINFO);
+
+ err = addattr_l(nlh, reqlen, IFLA_INFO_KIND, filter.kind,
+ strlen(filter.kind));
+ if (err)
+ return err;
+
+ addattr_nest_end(nlh, linkinfo);
+ }
+
+ return 0;
+}
+
+static int ipaddr_link_get(int index, struct nlmsg_chain *linfo)
+{
+ struct iplink_req req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_GETLINK,
+ .i.ifi_family = filter.family,
+ .i.ifi_index = index,
+ };
+ __u32 filt_mask = RTEXT_FILTER_VF;
+ struct nlmsghdr *answer;
+
+ if (!show_stats)
+ filt_mask |= RTEXT_FILTER_SKIP_STATS;
+
+ addattr32(&req.n, sizeof(req), IFLA_EXT_MASK, filt_mask);
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0) {
+ perror("Cannot send link request");
+ return 1;
+ }
+
+ if (store_nlmsg(answer, linfo) < 0) {
+ fprintf(stderr, "Failed to process link information\n");
+ free(answer);
+ return 1;
+ }
+ free(answer);
+
+ return 0;
+}
+
+/* fills in linfo with link data and optionally ainfo with address info
+ * caller can walk lists as desired and must call free_nlmsg_chain for
+ * both when done
+ */
+int ip_link_list(req_filter_fn_t filter_fn, struct nlmsg_chain *linfo)
+{
+ if (rtnl_linkdump_req_filter_fn(&rth, preferred_family,
+ filter_fn) < 0) {
+ perror("Cannot send dump request");
+ return 1;
+ }
+
+ if (rtnl_dump_filter(&rth, store_nlmsg, linfo) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+static int ip_addr_list(struct nlmsg_chain *ainfo)
+{
+ if (rtnl_addrdump_req(&rth, filter.family, ipaddr_dump_filter) < 0) {
+ perror("Cannot send dump request");
+ return 1;
+ }
+
+ if (rtnl_dump_filter(&rth, store_nlmsg, ainfo) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+static int ipaddr_list_flush_or_save(int argc, char **argv, int action)
+{
+ struct nlmsg_chain linfo = { NULL, NULL};
+ struct nlmsg_chain _ainfo = { NULL, NULL}, *ainfo = &_ainfo;
+ struct nlmsg_list *l;
+ char *filter_dev = NULL;
+ int no_link = 0;
+
+ ipaddr_reset_filter(oneline, 0);
+ filter.showqueue = 1;
+ filter.family = preferred_family;
+
+ if (action == IPADD_FLUSH) {
+ if (argc <= 0) {
+ fprintf(stderr, "Flush requires arguments.\n");
+
+ return -1;
+ }
+ if (filter.family == AF_PACKET) {
+ fprintf(stderr, "Cannot flush link addresses.\n");
+ return -1;
+ }
+ }
+
+ while (argc > 0) {
+ if (strcmp(*argv, "to") == 0) {
+ NEXT_ARG();
+ if (get_prefix(&filter.pfx, *argv, filter.family))
+ invarg("invalid \"to\"\n", *argv);
+ if (filter.family == AF_UNSPEC)
+ filter.family = filter.pfx.family;
+ } else if (strcmp(*argv, "scope") == 0) {
+ unsigned int scope = 0;
+
+ NEXT_ARG();
+ filter.scopemask = -1;
+ if (rtnl_rtscope_a2n(&scope, *argv)) {
+ if (strcmp(*argv, "all") != 0)
+ invarg("invalid \"scope\"\n", *argv);
+ scope = RT_SCOPE_NOWHERE;
+ filter.scopemask = 0;
+ }
+ filter.scope = scope;
+ } else if (strcmp(*argv, "up") == 0) {
+ filter.up = 1;
+ } else if (get_filter(*argv) == 0) {
+
+ } else if (strcmp(*argv, "label") == 0) {
+ NEXT_ARG();
+ filter.label = *argv;
+ } else if (strcmp(*argv, "group") == 0) {
+ NEXT_ARG();
+ if (rtnl_group_a2n(&filter.group, *argv))
+ invarg("Invalid \"group\" value\n", *argv);
+ } else if (strcmp(*argv, "master") == 0) {
+ int ifindex;
+
+ NEXT_ARG();
+ ifindex = ll_name_to_index(*argv);
+ if (!ifindex)
+ invarg("Device does not exist\n", *argv);
+ filter.master = ifindex;
+ } else if (strcmp(*argv, "vrf") == 0) {
+ int ifindex;
+
+ NEXT_ARG();
+ ifindex = ll_name_to_index(*argv);
+ if (!ifindex)
+ invarg("Not a valid VRF name\n", *argv);
+ if (!name_is_vrf(*argv))
+ invarg("Not a valid VRF name\n", *argv);
+ filter.master = ifindex;
+ } else if (strcmp(*argv, "nomaster") == 0) {
+ filter.master = -1;
+ } else if (strcmp(*argv, "type") == 0) {
+ int soff;
+
+ NEXT_ARG();
+ soff = strlen(*argv) - strlen("_slave");
+ if (!strcmp(*argv + soff, "_slave")) {
+ (*argv)[soff] = '\0';
+ filter.slave_kind = *argv;
+ } else {
+ filter.kind = *argv;
+ }
+ } else if (strcmp(*argv, "proto") == 0) {
+ __u8 proto;
+
+ NEXT_ARG();
+ if (get_u8(&proto, *argv, 0))
+ invarg("\"proto\" value is invalid\n", *argv);
+ filter.have_proto = true;
+ filter.proto = proto;
+ } else {
+ if (strcmp(*argv, "dev") == 0)
+ NEXT_ARG();
+ else if (matches(*argv, "help") == 0)
+ usage();
+ if (filter_dev)
+ duparg2("dev", *argv);
+ filter_dev = *argv;
+ }
+ argv++; argc--;
+ }
+
+ if (filter_dev) {
+ filter.ifindex = ll_name_to_index(filter_dev);
+ if (filter.ifindex <= 0) {
+ fprintf(stderr, "Device \"%s\" does not exist.\n", filter_dev);
+ return -1;
+ }
+ }
+
+ if (action == IPADD_FLUSH)
+ return ipaddr_flush();
+
+ if (action == IPADD_SAVE) {
+ if (ipadd_save_prep())
+ exit(1);
+
+ if (rtnl_addrdump_req(&rth, preferred_family,
+ ipaddr_dump_filter) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+
+ if (rtnl_dump_filter(&rth, save_nlmsg, stdout) < 0) {
+ fprintf(stderr, "Save terminated\n");
+ exit(1);
+ }
+
+ exit(0);
+ }
+
+ /*
+ * Initialize a json_writer and open an array object
+ * if -json was specified.
+ */
+ new_json_obj(json);
+
+ /*
+ * If only filter_dev present and none of the other
+ * link filters are present, use RTM_GETLINK to get
+ * the link device
+ */
+ if (filter_dev && filter.group == -1 && do_link == 1) {
+ if (iplink_get(filter_dev, RTEXT_FILTER_VF) < 0) {
+ perror("Cannot send link get request");
+ delete_json_obj();
+ exit(1);
+ }
+ delete_json_obj();
+ goto out;
+ }
+
+ if (filter.ifindex) {
+ if (ipaddr_link_get(filter.ifindex, &linfo) != 0)
+ goto out;
+ } else {
+ if (ip_link_list(iplink_filter_req, &linfo) != 0)
+ goto out;
+ }
+
+ if (filter.family != AF_PACKET) {
+ if (filter.oneline)
+ no_link = 1;
+
+ if (ip_addr_list(ainfo) != 0)
+ goto out;
+
+ ipaddr_filter(&linfo, ainfo);
+ }
+
+ for (l = linfo.head; l; l = l->next) {
+ struct nlmsghdr *n = &l->h;
+ struct ifinfomsg *ifi = NLMSG_DATA(n);
+ int res = 0;
+
+ open_json_object(NULL);
+ if (brief || !no_link)
+ res = print_linkinfo(n, stdout);
+ if (res >= 0 && filter.family != AF_PACKET)
+ print_selected_addrinfo(ifi, ainfo->head, stdout);
+ if (res > 0 && !do_link && show_stats)
+ print_link_stats(stdout, n);
+ close_json_object();
+ }
+ fflush(stdout);
+
+out:
+ free_nlmsg_chain(ainfo);
+ free_nlmsg_chain(&linfo);
+ delete_json_obj();
+ return 0;
+}
+
+static void
+ipaddr_loop_each_vf(struct rtattr *tb[], int vfnum, int *min, int *max)
+{
+ struct rtattr *vflist = tb[IFLA_VFINFO_LIST];
+ struct rtattr *i, *vf[IFLA_VF_MAX+1];
+ struct ifla_vf_rate *vf_rate;
+ int rem;
+
+ rem = RTA_PAYLOAD(vflist);
+
+ for (i = RTA_DATA(vflist); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+ parse_rtattr_nested(vf, IFLA_VF_MAX, i);
+
+ if (!vf[IFLA_VF_RATE]) {
+ fprintf(stderr, "VF min/max rate API not supported\n");
+ exit(1);
+ }
+
+ vf_rate = RTA_DATA(vf[IFLA_VF_RATE]);
+ if (vf_rate->vf == vfnum) {
+ *min = vf_rate->min_tx_rate;
+ *max = vf_rate->max_tx_rate;
+ return;
+ }
+ }
+ fprintf(stderr, "Cannot find VF %d\n", vfnum);
+ exit(1);
+}
+
+void ipaddr_get_vf_rate(int vfnum, int *min, int *max, const char *dev)
+{
+ struct nlmsg_chain linfo = { NULL, NULL};
+ struct rtattr *tb[IFLA_MAX+1];
+ struct ifinfomsg *ifi;
+ struct nlmsg_list *l;
+ struct nlmsghdr *n;
+ int idx, len;
+
+ idx = ll_name_to_index(dev);
+ if (idx == 0) {
+ fprintf(stderr, "Device %s does not exist\n", dev);
+ exit(1);
+ }
+
+ if (rtnl_linkdump_req(&rth, AF_UNSPEC) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+ if (rtnl_dump_filter(&rth, store_nlmsg, &linfo) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ for (l = linfo.head; l; l = l->next) {
+ n = &l->h;
+ ifi = NLMSG_DATA(n);
+
+ len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi));
+ if (len < 0 || (idx && idx != ifi->ifi_index))
+ continue;
+
+ parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+
+ if ((tb[IFLA_VFINFO_LIST] && tb[IFLA_NUM_VF])) {
+ ipaddr_loop_each_vf(tb, vfnum, min, max);
+ return;
+ }
+ }
+}
+
+int ipaddr_list_link(int argc, char **argv)
+{
+ preferred_family = AF_PACKET;
+ do_link = 1;
+ return ipaddr_list_flush_or_save(argc, argv, IPADD_LIST);
+}
+
+void ipaddr_reset_filter(int oneline, int ifindex)
+{
+ memset(&filter, 0, sizeof(filter));
+ filter.oneline = oneline;
+ filter.ifindex = ifindex;
+ filter.group = -1;
+}
+
+static int default_scope(inet_prefix *lcl)
+{
+ if (lcl->family == AF_INET) {
+ if (lcl->bytelen >= 1 && *(__u8 *)&lcl->data == 127)
+ return RT_SCOPE_HOST;
+ }
+ return 0;
+}
+
+static bool ipaddr_is_multicast(inet_prefix *a)
+{
+ if (a->family == AF_INET)
+ return IN_MULTICAST(ntohl(a->data[0]));
+ else if (a->family == AF_INET6)
+ return IN6_IS_ADDR_MULTICAST(a->data);
+ else
+ return false;
+}
+
+static int ipaddr_modify(int cmd, int flags, int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct ifaddrmsg ifa;
+ char buf[256];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST | flags,
+ .n.nlmsg_type = cmd,
+ .ifa.ifa_family = preferred_family,
+ };
+ char *d = NULL;
+ char *l = NULL;
+ char *lcl_arg = NULL;
+ char *valid_lftp = NULL;
+ char *preferred_lftp = NULL;
+ inet_prefix lcl = {};
+ inet_prefix peer;
+ int local_len = 0;
+ int peer_len = 0;
+ int brd_len = 0;
+ int any_len = 0;
+ int scoped = 0;
+ __u32 preferred_lft = INFINITY_LIFE_TIME;
+ __u32 valid_lft = INFINITY_LIFE_TIME;
+ unsigned int ifa_flags = 0;
+ int ret;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "peer") == 0 ||
+ strcmp(*argv, "remote") == 0) {
+ NEXT_ARG();
+
+ if (peer_len)
+ duparg("peer", *argv);
+ get_prefix(&peer, *argv, req.ifa.ifa_family);
+ peer_len = peer.bytelen;
+ if (req.ifa.ifa_family == AF_UNSPEC)
+ req.ifa.ifa_family = peer.family;
+ addattr_l(&req.n, sizeof(req), IFA_ADDRESS, &peer.data, peer.bytelen);
+ req.ifa.ifa_prefixlen = peer.bitlen;
+ } else if (matches(*argv, "broadcast") == 0 ||
+ strcmp(*argv, "brd") == 0) {
+ inet_prefix addr;
+
+ NEXT_ARG();
+ if (brd_len)
+ duparg("broadcast", *argv);
+ if (strcmp(*argv, "+") == 0)
+ brd_len = -1;
+ else if (strcmp(*argv, "-") == 0)
+ brd_len = -2;
+ else {
+ get_addr(&addr, *argv, req.ifa.ifa_family);
+ if (req.ifa.ifa_family == AF_UNSPEC)
+ req.ifa.ifa_family = addr.family;
+ addattr_l(&req.n, sizeof(req), IFA_BROADCAST, &addr.data, addr.bytelen);
+ brd_len = addr.bytelen;
+ }
+ } else if (strcmp(*argv, "anycast") == 0) {
+ inet_prefix addr;
+
+ NEXT_ARG();
+ if (any_len)
+ duparg("anycast", *argv);
+ get_addr(&addr, *argv, req.ifa.ifa_family);
+ if (req.ifa.ifa_family == AF_UNSPEC)
+ req.ifa.ifa_family = addr.family;
+ addattr_l(&req.n, sizeof(req), IFA_ANYCAST, &addr.data, addr.bytelen);
+ any_len = addr.bytelen;
+ } else if (strcmp(*argv, "scope") == 0) {
+ unsigned int scope = 0;
+
+ NEXT_ARG();
+ if (rtnl_rtscope_a2n(&scope, *argv))
+ invarg("invalid scope value.", *argv);
+ req.ifa.ifa_scope = scope;
+ scoped = 1;
+ } else if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ d = *argv;
+ } else if (strcmp(*argv, "label") == 0) {
+ NEXT_ARG();
+ l = *argv;
+ addattr_l(&req.n, sizeof(req), IFA_LABEL, l, strlen(l)+1);
+ } else if (matches(*argv, "metric") == 0 ||
+ matches(*argv, "priority") == 0 ||
+ matches(*argv, "preference") == 0) {
+ __u32 metric;
+
+ NEXT_ARG();
+ if (get_u32(&metric, *argv, 0))
+ invarg("\"metric\" value is invalid\n", *argv);
+ addattr32(&req.n, sizeof(req), IFA_RT_PRIORITY, metric);
+ } else if (matches(*argv, "valid_lft") == 0) {
+ if (valid_lftp)
+ duparg("valid_lft", *argv);
+ NEXT_ARG();
+ valid_lftp = *argv;
+ if (set_lifetime(&valid_lft, *argv))
+ invarg("valid_lft value", *argv);
+ } else if (matches(*argv, "preferred_lft") == 0) {
+ if (preferred_lftp)
+ duparg("preferred_lft", *argv);
+ NEXT_ARG();
+ preferred_lftp = *argv;
+ if (set_lifetime(&preferred_lft, *argv))
+ invarg("preferred_lft value", *argv);
+ } else if (lookup_flag_data_by_name(*argv)) {
+ const struct ifa_flag_data_t* flag_data = lookup_flag_data_by_name(*argv);
+ if (flag_data->readonly) {
+ fprintf(stderr, "Warning: %s option is not mutable from userspace\n", flag_data->name);
+ } else if (flag_data->v6only && req.ifa.ifa_family != AF_INET6) {
+ fprintf(stderr, "Warning: %s option can be set only for IPv6 addresses\n", flag_data->name);
+ } else {
+ ifa_flags |= flag_data->mask;
+ }
+ } else if (strcmp(*argv, "proto") == 0) {
+ __u8 proto;
+
+ NEXT_ARG();
+ if (rtnl_addrprot_a2n(&proto, *argv))
+ invarg("\"proto\" value is invalid\n", *argv);
+ addattr8(&req.n, sizeof(req), IFA_PROTO, proto);
+ } else {
+ if (strcmp(*argv, "local") == 0)
+ NEXT_ARG();
+ if (matches(*argv, "help") == 0)
+ usage();
+ if (local_len)
+ duparg2("local", *argv);
+ lcl_arg = *argv;
+ get_prefix(&lcl, *argv, req.ifa.ifa_family);
+ if (req.ifa.ifa_family == AF_UNSPEC)
+ req.ifa.ifa_family = lcl.family;
+ addattr_l(&req.n, sizeof(req), IFA_LOCAL, &lcl.data, lcl.bytelen);
+ local_len = lcl.bytelen;
+ }
+ argc--; argv++;
+ }
+ if (ifa_flags <= 0xff)
+ req.ifa.ifa_flags = ifa_flags;
+ else
+ addattr32(&req.n, sizeof(req), IFA_FLAGS, ifa_flags);
+
+ if (d == NULL) {
+ fprintf(stderr, "Not enough information: \"dev\" argument is required.\n");
+ return -1;
+ }
+
+ if (peer_len == 0 && local_len) {
+ if (cmd == RTM_DELADDR && lcl.family == AF_INET && !(lcl.flags & PREFIXLEN_SPECIFIED)) {
+ fprintf(stderr,
+ "Warning: Executing wildcard deletion to stay compatible with old scripts.\n"
+ " Explicitly specify the prefix length (%s/%d) to avoid this warning.\n"
+ " This special behaviour is likely to disappear in further releases,\n"
+ " fix your scripts!\n", lcl_arg, local_len*8);
+ } else {
+ peer = lcl;
+ addattr_l(&req.n, sizeof(req), IFA_ADDRESS, &lcl.data, lcl.bytelen);
+ }
+ }
+ if (req.ifa.ifa_prefixlen == 0)
+ req.ifa.ifa_prefixlen = lcl.bitlen;
+
+ if (brd_len < 0 && cmd != RTM_DELADDR) {
+ inet_prefix brd;
+ int i;
+
+ if (req.ifa.ifa_family != AF_INET) {
+ fprintf(stderr, "Broadcast can be set only for IPv4 addresses\n");
+ return -1;
+ }
+ brd = peer;
+ if (brd.bitlen <= 30) {
+ for (i = 31; i >= brd.bitlen; i--) {
+ if (brd_len == -1)
+ brd.data[0] |= htonl(1<<(31-i));
+ else
+ brd.data[0] &= ~htonl(1<<(31-i));
+ }
+ addattr_l(&req.n, sizeof(req), IFA_BROADCAST, &brd.data, brd.bytelen);
+ brd_len = brd.bytelen;
+ }
+ }
+ if (!scoped && cmd != RTM_DELADDR)
+ req.ifa.ifa_scope = default_scope(&lcl);
+
+ req.ifa.ifa_index = ll_name_to_index(d);
+ if (!req.ifa.ifa_index)
+ return nodev(d);
+
+ if (valid_lftp || preferred_lftp) {
+ struct ifa_cacheinfo cinfo = {};
+
+ if (!valid_lft) {
+ fprintf(stderr, "valid_lft is zero\n");
+ return -1;
+ }
+ if (valid_lft < preferred_lft) {
+ fprintf(stderr, "preferred_lft is greater than valid_lft\n");
+ return -1;
+ }
+
+ cinfo.ifa_prefered = preferred_lft;
+ cinfo.ifa_valid = valid_lft;
+ addattr_l(&req.n, sizeof(req), IFA_CACHEINFO, &cinfo,
+ sizeof(cinfo));
+ }
+
+ if ((ifa_flags & IFA_F_MCAUTOJOIN) && !ipaddr_is_multicast(&lcl)) {
+ fprintf(stderr, "autojoin needs multicast address\n");
+ return -1;
+ }
+
+ if (echo_request)
+ ret = rtnl_echo_talk(&rth, &req.n, json, print_addrinfo);
+ else
+ ret = rtnl_talk(&rth, &req.n, NULL);
+
+ if (ret)
+ return -2;
+
+ return 0;
+}
+
+int do_ipaddr(int argc, char **argv)
+{
+ if (argc < 1)
+ return ipaddr_list_flush_or_save(0, NULL, IPADD_LIST);
+ if (matches(*argv, "add") == 0)
+ return ipaddr_modify(RTM_NEWADDR, NLM_F_CREATE|NLM_F_EXCL, argc-1, argv+1);
+ if (matches(*argv, "change") == 0 ||
+ strcmp(*argv, "chg") == 0)
+ return ipaddr_modify(RTM_NEWADDR, NLM_F_REPLACE, argc-1, argv+1);
+ if (matches(*argv, "replace") == 0)
+ return ipaddr_modify(RTM_NEWADDR, NLM_F_CREATE|NLM_F_REPLACE, argc-1, argv+1);
+ if (matches(*argv, "delete") == 0)
+ return ipaddr_modify(RTM_DELADDR, 0, argc-1, argv+1);
+ if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
+ || matches(*argv, "lst") == 0)
+ return ipaddr_list_flush_or_save(argc-1, argv+1, IPADD_LIST);
+ if (matches(*argv, "flush") == 0)
+ return ipaddr_list_flush_or_save(argc-1, argv+1, IPADD_FLUSH);
+ if (matches(*argv, "save") == 0)
+ return ipaddr_list_flush_or_save(argc-1, argv+1, IPADD_SAVE);
+ if (matches(*argv, "showdump") == 0)
+ return ipaddr_showdump();
+ if (matches(*argv, "restore") == 0)
+ return ipaddr_restore();
+ if (matches(*argv, "help") == 0)
+ usage();
+ fprintf(stderr, "Command \"%s\" is unknown, try \"ip address help\".\n", *argv);
+ exit(-1);
+}
diff --git a/ip/ipaddrlabel.c b/ip/ipaddrlabel.c
new file mode 100644
index 0000000..b045827
--- /dev/null
+++ b/ip/ipaddrlabel.c
@@ -0,0 +1,260 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * ipaddrlabel.c "ip addrlabel"
+ *
+ * Copyright (C)2007 USAGI/WIDE Project
+ *
+ * Based on iprule.c.
+ *
+ * Authors: YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <linux/types.h>
+#include <linux/if_addrlabel.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+#define IFAL_RTA(r) ((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrlblmsg))))
+#define IFAL_PAYLOAD(n) NLMSG_PAYLOAD(n, sizeof(struct ifaddrlblmsg))
+
+extern struct rtnl_handle rth;
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip addrlabel { add | del } prefix PREFIX [ dev DEV ] [ label LABEL ]\n"
+ " ip addrlabel [ list | flush | help ]\n");
+ exit(-1);
+}
+
+int print_addrlabel(struct nlmsghdr *n, void *arg)
+{
+ struct ifaddrlblmsg *ifal = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *tb[IFAL_MAX+1];
+
+ if (n->nlmsg_type != RTM_NEWADDRLABEL && n->nlmsg_type != RTM_DELADDRLABEL)
+ return 0;
+
+ len -= NLMSG_LENGTH(sizeof(*ifal));
+ if (len < 0)
+ return -1;
+
+ parse_rtattr(tb, IFAL_MAX, IFAL_RTA(ifal), len);
+
+ open_json_object(NULL);
+ if (n->nlmsg_type == RTM_DELADDRLABEL)
+ print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+ if (tb[IFAL_ADDRESS]) {
+ const char *host
+ = format_host_rta(ifal->ifal_family,
+ tb[IFAL_ADDRESS]);
+
+ print_string(PRINT_FP, NULL, "prefix ", NULL);
+ print_color_string(PRINT_ANY,
+ ifa_family_color(ifal->ifal_family),
+ "address", "%s", host);
+
+ print_uint(PRINT_ANY, "prefixlen", "/%u ",
+ ifal->ifal_prefixlen);
+ }
+
+ if (ifal->ifal_index) {
+ print_string(PRINT_FP, NULL, "dev ", NULL);
+ print_color_string(PRINT_ANY, COLOR_IFNAME,
+ "ifname", "%s ",
+ ll_index_to_name(ifal->ifal_index));
+ }
+
+ if (tb[IFAL_LABEL] && RTA_PAYLOAD(tb[IFAL_LABEL]) == sizeof(uint32_t)) {
+ uint32_t label = rta_getattr_u32(tb[IFAL_LABEL]);
+
+ print_uint(PRINT_ANY,
+ "label", "label %u ", label);
+ }
+ print_string(PRINT_FP, NULL, "\n", "");
+ close_json_object();
+
+ return 0;
+}
+
+static int ipaddrlabel_list(int argc, char **argv)
+{
+ int af = preferred_family;
+
+ if (af == AF_UNSPEC)
+ af = AF_INET6;
+
+ if (argc > 0) {
+ fprintf(stderr, "\"ip addrlabel show\" does not take any arguments.\n");
+ return -1;
+ }
+
+ if (rtnl_addrlbldump_req(&rth, af) < 0) {
+ perror("Cannot send dump request");
+ return 1;
+ }
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&rth, print_addrlabel, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ delete_json_obj();
+ return 1;
+ }
+ delete_json_obj();
+
+ return 0;
+}
+
+
+static int ipaddrlabel_modify(int cmd, int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct ifaddrlblmsg ifal;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_type = cmd,
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrlblmsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .ifal.ifal_family = preferred_family,
+ };
+
+ inet_prefix prefix = {};
+ uint32_t label = 0xffffffffUL;
+ char *p = NULL;
+ char *l = NULL;
+
+ if (cmd == RTM_NEWADDRLABEL) {
+ req.n.nlmsg_flags |= NLM_F_CREATE|NLM_F_EXCL;
+ }
+
+ while (argc > 0) {
+ if (strcmp(*argv, "prefix") == 0) {
+ NEXT_ARG();
+ p = *argv;
+ get_prefix(&prefix, *argv, preferred_family);
+ } else if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if ((req.ifal.ifal_index = ll_name_to_index(*argv)) == 0)
+ invarg("dev is invalid\n", *argv);
+ } else if (strcmp(*argv, "label") == 0) {
+ NEXT_ARG();
+ l = *argv;
+ if (get_u32(&label, *argv, 0) || label == 0xffffffffUL)
+ invarg("label is invalid\n", *argv);
+ }
+ argc--;
+ argv++;
+ }
+ if (p == NULL) {
+ fprintf(stderr, "Not enough information: \"prefix\" argument is required.\n");
+ return -1;
+ }
+ if (l == NULL) {
+ fprintf(stderr, "Not enough information: \"label\" argument is required.\n");
+ return -1;
+ }
+ addattr32(&req.n, sizeof(req), IFAL_LABEL, label);
+ addattr_l(&req.n, sizeof(req), IFAL_ADDRESS, &prefix.data, prefix.bytelen);
+ req.ifal.ifal_prefixlen = prefix.bitlen;
+
+ if (req.ifal.ifal_family == AF_UNSPEC)
+ req.ifal.ifal_family = AF_INET6;
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ return -2;
+
+ return 0;
+}
+
+
+static int flush_addrlabel(struct nlmsghdr *n, void *arg)
+{
+ struct rtnl_handle rth2;
+ struct rtmsg *r = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *tb[IFAL_MAX+1];
+
+ len -= NLMSG_LENGTH(sizeof(*r));
+ if (len < 0)
+ return -1;
+
+ parse_rtattr(tb, IFAL_MAX, RTM_RTA(r), len);
+
+ if (tb[IFAL_ADDRESS]) {
+ n->nlmsg_type = RTM_DELADDRLABEL;
+ n->nlmsg_flags = NLM_F_REQUEST;
+
+ if (rtnl_open(&rth2, 0) < 0)
+ return -1;
+
+ if (rtnl_talk(&rth2, n, NULL) < 0)
+ return -2;
+
+ rtnl_close(&rth2);
+ }
+
+ return 0;
+}
+
+static int ipaddrlabel_flush(int argc, char **argv)
+{
+ int af = preferred_family;
+
+ if (af == AF_UNSPEC)
+ af = AF_INET6;
+
+ if (argc > 0) {
+ fprintf(stderr, "\"ip addrlabel flush\" does not allow extra arguments\n");
+ return -1;
+ }
+
+ if (rtnl_addrlbldump_req(&rth, af) < 0) {
+ perror("Cannot send dump request");
+ return -1;
+ }
+
+ if (rtnl_dump_filter(&rth, flush_addrlabel, NULL) < 0) {
+ fprintf(stderr, "Flush terminated\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+int do_ipaddrlabel(int argc, char **argv)
+{
+ if (argc < 1) {
+ return ipaddrlabel_list(0, NULL);
+ } else if (matches(argv[0], "list") == 0 ||
+ matches(argv[0], "lst") == 0 ||
+ matches(argv[0], "show") == 0) {
+ return ipaddrlabel_list(argc-1, argv+1);
+ } else if (matches(argv[0], "add") == 0) {
+ return ipaddrlabel_modify(RTM_NEWADDRLABEL, argc-1, argv+1);
+ } else if (matches(argv[0], "delete") == 0) {
+ return ipaddrlabel_modify(RTM_DELADDRLABEL, argc-1, argv+1);
+ } else if (matches(argv[0], "flush") == 0) {
+ return ipaddrlabel_flush(argc-1, argv+1);
+ } else if (matches(argv[0], "help") == 0)
+ usage();
+
+ fprintf(stderr, "Command \"%s\" is unknown, try \"ip addrlabel help\".\n", *argv);
+ exit(-1);
+}
diff --git a/ip/ipfou.c b/ip/ipfou.c
new file mode 100644
index 0000000..760cfee
--- /dev/null
+++ b/ip/ipfou.c
@@ -0,0 +1,351 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * ipfou.c FOU (foo over UDP) support
+ *
+ * Authors: Tom Herbert <therbert@google.com>
+ */
+
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <net/if.h>
+#include <linux/fou.h>
+#include <linux/genetlink.h>
+#include <linux/ip.h>
+#include <arpa/inet.h>
+
+#include "libgenl.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip fou add port PORT { ipproto PROTO | gue }\n"
+ " [ local IFADDR ] [ peer IFADDR ]\n"
+ " [ peer_port PORT ] [ dev IFNAME ]\n"
+ " ip fou del port PORT [ local IFADDR ]\n"
+ " [ peer IFADDR ] [ peer_port PORT ]\n"
+ " [ dev IFNAME ]\n"
+ " ip fou show\n"
+ "\n"
+ "Where: PROTO { ipproto-name | 1..255 }\n"
+ " PORT { 1..65535 }\n"
+ " IFADDR { addr }\n");
+
+ exit(-1);
+}
+
+/* netlink socket */
+static struct rtnl_handle genl_rth = { .fd = -1 };
+static int genl_family = -1;
+
+#define FOU_REQUEST(_req, _bufsiz, _cmd, _flags) \
+ GENL_REQUEST(_req, _bufsiz, genl_family, 0, \
+ FOU_GENL_VERSION, _cmd, _flags)
+
+static int fou_parse_opt(int argc, char **argv, struct nlmsghdr *n,
+ bool adding)
+{
+ const char *local = NULL, *peer = NULL;
+ __u16 port, peer_port = 0;
+ __u8 family = preferred_family;
+ bool gue_set = false;
+ int ipproto_set = 0;
+ __u8 ipproto, type;
+ int port_set = 0;
+ int index = 0;
+
+ if (preferred_family == AF_UNSPEC) {
+ family = AF_INET;
+ }
+
+ while (argc > 0) {
+ if (!matches(*argv, "port")) {
+ NEXT_ARG();
+
+ if (get_be16(&port, *argv, 0) || port == 0)
+ invarg("invalid port", *argv);
+ port_set = 1;
+ } else if (!matches(*argv, "ipproto")) {
+ struct protoent *servptr;
+
+ NEXT_ARG();
+
+ servptr = getprotobyname(*argv);
+ if (servptr)
+ ipproto = servptr->p_proto;
+ else if (get_u8(&ipproto, *argv, 0) || ipproto == 0)
+ invarg("invalid ipproto", *argv);
+ ipproto_set = 1;
+ } else if (!matches(*argv, "gue")) {
+ gue_set = true;
+ } else if (!matches(*argv, "-6")) {
+ family = AF_INET6;
+ } else if (!matches(*argv, "local")) {
+ NEXT_ARG();
+
+ local = *argv;
+ } else if (!matches(*argv, "peer")) {
+ NEXT_ARG();
+
+ peer = *argv;
+ } else if (!matches(*argv, "peer_port")) {
+ NEXT_ARG();
+
+ if (get_be16(&peer_port, *argv, 0) || peer_port == 0)
+ invarg("invalid peer port", *argv);
+ } else if (!matches(*argv, "dev")) {
+ const char *ifname;
+
+ NEXT_ARG();
+
+ ifname = *argv;
+
+ if (check_ifname(ifname)) {
+ fprintf(stderr, "fou: invalid device name\n");
+ exit(EXIT_FAILURE);
+ }
+
+ index = ll_name_to_index(ifname);
+
+ if (!index) {
+ fprintf(stderr, "fou: unknown device name\n");
+ exit(EXIT_FAILURE);
+ }
+ } else {
+ fprintf(stderr
+ , "fou: unknown command \"%s\"?\n", *argv);
+ usage();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ if (!port_set) {
+ fprintf(stderr, "fou: missing port\n");
+ return -1;
+ }
+
+ if (!ipproto_set && !gue_set && adding) {
+ fprintf(stderr, "fou: must set ipproto or gue\n");
+ return -1;
+ }
+
+ if (ipproto_set && gue_set) {
+ fprintf(stderr, "fou: cannot set ipproto and gue\n");
+ return -1;
+ }
+
+ if ((peer_port && !peer) || (peer && !peer_port)) {
+ fprintf(stderr, "fou: both peer and peer port must be set\n");
+ return -1;
+ }
+
+ type = gue_set ? FOU_ENCAP_GUE : FOU_ENCAP_DIRECT;
+
+ addattr16(n, 1024, FOU_ATTR_PORT, port);
+ addattr8(n, 1024, FOU_ATTR_TYPE, type);
+ addattr8(n, 1024, FOU_ATTR_AF, family);
+
+ if (ipproto_set)
+ addattr8(n, 1024, FOU_ATTR_IPPROTO, ipproto);
+
+ if (local) {
+ inet_prefix local_addr;
+ __u8 attr_type = family == AF_INET ? FOU_ATTR_LOCAL_V4 :
+ FOU_ATTR_LOCAL_V6;
+
+ if (get_addr(&local_addr, local, family)) {
+ fprintf(stderr, "fou: parsing local address failed\n");
+ exit(EXIT_FAILURE);
+ }
+ addattr_l(n, 1024, attr_type, &local_addr.data,
+ local_addr.bytelen);
+ }
+
+ if (peer) {
+ inet_prefix peer_addr;
+ __u8 attr_type = family == AF_INET ? FOU_ATTR_PEER_V4 :
+ FOU_ATTR_PEER_V6;
+
+ if (get_addr(&peer_addr, peer, family)) {
+ fprintf(stderr, "fou: parsing peer address failed\n");
+ exit(EXIT_FAILURE);
+ }
+ addattr_l(n, 1024, attr_type, &peer_addr.data,
+ peer_addr.bytelen);
+
+ if (peer_port)
+ addattr16(n, 1024, FOU_ATTR_PEER_PORT, peer_port);
+ }
+
+ if (index)
+ addattr32(n, 1024, FOU_ATTR_IFINDEX, index);
+
+ return 0;
+}
+
+static int do_add(int argc, char **argv)
+{
+ FOU_REQUEST(req, 1024, FOU_CMD_ADD, NLM_F_REQUEST);
+
+ fou_parse_opt(argc, argv, &req.n, true);
+
+ if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
+ return -2;
+
+ return 0;
+}
+
+static int do_del(int argc, char **argv)
+{
+ FOU_REQUEST(req, 1024, FOU_CMD_DEL, NLM_F_REQUEST);
+
+ fou_parse_opt(argc, argv, &req.n, false);
+
+ if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
+ return -2;
+
+ return 0;
+}
+
+static int print_fou_mapping(struct nlmsghdr *n, void *arg)
+{
+ __u8 family = AF_INET, local_attr_type, peer_attr_type, byte_len;
+ struct rtattr *tb[FOU_ATTR_MAX + 1];
+ __u8 empty_buf[16] = {0};
+ struct genlmsghdr *ghdr;
+ 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(tb, FOU_ATTR_MAX, (void *) ghdr + GENL_HDRLEN, len);
+
+ open_json_object(NULL);
+ if (tb[FOU_ATTR_PORT])
+ print_uint(PRINT_ANY, "port", "port %u",
+ ntohs(rta_getattr_u16(tb[FOU_ATTR_PORT])));
+
+ if (tb[FOU_ATTR_TYPE] &&
+ rta_getattr_u8(tb[FOU_ATTR_TYPE]) == FOU_ENCAP_GUE)
+ print_null(PRINT_ANY, "gue", " gue", NULL);
+ else if (tb[FOU_ATTR_IPPROTO])
+ print_uint(PRINT_ANY, "ipproto",
+ " ipproto %u", rta_getattr_u8(tb[FOU_ATTR_IPPROTO]));
+
+ if (tb[FOU_ATTR_AF]) {
+ family = rta_getattr_u8(tb[FOU_ATTR_AF]);
+
+ print_string(PRINT_JSON, "family", NULL,
+ family_name(family));
+
+ if (family == AF_INET6)
+ print_string(PRINT_FP, NULL,
+ " -6", NULL);
+ }
+
+ local_attr_type = family == AF_INET ? FOU_ATTR_LOCAL_V4 :
+ FOU_ATTR_LOCAL_V6;
+ peer_attr_type = family == AF_INET ? FOU_ATTR_PEER_V4 :
+ FOU_ATTR_PEER_V6;
+ byte_len = af_bit_len(family) / 8;
+
+ if (tb[local_attr_type] && memcmp(RTA_DATA(tb[local_attr_type]),
+ empty_buf, byte_len)) {
+ print_string(PRINT_ANY, "local", " local %s",
+ format_host_rta(family, tb[local_attr_type]));
+ }
+
+ if (tb[peer_attr_type] && memcmp(RTA_DATA(tb[peer_attr_type]),
+ empty_buf, byte_len)) {
+ print_string(PRINT_ANY, "peer", " peer %s",
+ format_host_rta(family, tb[peer_attr_type]));
+ }
+
+ if (tb[FOU_ATTR_PEER_PORT]) {
+ __u16 p_port = ntohs(rta_getattr_u16(tb[FOU_ATTR_PEER_PORT]));
+
+ if (p_port)
+ print_uint(PRINT_ANY, "peer_port", " peer_port %u",
+ p_port);
+
+ }
+
+ if (tb[FOU_ATTR_IFINDEX]) {
+ int index = rta_getattr_s32(tb[FOU_ATTR_IFINDEX]);
+
+ if (index) {
+ const char *ifname;
+
+ ifname = ll_index_to_name(index);
+
+ if (ifname)
+ print_string(PRINT_ANY, "dev", " dev %s",
+ ifname);
+ }
+ }
+
+ print_string(PRINT_FP, NULL, "\n", NULL);
+ close_json_object();
+
+ return 0;
+}
+
+static int do_show(int argc, char **argv)
+{
+ FOU_REQUEST(req, 4096, FOU_CMD_GET, NLM_F_REQUEST | NLM_F_DUMP);
+
+ if (argc > 0) {
+ fprintf(stderr,
+ "\"ip fou show\" does not take any arguments.\n");
+ return -1;
+ }
+
+ 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_fou_mapping, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ delete_json_obj();
+ return 1;
+ }
+ delete_json_obj();
+ fflush(stdout);
+
+ return 0;
+}
+
+int do_ipfou(int argc, char **argv)
+{
+ if (argc < 1)
+ usage();
+
+ if (matches(*argv, "help") == 0)
+ usage();
+
+ if (genl_init_handle(&genl_rth, FOU_GENL_NAME, &genl_family))
+ exit(1);
+
+ if (matches(*argv, "add") == 0)
+ return do_add(argc-1, argv+1);
+ if (matches(*argv, "delete") == 0)
+ return do_del(argc-1, argv+1);
+ if (matches(*argv, "show") == 0)
+ return do_show(argc-1, argv+1);
+
+ fprintf(stderr,
+ "Command \"%s\" is unknown, try \"ip fou help\".\n", *argv);
+ exit(-1);
+}
diff --git a/ip/ipila.c b/ip/ipila.c
new file mode 100644
index 0000000..f4387e0
--- /dev/null
+++ b/ip/ipila.c
@@ -0,0 +1,310 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * ipila.c ILA (Identifier Locator Addressing) support
+ *
+ * Authors: Tom Herbert <tom@herbertland.com>
+ */
+
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <net/if.h>
+#include <linux/ila.h>
+#include <linux/genetlink.h>
+#include <linux/ip.h>
+#include <arpa/inet.h>
+
+#include "libgenl.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "ila_common.h"
+#include "json_print.h"
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip ila add loc_match LOCATOR_MATCH loc LOCATOR [ dev DEV ] OPTIONS\n"
+ " ip ila del loc_match LOCATOR_MATCH [ loc LOCATOR ] [ dev DEV ]\n"
+ " ip ila list\n"
+ "OPTIONS := [ csum-mode { adj-transport | neutral-map |\n"
+ " neutral-map-auto | no-action } ]\n"
+ " [ ident-type { luid | use-format } ]\n");
+
+ exit(-1);
+}
+
+/* netlink socket */
+static struct rtnl_handle genl_rth = { .fd = -1 };
+static int genl_family = -1;
+
+#define ILA_REQUEST(_req, _bufsiz, _cmd, _flags) \
+ GENL_REQUEST(_req, _bufsiz, genl_family, 0, \
+ ILA_GENL_VERSION, _cmd, _flags)
+
+#define ILA_RTA(g) ((struct rtattr *)(((char *)(g)) + \
+ NLMSG_ALIGN(sizeof(struct genlmsghdr))))
+
+static void print_addr64(__u64 addr, char *buff, size_t len)
+{
+ union {
+ __u64 id64;
+ __u16 words[4];
+ } id = { .id64 = addr };
+ __u16 v;
+ int i, ret;
+ size_t written = 0;
+ char *sep = ":";
+
+ for (i = 0; i < 4; i++) {
+ v = ntohs(id.words[i]);
+
+ if (i == 3)
+ sep = "";
+
+ ret = snprintf(&buff[written], len - written, "%x%s", v, sep);
+ if (ret < 0 || ret >= len - written)
+ break;
+ written += ret;
+ }
+}
+
+static void print_ila_locid(const char *tag, int attr, struct rtattr *tb[])
+{
+ char abuf[256];
+
+ if (tb[attr])
+ print_addr64(rta_getattr_u64(tb[attr]),
+ abuf, sizeof(abuf));
+ else
+ snprintf(abuf, sizeof(abuf), "-");
+
+ /* 20 = sizeof("xxxx:xxxx:xxxx:xxxx") */
+ print_string(PRINT_ANY, tag, "%-20s", abuf);
+}
+
+static int print_ila_mapping(struct nlmsghdr *n, void *arg)
+{
+ struct genlmsghdr *ghdr;
+ struct rtattr *tb[ILA_ATTR_MAX + 1];
+ 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(tb, ILA_ATTR_MAX, (void *) ghdr + GENL_HDRLEN, len);
+
+ open_json_object(NULL);
+ print_ila_locid("locator_match", ILA_ATTR_LOCATOR_MATCH, tb);
+ print_ila_locid("locator", ILA_ATTR_LOCATOR, tb);
+
+ if (tb[ILA_ATTR_IFINDEX]) {
+ __u32 ifindex
+ = rta_getattr_u32(tb[ILA_ATTR_IFINDEX]);
+
+ print_color_string(PRINT_ANY, COLOR_IFNAME,
+ "interface", "%-16s",
+ ll_index_to_name(ifindex));
+ } else {
+ print_string(PRINT_FP, NULL, "%-10s ", "-");
+ }
+
+ if (tb[ILA_ATTR_CSUM_MODE]) {
+ __u8 csum = rta_getattr_u8(tb[ILA_ATTR_CSUM_MODE]);
+
+ print_string(PRINT_ANY, "csum_mode", "%s",
+ ila_csum_mode2name(csum));
+ } else
+ print_string(PRINT_FP, NULL, "%-10s ", "-");
+
+ if (tb[ILA_ATTR_IDENT_TYPE])
+ print_string(PRINT_ANY, "ident_type", "%s",
+ ila_ident_type2name(rta_getattr_u8(
+ tb[ILA_ATTR_IDENT_TYPE])));
+ else
+ print_string(PRINT_FP, NULL, "%s", "-");
+
+ print_nl();
+ close_json_object();
+
+ return 0;
+}
+
+#define NLMSG_BUF_SIZE 4096
+
+static int do_list(int argc, char **argv)
+{
+ ILA_REQUEST(req, 1024, ILA_CMD_GET, NLM_F_REQUEST | NLM_F_DUMP);
+
+ if (argc > 0) {
+ fprintf(stderr, "\"ip ila show\" does not take "
+ "any arguments.\n");
+ return -1;
+ }
+
+ if (rtnl_send(&genl_rth, (void *)&req, req.n.nlmsg_len) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&genl_rth, print_ila_mapping, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ delete_json_obj();
+ return 1;
+ }
+ delete_json_obj();
+ fflush(stdout);
+
+ return 0;
+}
+
+static int ila_parse_opt(int argc, char **argv, struct nlmsghdr *n,
+ bool adding)
+{
+ __u64 locator = 0;
+ __u64 locator_match = 0;
+ int ifindex = 0;
+ int csum_mode = 0;
+ int ident_type = 0;
+ bool loc_set = false;
+ bool loc_match_set = false;
+ bool ifindex_set = false;
+ bool csum_mode_set = false;
+ bool ident_type_set = false;
+
+ while (argc > 0) {
+ if (!matches(*argv, "loc")) {
+ NEXT_ARG();
+
+ if (get_addr64(&locator, *argv) < 0) {
+ fprintf(stderr, "Bad locator: %s\n", *argv);
+ return -1;
+ }
+ loc_set = true;
+ } else if (!matches(*argv, "loc_match")) {
+ NEXT_ARG();
+
+ if (get_addr64(&locator_match, *argv) < 0) {
+ fprintf(stderr, "Bad locator to match: %s\n",
+ *argv);
+ return -1;
+ }
+ loc_match_set = true;
+ } else if (!matches(*argv, "csum-mode")) {
+ NEXT_ARG();
+
+ csum_mode = ila_csum_name2mode(*argv);
+ if (csum_mode < 0) {
+ fprintf(stderr, "Bad csum-mode: %s\n",
+ *argv);
+ return -1;
+ }
+ csum_mode_set = true;
+ } else if (!matches(*argv, "ident-type")) {
+ NEXT_ARG();
+
+ ident_type = ila_ident_name2type(*argv);
+ if (ident_type < 0) {
+ fprintf(stderr, "Bad ident-type: %s\n",
+ *argv);
+ return -1;
+ }
+ ident_type_set = true;
+ } else if (!matches(*argv, "dev")) {
+ NEXT_ARG();
+
+ ifindex = ll_name_to_index(*argv);
+ if (ifindex == 0) {
+ fprintf(stderr, "No such interface: %s\n",
+ *argv);
+ return -1;
+ }
+ ifindex_set = true;
+ } else {
+ usage();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ if (adding) {
+ if (!loc_set) {
+ fprintf(stderr, "ila: missing locator\n");
+ return -1;
+ }
+ if (!loc_match_set) {
+ fprintf(stderr, "ila: missing locator0match\n");
+ return -1;
+ }
+ }
+
+ if (loc_match_set)
+ addattr64(n, 1024, ILA_ATTR_LOCATOR_MATCH, locator_match);
+
+ if (loc_set)
+ addattr64(n, 1024, ILA_ATTR_LOCATOR, locator);
+
+ if (ifindex_set)
+ addattr32(n, 1024, ILA_ATTR_IFINDEX, ifindex);
+
+ if (csum_mode_set)
+ addattr8(n, 1024, ILA_ATTR_CSUM_MODE, csum_mode);
+
+ if (ident_type_set)
+ addattr8(n, 1024, ILA_ATTR_IDENT_TYPE, ident_type);
+
+ return 0;
+}
+
+static int do_add(int argc, char **argv)
+{
+ ILA_REQUEST(req, 1024, ILA_CMD_ADD, NLM_F_REQUEST);
+
+ ila_parse_opt(argc, argv, &req.n, true);
+
+ if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
+ return -2;
+
+ return 0;
+}
+
+static int do_del(int argc, char **argv)
+{
+ ILA_REQUEST(req, 1024, ILA_CMD_DEL, NLM_F_REQUEST);
+
+ ila_parse_opt(argc, argv, &req.n, false);
+
+ if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
+ return -2;
+
+ return 0;
+}
+
+int do_ipila(int argc, char **argv)
+{
+ if (argc < 1)
+ usage();
+
+ if (matches(*argv, "help") == 0)
+ usage();
+
+ if (genl_init_handle(&genl_rth, ILA_GENL_NAME, &genl_family))
+ exit(1);
+
+ if (matches(*argv, "add") == 0)
+ return do_add(argc-1, argv+1);
+ if (matches(*argv, "delete") == 0)
+ return do_del(argc-1, argv+1);
+ if (matches(*argv, "list") == 0)
+ return do_list(argc-1, argv+1);
+
+ fprintf(stderr, "Command \"%s\" is unknown, try \"ip ila help\".\n",
+ *argv);
+ exit(-1);
+}
diff --git a/ip/ipioam6.c b/ip/ipioam6.c
new file mode 100644
index 0000000..b63d7d5
--- /dev/null
+++ b/ip/ipioam6.c
@@ -0,0 +1,333 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ioam6.c "ip ioam"
+ *
+ * Author: Justin Iurman <justin.iurman@uliege.be>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <inttypes.h>
+
+#include <linux/genetlink.h>
+#include <linux/ioam6_genl.h>
+
+#include "utils.h"
+#include "ip_common.h"
+#include "libgenl.h"
+#include "json_print.h"
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip ioam { COMMAND | help }\n"
+ " ip ioam namespace show\n"
+ " ip ioam namespace add ID [ data DATA32 ] [ wide DATA64 ]\n"
+ " ip ioam namespace del ID\n"
+ " ip ioam schema show\n"
+ " ip ioam schema add ID DATA\n"
+ " ip ioam schema del ID\n"
+ " ip ioam namespace set ID schema { ID | none }\n");
+ exit(-1);
+}
+
+static struct rtnl_handle grth = { .fd = -1 };
+static int genl_family = -1;
+
+#define IOAM6_REQUEST(_req, _bufsiz, _cmd, _flags) \
+ GENL_REQUEST(_req, _bufsiz, genl_family, 0, \
+ IOAM6_GENL_VERSION, _cmd, _flags)
+
+static struct {
+ unsigned int cmd;
+ __u32 sc_id;
+ __u32 ns_data;
+ __u64 ns_data_wide;
+ __u16 ns_id;
+ bool has_ns_data;
+ bool has_ns_data_wide;
+ bool sc_none;
+ __u8 sc_data[IOAM6_MAX_SCHEMA_DATA_LEN];
+} opts;
+
+static void print_namespace(struct rtattr *attrs[])
+{
+ print_uint(PRINT_ANY, "namespace", "namespace %u",
+ rta_getattr_u16(attrs[IOAM6_ATTR_NS_ID]));
+
+ if (attrs[IOAM6_ATTR_SC_ID])
+ print_uint(PRINT_ANY, "schema", " [schema %u]",
+ rta_getattr_u32(attrs[IOAM6_ATTR_SC_ID]));
+
+ if (attrs[IOAM6_ATTR_NS_DATA])
+ print_hex(PRINT_ANY, "data", ", data %#010x",
+ rta_getattr_u32(attrs[IOAM6_ATTR_NS_DATA]));
+
+ if (attrs[IOAM6_ATTR_NS_DATA_WIDE])
+ print_0xhex(PRINT_ANY, "wide", ", wide %#018lx",
+ rta_getattr_u64(attrs[IOAM6_ATTR_NS_DATA_WIDE]));
+
+ print_nl();
+}
+
+static void print_schema(struct rtattr *attrs[])
+{
+ __u8 data[IOAM6_MAX_SCHEMA_DATA_LEN];
+ int len, i = 0;
+
+ print_uint(PRINT_ANY, "schema", "schema %u",
+ rta_getattr_u32(attrs[IOAM6_ATTR_SC_ID]));
+
+ if (attrs[IOAM6_ATTR_NS_ID])
+ print_uint(PRINT_ANY, "namespace", " [namespace %u]",
+ rta_getattr_u16(attrs[IOAM6_ATTR_NS_ID]));
+
+ len = RTA_PAYLOAD(attrs[IOAM6_ATTR_SC_DATA]);
+ memcpy(data, RTA_DATA(attrs[IOAM6_ATTR_SC_DATA]), len);
+
+ print_null(PRINT_ANY, "data", ", data:", NULL);
+ while (i < len) {
+ print_hhu(PRINT_ANY, "", " %02x", data[i]);
+ i++;
+ }
+ print_nl();
+}
+
+static int process_msg(struct nlmsghdr *n, void *arg)
+{
+ struct rtattr *attrs[IOAM6_ATTR_MAX + 1];
+ struct genlmsghdr *ghdr;
+ int len = n->nlmsg_len;
+
+ if (n->nlmsg_type != genl_family)
+ return -1;
+
+ len -= NLMSG_LENGTH(GENL_HDRLEN);
+ if (len < 0)
+ return -1;
+
+ ghdr = NLMSG_DATA(n);
+ parse_rtattr(attrs, IOAM6_ATTR_MAX, (void *)ghdr + GENL_HDRLEN, len);
+
+ open_json_object(NULL);
+ switch (ghdr->cmd) {
+ case IOAM6_CMD_DUMP_NAMESPACES:
+ print_namespace(attrs);
+ break;
+ case IOAM6_CMD_DUMP_SCHEMAS:
+ print_schema(attrs);
+ break;
+ }
+ close_json_object();
+
+ return 0;
+}
+
+static int ioam6_do_cmd(void)
+{
+ IOAM6_REQUEST(req, 1056, opts.cmd, NLM_F_REQUEST);
+ int dump = 0;
+
+ if (genl_init_handle(&grth, IOAM6_GENL_NAME, &genl_family))
+ exit(1);
+
+ req.n.nlmsg_type = genl_family;
+
+ switch (opts.cmd) {
+ case IOAM6_CMD_ADD_NAMESPACE:
+ addattr16(&req.n, sizeof(req), IOAM6_ATTR_NS_ID, opts.ns_id);
+ if (opts.has_ns_data)
+ addattr32(&req.n, sizeof(req), IOAM6_ATTR_NS_DATA,
+ opts.ns_data);
+ if (opts.has_ns_data_wide)
+ addattr64(&req.n, sizeof(req), IOAM6_ATTR_NS_DATA_WIDE,
+ opts.ns_data_wide);
+ break;
+ case IOAM6_CMD_DEL_NAMESPACE:
+ addattr16(&req.n, sizeof(req), IOAM6_ATTR_NS_ID, opts.ns_id);
+ break;
+ case IOAM6_CMD_DUMP_NAMESPACES:
+ case IOAM6_CMD_DUMP_SCHEMAS:
+ dump = 1;
+ break;
+ case IOAM6_CMD_ADD_SCHEMA:
+ addattr32(&req.n, sizeof(req), IOAM6_ATTR_SC_ID, opts.sc_id);
+ addattr_l(&req.n, sizeof(req), IOAM6_ATTR_SC_DATA, opts.sc_data,
+ strlen((const char *)opts.sc_data));
+ break;
+ case IOAM6_CMD_DEL_SCHEMA:
+ addattr32(&req.n, sizeof(req), IOAM6_ATTR_SC_ID, opts.sc_id);
+ break;
+ case IOAM6_CMD_NS_SET_SCHEMA:
+ addattr16(&req.n, sizeof(req), IOAM6_ATTR_NS_ID, opts.ns_id);
+ if (opts.sc_none)
+ addattr(&req.n, sizeof(req), IOAM6_ATTR_SC_NONE);
+ else
+ addattr32(&req.n, sizeof(req), IOAM6_ATTR_SC_ID,
+ opts.sc_id);
+ break;
+ }
+
+ if (!dump) {
+ if (rtnl_talk(&grth, &req.n, NULL) < 0)
+ return -1;
+ } else {
+ req.n.nlmsg_flags |= NLM_F_DUMP;
+ req.n.nlmsg_seq = grth.dump = ++grth.seq;
+ if (rtnl_send(&grth, &req, req.n.nlmsg_len) < 0) {
+ perror("Failed to send dump request");
+ exit(1);
+ }
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&grth, process_msg, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ delete_json_obj();
+ fflush(stdout);
+ }
+
+ return 0;
+}
+
+int do_ioam6(int argc, char **argv)
+{
+ bool maybe_wide = false;
+
+ if (argc < 1 || strcmp(*argv, "help") == 0)
+ usage();
+
+ memset(&opts, 0, sizeof(opts));
+
+ if (strcmp(*argv, "namespace") == 0) {
+ NEXT_ARG();
+
+ if (strcmp(*argv, "show") == 0) {
+ opts.cmd = IOAM6_CMD_DUMP_NAMESPACES;
+
+ } else if (strcmp(*argv, "add") == 0) {
+ NEXT_ARG();
+
+ if (get_u16(&opts.ns_id, *argv, 0))
+ invarg("Invalid namespace ID", *argv);
+
+ if (NEXT_ARG_OK()) {
+ NEXT_ARG_FWD();
+
+ if (strcmp(*argv, "data") == 0) {
+ NEXT_ARG();
+
+ if (get_u32(&opts.ns_data, *argv, 0))
+ invarg("Invalid data", *argv);
+
+ maybe_wide = true;
+ opts.has_ns_data = true;
+
+ } else if (strcmp(*argv, "wide") == 0) {
+ NEXT_ARG();
+
+ if (get_u64(&opts.ns_data_wide, *argv, 16))
+ invarg("Invalid wide data", *argv);
+
+ opts.has_ns_data_wide = true;
+
+ } else {
+ invarg("Invalid argument", *argv);
+ }
+ }
+
+ if (NEXT_ARG_OK()) {
+ NEXT_ARG_FWD();
+
+ if (!maybe_wide || strcmp(*argv, "wide") != 0)
+ invarg("Unexpected argument", *argv);
+
+ NEXT_ARG();
+
+ if (get_u64(&opts.ns_data_wide, *argv, 16))
+ invarg("Invalid wide data", *argv);
+
+ opts.has_ns_data_wide = true;
+ }
+
+ opts.cmd = IOAM6_CMD_ADD_NAMESPACE;
+
+ } else if (strcmp(*argv, "del") == 0) {
+ NEXT_ARG();
+
+ if (get_u16(&opts.ns_id, *argv, 0))
+ invarg("Invalid namespace ID", *argv);
+
+ opts.cmd = IOAM6_CMD_DEL_NAMESPACE;
+
+ } else if (strcmp(*argv, "set") == 0) {
+ NEXT_ARG();
+
+ if (get_u16(&opts.ns_id, *argv, 0))
+ invarg("Invalid namespace ID", *argv);
+
+ NEXT_ARG();
+
+ if (strcmp(*argv, "schema") != 0)
+ invarg("Unknown", *argv);
+
+ NEXT_ARG();
+
+ if (strcmp(*argv, "none") == 0) {
+ opts.sc_none = true;
+
+ } else {
+ if (get_u32(&opts.sc_id, *argv, 0))
+ invarg("Invalid schema ID", *argv);
+
+ opts.sc_none = false;
+ }
+
+ opts.cmd = IOAM6_CMD_NS_SET_SCHEMA;
+
+ } else {
+ invarg("Unknown", *argv);
+ }
+
+ } else if (strcmp(*argv, "schema") == 0) {
+ NEXT_ARG();
+
+ if (strcmp(*argv, "show") == 0) {
+ opts.cmd = IOAM6_CMD_DUMP_SCHEMAS;
+
+ } else if (strcmp(*argv, "add") == 0) {
+ NEXT_ARG();
+
+ if (get_u32(&opts.sc_id, *argv, 0))
+ invarg("Invalid schema ID", *argv);
+
+ NEXT_ARG();
+
+ if (strlen(*argv) > IOAM6_MAX_SCHEMA_DATA_LEN)
+ invarg("Schema DATA too big", *argv);
+
+ memcpy(opts.sc_data, *argv, strlen(*argv));
+ opts.cmd = IOAM6_CMD_ADD_SCHEMA;
+
+ } else if (strcmp(*argv, "del") == 0) {
+ NEXT_ARG();
+
+ if (get_u32(&opts.sc_id, *argv, 0))
+ invarg("Invalid schema ID", *argv);
+
+ opts.cmd = IOAM6_CMD_DEL_SCHEMA;
+
+ } else {
+ invarg("Unknown", *argv);
+ }
+
+ } else {
+ invarg("Unknown", *argv);
+ }
+
+ return ioam6_do_cmd();
+}
diff --git a/ip/ipl2tp.c b/ip/ipl2tp.c
new file mode 100644
index 0000000..87a4b89
--- /dev/null
+++ b/ip/ipl2tp.c
@@ -0,0 +1,846 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * ipl2tp.c "ip l2tp"
+ *
+ * Original Author: James Chapman <jchapman@katalix.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <linux/if.h>
+#include <linux/if_arp.h>
+#include <linux/ip.h>
+
+#include <linux/genetlink.h>
+#include <linux/l2tp.h>
+#include "libgenl.h"
+
+#include "utils.h"
+#include "ip_common.h"
+
+enum {
+ L2TP_ADD,
+ L2TP_CHG,
+ L2TP_DEL,
+ L2TP_GET
+};
+
+struct l2tp_parm {
+ uint32_t tunnel_id;
+ uint32_t peer_tunnel_id;
+ uint32_t session_id;
+ uint32_t peer_session_id;
+ enum l2tp_encap_type encap;
+ uint16_t local_udp_port;
+ uint16_t peer_udp_port;
+ int cookie_len;
+ uint8_t cookie[8];
+ int peer_cookie_len;
+ uint8_t peer_cookie[8];
+ inet_prefix local_ip;
+ inet_prefix peer_ip;
+
+ uint16_t pw_type;
+ unsigned int udp6_csum_tx:1;
+ unsigned int udp6_csum_rx:1;
+ unsigned int udp_csum:1;
+ unsigned int recv_seq:1;
+ unsigned int send_seq:1;
+ unsigned int tunnel:1;
+ unsigned int session:1;
+ int reorder_timeout;
+ const char *ifname;
+ uint8_t l2spec_type;
+ uint8_t l2spec_len;
+};
+
+struct l2tp_stats {
+ uint64_t data_rx_packets;
+ uint64_t data_rx_bytes;
+ uint64_t data_rx_errors;
+ uint64_t data_rx_oos_packets;
+ uint64_t data_rx_oos_discards;
+ uint64_t data_tx_packets;
+ uint64_t data_tx_bytes;
+ uint64_t data_tx_errors;
+};
+
+struct l2tp_data {
+ struct l2tp_parm config;
+ struct l2tp_stats stats;
+};
+
+/* netlink socket */
+static struct rtnl_handle genl_rth;
+static int genl_family = -1;
+
+/*****************************************************************************
+ * Netlink actions
+ *****************************************************************************/
+
+static int create_tunnel(struct l2tp_parm *p)
+{
+ uint32_t local_attr = L2TP_ATTR_IP_SADDR;
+ uint32_t peer_attr = L2TP_ATTR_IP_DADDR;
+
+ GENL_REQUEST(req, 1024, genl_family, 0, L2TP_GENL_VERSION,
+ L2TP_CMD_TUNNEL_CREATE, NLM_F_REQUEST | NLM_F_ACK);
+
+ addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->tunnel_id);
+ addattr32(&req.n, 1024, L2TP_ATTR_PEER_CONN_ID, p->peer_tunnel_id);
+ addattr8(&req.n, 1024, L2TP_ATTR_PROTO_VERSION, 3);
+ addattr16(&req.n, 1024, L2TP_ATTR_ENCAP_TYPE, p->encap);
+
+ if (p->local_ip.family == AF_INET6)
+ local_attr = L2TP_ATTR_IP6_SADDR;
+ addattr_l(&req.n, 1024, local_attr, &p->local_ip.data,
+ p->local_ip.bytelen);
+
+ if (p->peer_ip.family == AF_INET6)
+ peer_attr = L2TP_ATTR_IP6_DADDR;
+ addattr_l(&req.n, 1024, peer_attr, &p->peer_ip.data,
+ p->peer_ip.bytelen);
+
+ if (p->encap == L2TP_ENCAPTYPE_UDP) {
+ addattr16(&req.n, 1024, L2TP_ATTR_UDP_SPORT, p->local_udp_port);
+ addattr16(&req.n, 1024, L2TP_ATTR_UDP_DPORT, p->peer_udp_port);
+ if (p->udp_csum)
+ addattr8(&req.n, 1024, L2TP_ATTR_UDP_CSUM, 1);
+ if (!p->udp6_csum_tx)
+ addattr(&req.n, 1024, L2TP_ATTR_UDP_ZERO_CSUM6_TX);
+ if (!p->udp6_csum_rx)
+ addattr(&req.n, 1024, L2TP_ATTR_UDP_ZERO_CSUM6_RX);
+ }
+
+ if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
+ return -2;
+
+ return 0;
+}
+
+static int delete_tunnel(struct l2tp_parm *p)
+{
+ GENL_REQUEST(req, 128, genl_family, 0, L2TP_GENL_VERSION,
+ L2TP_CMD_TUNNEL_DELETE, NLM_F_REQUEST | NLM_F_ACK);
+
+ addattr32(&req.n, 128, L2TP_ATTR_CONN_ID, p->tunnel_id);
+
+ if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
+ return -2;
+
+ return 0;
+}
+
+static int create_session(struct l2tp_parm *p)
+{
+ GENL_REQUEST(req, 1024, genl_family, 0, L2TP_GENL_VERSION,
+ L2TP_CMD_SESSION_CREATE, NLM_F_REQUEST | NLM_F_ACK);
+
+ addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->tunnel_id);
+ addattr32(&req.n, 1024, L2TP_ATTR_PEER_CONN_ID, p->peer_tunnel_id);
+ addattr32(&req.n, 1024, L2TP_ATTR_SESSION_ID, p->session_id);
+ addattr32(&req.n, 1024, L2TP_ATTR_PEER_SESSION_ID, p->peer_session_id);
+ addattr16(&req.n, 1024, L2TP_ATTR_PW_TYPE, p->pw_type);
+ addattr8(&req.n, 1024, L2TP_ATTR_L2SPEC_TYPE, p->l2spec_type);
+ addattr8(&req.n, 1024, L2TP_ATTR_L2SPEC_LEN, p->l2spec_len);
+
+ if (p->recv_seq)
+ addattr8(&req.n, 1024, L2TP_ATTR_RECV_SEQ, 1);
+ if (p->send_seq)
+ addattr8(&req.n, 1024, L2TP_ATTR_SEND_SEQ, 1);
+ if (p->reorder_timeout)
+ addattr64(&req.n, 1024, L2TP_ATTR_RECV_TIMEOUT,
+ p->reorder_timeout);
+ if (p->cookie_len)
+ addattr_l(&req.n, 1024, L2TP_ATTR_COOKIE,
+ p->cookie, p->cookie_len);
+ if (p->peer_cookie_len)
+ addattr_l(&req.n, 1024, L2TP_ATTR_PEER_COOKIE,
+ p->peer_cookie, p->peer_cookie_len);
+ if (p->ifname)
+ addattrstrz(&req.n, 1024, L2TP_ATTR_IFNAME, p->ifname);
+
+ if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
+ return -2;
+
+ return 0;
+}
+
+static int delete_session(struct l2tp_parm *p)
+{
+ GENL_REQUEST(req, 1024, genl_family, 0, L2TP_GENL_VERSION,
+ L2TP_CMD_SESSION_DELETE, NLM_F_REQUEST | NLM_F_ACK);
+
+ addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->tunnel_id);
+ addattr32(&req.n, 1024, L2TP_ATTR_SESSION_ID, p->session_id);
+ if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
+ return -2;
+
+ return 0;
+}
+
+static void __attribute__((format(printf, 2, 0)))
+print_cookie(const char *name, const char *fmt,
+ const uint8_t *cookie, int len)
+{
+ char abuf[32];
+ size_t n;
+
+ n = snprintf(abuf, sizeof(abuf),
+ "%02x%02x%02x%02x",
+ cookie[0], cookie[1], cookie[2], cookie[3]);
+ if (len == 8)
+ snprintf(abuf + n, sizeof(abuf) - n,
+ "%02x%02x%02x%02x",
+ cookie[4], cookie[5],
+ cookie[6], cookie[7]);
+
+ print_string(PRINT_ANY, name, fmt, abuf);
+}
+
+static void print_tunnel(const struct l2tp_data *data)
+{
+ const struct l2tp_parm *p = &data->config;
+ char buf[INET6_ADDRSTRLEN];
+
+ open_json_object(NULL);
+ print_uint(PRINT_ANY, "tunnel_id", "Tunnel %u,", p->tunnel_id);
+ print_string(PRINT_ANY, "encap", " encap %s",
+ p->encap == L2TP_ENCAPTYPE_UDP ? "UDP" :
+ p->encap == L2TP_ENCAPTYPE_IP ? "IP" : "??");
+ print_nl();
+
+ print_string(PRINT_ANY, "local", " From %s ",
+ inet_ntop(p->local_ip.family, p->local_ip.data,
+ buf, sizeof(buf)));
+ print_string(PRINT_ANY, "peer", "to %s",
+ inet_ntop(p->peer_ip.family, p->peer_ip.data,
+ buf, sizeof(buf)));
+ print_nl();
+
+ print_uint(PRINT_ANY, "peer_tunnel", " Peer tunnel %u",
+ p->peer_tunnel_id);
+ print_nl();
+
+ if (p->encap == L2TP_ENCAPTYPE_UDP) {
+ print_string(PRINT_FP, NULL,
+ " UDP source / dest ports:", NULL);
+
+ print_hu(PRINT_ANY, "local_port", " %hu",
+ p->local_udp_port);
+ print_hu(PRINT_ANY, "peer_port", "/%hu",
+ p->peer_udp_port);
+ print_nl();
+
+ switch (p->local_ip.family) {
+ case AF_INET:
+ print_bool(PRINT_JSON, "checksum",
+ NULL, p->udp_csum);
+ print_string(PRINT_FP, NULL,
+ " UDP checksum: %s\n",
+ p->udp_csum ? "enabled" : "disabled");
+ break;
+ case AF_INET6:
+ if (is_json_context()) {
+ print_bool(PRINT_JSON, "checksum_tx",
+ NULL, p->udp6_csum_tx);
+
+ print_bool(PRINT_JSON, "checksum_rx",
+ NULL, p->udp6_csum_rx);
+ } else {
+ printf(" UDP checksum: %s%s%s%s\n",
+ p->udp6_csum_tx && p->udp6_csum_rx
+ ? "enabled" : "",
+ p->udp6_csum_tx && !p->udp6_csum_rx
+ ? "tx" : "",
+ !p->udp6_csum_tx && p->udp6_csum_rx
+ ? "rx" : "",
+ !p->udp6_csum_tx && !p->udp6_csum_rx
+ ? "disabled" : "");
+ }
+ break;
+ }
+ }
+ close_json_object();
+}
+
+static void print_session(struct l2tp_data *data)
+{
+ struct l2tp_parm *p = &data->config;
+
+ open_json_object(NULL);
+
+ print_uint(PRINT_ANY, "session_id", "Session %u", p->session_id);
+ print_uint(PRINT_ANY, "tunnel_id", " in tunnel %u", p->tunnel_id);
+ print_nl();
+
+ print_uint(PRINT_ANY, "peer_session_id",
+ " Peer session %u,", p->peer_session_id);
+ print_uint(PRINT_ANY, "peer_tunnel_id",
+ " tunnel %u", p->peer_tunnel_id);
+ print_nl();
+
+ if (p->ifname != NULL) {
+ print_color_string(PRINT_ANY, COLOR_IFNAME,
+ "interface", " interface name: %s" , p->ifname);
+ print_nl();
+ }
+
+ /* Show offsets only for plain console output (for legacy scripts) */
+ print_uint(PRINT_FP, "offset", " offset %u,", 0);
+ print_uint(PRINT_FP, "peer_offset", " peer offset %u\n", 0);
+
+ if (p->cookie_len > 0)
+ print_cookie("cookie", " cookie %s",
+ p->cookie, p->cookie_len);
+
+ if (p->peer_cookie_len > 0)
+ print_cookie("peer_cookie", " peer cookie %s",
+ p->peer_cookie, p->peer_cookie_len);
+
+ if (p->reorder_timeout != 0)
+ print_uint(PRINT_ANY, "reorder_timeout",
+ " reorder timeout: %u", p->reorder_timeout);
+
+
+ if (p->send_seq || p->recv_seq) {
+ print_string(PRINT_FP, NULL, "%s sequence numbering:", _SL_);
+
+ if (p->send_seq)
+ print_null(PRINT_ANY, "send_seq", " send", NULL);
+ if (p->recv_seq)
+ print_null(PRINT_ANY, "recv_seq", " recv", NULL);
+
+ }
+ print_string(PRINT_FP, NULL, "\n", NULL);
+ close_json_object();
+}
+
+static int get_response(struct nlmsghdr *n, void *arg)
+{
+ struct genlmsghdr *ghdr;
+ struct l2tp_data *data = arg;
+ struct l2tp_parm *p = &data->config;
+ struct rtattr *attrs[L2TP_ATTR_MAX + 1];
+ struct rtattr *nla_stats, *rta;
+ int len;
+
+ /* Validate message and parse attributes */
+ if (n->nlmsg_type == NLMSG_ERROR)
+ return -EBADMSG;
+
+ ghdr = NLMSG_DATA(n);
+ len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*ghdr));
+ if (len < 0)
+ return -1;
+
+ parse_rtattr(attrs, L2TP_ATTR_MAX, (void *)ghdr + GENL_HDRLEN, len);
+
+ if (attrs[L2TP_ATTR_PW_TYPE])
+ p->pw_type = rta_getattr_u16(attrs[L2TP_ATTR_PW_TYPE]);
+ if (attrs[L2TP_ATTR_ENCAP_TYPE])
+ p->encap = rta_getattr_u16(attrs[L2TP_ATTR_ENCAP_TYPE]);
+ if (attrs[L2TP_ATTR_CONN_ID])
+ p->tunnel_id = rta_getattr_u32(attrs[L2TP_ATTR_CONN_ID]);
+ if (attrs[L2TP_ATTR_PEER_CONN_ID])
+ p->peer_tunnel_id = rta_getattr_u32(attrs[L2TP_ATTR_PEER_CONN_ID]);
+ if (attrs[L2TP_ATTR_SESSION_ID])
+ p->session_id = rta_getattr_u32(attrs[L2TP_ATTR_SESSION_ID]);
+ if (attrs[L2TP_ATTR_PEER_SESSION_ID])
+ p->peer_session_id = rta_getattr_u32(attrs[L2TP_ATTR_PEER_SESSION_ID]);
+ if (attrs[L2TP_ATTR_L2SPEC_TYPE])
+ p->l2spec_type = rta_getattr_u8(attrs[L2TP_ATTR_L2SPEC_TYPE]);
+ if (attrs[L2TP_ATTR_L2SPEC_LEN])
+ p->l2spec_len = rta_getattr_u8(attrs[L2TP_ATTR_L2SPEC_LEN]);
+
+ if (attrs[L2TP_ATTR_UDP_CSUM])
+ p->udp_csum = !!rta_getattr_u8(attrs[L2TP_ATTR_UDP_CSUM]);
+
+ p->udp6_csum_tx = !attrs[L2TP_ATTR_UDP_ZERO_CSUM6_TX];
+ p->udp6_csum_rx = !attrs[L2TP_ATTR_UDP_ZERO_CSUM6_RX];
+
+ if (attrs[L2TP_ATTR_COOKIE])
+ memcpy(p->cookie, RTA_DATA(attrs[L2TP_ATTR_COOKIE]),
+ p->cookie_len = RTA_PAYLOAD(attrs[L2TP_ATTR_COOKIE]));
+
+ if (attrs[L2TP_ATTR_PEER_COOKIE])
+ memcpy(p->peer_cookie, RTA_DATA(attrs[L2TP_ATTR_PEER_COOKIE]),
+ p->peer_cookie_len = RTA_PAYLOAD(attrs[L2TP_ATTR_PEER_COOKIE]));
+
+ if (attrs[L2TP_ATTR_RECV_SEQ])
+ p->recv_seq = !!rta_getattr_u8(attrs[L2TP_ATTR_RECV_SEQ]);
+ if (attrs[L2TP_ATTR_SEND_SEQ])
+ p->send_seq = !!rta_getattr_u8(attrs[L2TP_ATTR_SEND_SEQ]);
+
+ if (attrs[L2TP_ATTR_RECV_TIMEOUT])
+ p->reorder_timeout = rta_getattr_u64(attrs[L2TP_ATTR_RECV_TIMEOUT]);
+
+ rta = attrs[L2TP_ATTR_IP_SADDR];
+ p->local_ip.family = AF_INET;
+ if (!rta) {
+ rta = attrs[L2TP_ATTR_IP6_SADDR];
+ p->local_ip.family = AF_INET6;
+ }
+ if (rta && get_addr_rta(&p->local_ip, rta, p->local_ip.family))
+ return -1;
+
+ rta = attrs[L2TP_ATTR_IP_DADDR];
+ p->peer_ip.family = AF_INET;
+ if (!rta) {
+ rta = attrs[L2TP_ATTR_IP6_DADDR];
+ p->peer_ip.family = AF_INET6;
+ }
+ if (rta && get_addr_rta(&p->peer_ip, rta, p->peer_ip.family))
+ return -1;
+
+ if (attrs[L2TP_ATTR_UDP_SPORT])
+ p->local_udp_port = rta_getattr_u16(attrs[L2TP_ATTR_UDP_SPORT]);
+ if (attrs[L2TP_ATTR_UDP_DPORT])
+ p->peer_udp_port = rta_getattr_u16(attrs[L2TP_ATTR_UDP_DPORT]);
+ if (attrs[L2TP_ATTR_IFNAME])
+ p->ifname = rta_getattr_str(attrs[L2TP_ATTR_IFNAME]);
+
+ nla_stats = attrs[L2TP_ATTR_STATS];
+ if (nla_stats) {
+ struct rtattr *tb[L2TP_ATTR_STATS_MAX + 1];
+
+ parse_rtattr_nested(tb, L2TP_ATTR_STATS_MAX, nla_stats);
+
+ if (tb[L2TP_ATTR_TX_PACKETS])
+ data->stats.data_tx_packets = rta_getattr_u64(tb[L2TP_ATTR_TX_PACKETS]);
+ if (tb[L2TP_ATTR_TX_BYTES])
+ data->stats.data_tx_bytes = rta_getattr_u64(tb[L2TP_ATTR_TX_BYTES]);
+ if (tb[L2TP_ATTR_TX_ERRORS])
+ data->stats.data_tx_errors = rta_getattr_u64(tb[L2TP_ATTR_TX_ERRORS]);
+ if (tb[L2TP_ATTR_RX_PACKETS])
+ data->stats.data_rx_packets = rta_getattr_u64(tb[L2TP_ATTR_RX_PACKETS]);
+ if (tb[L2TP_ATTR_RX_BYTES])
+ data->stats.data_rx_bytes = rta_getattr_u64(tb[L2TP_ATTR_RX_BYTES]);
+ if (tb[L2TP_ATTR_RX_ERRORS])
+ data->stats.data_rx_errors = rta_getattr_u64(tb[L2TP_ATTR_RX_ERRORS]);
+ if (tb[L2TP_ATTR_RX_SEQ_DISCARDS])
+ data->stats.data_rx_oos_discards = rta_getattr_u64(tb[L2TP_ATTR_RX_SEQ_DISCARDS]);
+ if (tb[L2TP_ATTR_RX_OOS_PACKETS])
+ data->stats.data_rx_oos_packets = rta_getattr_u64(tb[L2TP_ATTR_RX_OOS_PACKETS]);
+ }
+
+ return 0;
+}
+
+static int session_nlmsg(struct nlmsghdr *n, void *arg)
+{
+ int ret = get_response(n, arg);
+
+ if (ret == 0)
+ print_session(arg);
+
+ return ret;
+}
+
+static int get_session(struct l2tp_data *p)
+{
+ GENL_REQUEST(req, 128, genl_family, 0, L2TP_GENL_VERSION,
+ L2TP_CMD_SESSION_GET,
+ NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST);
+
+ req.n.nlmsg_seq = genl_rth.dump = ++genl_rth.seq;
+
+ if (p->config.tunnel_id && p->config.session_id) {
+ addattr32(&req.n, 128, L2TP_ATTR_CONN_ID, p->config.tunnel_id);
+ addattr32(&req.n, 128, L2TP_ATTR_SESSION_ID,
+ p->config.session_id);
+ }
+
+ if (rtnl_send(&genl_rth, &req, req.n.nlmsg_len) < 0)
+ return -2;
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&genl_rth, session_nlmsg, p) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ delete_json_obj();
+ fflush(stdout);
+
+ return 0;
+}
+
+static int tunnel_nlmsg(struct nlmsghdr *n, void *arg)
+{
+ int ret = get_response(n, arg);
+
+ if (ret == 0)
+ print_tunnel(arg);
+
+ return ret;
+}
+
+static int get_tunnel(struct l2tp_data *p)
+{
+ GENL_REQUEST(req, 1024, genl_family, 0, L2TP_GENL_VERSION,
+ L2TP_CMD_TUNNEL_GET,
+ NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST);
+
+ req.n.nlmsg_seq = genl_rth.dump = ++genl_rth.seq;
+
+ if (p->config.tunnel_id)
+ addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->config.tunnel_id);
+
+ if (rtnl_send(&genl_rth, &req, req.n.nlmsg_len) < 0)
+ return -2;
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&genl_rth, tunnel_nlmsg, p) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ delete_json_obj();
+ fflush(stdout);
+
+ return 0;
+}
+
+/*****************************************************************************
+ * Command parser
+ *****************************************************************************/
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr, "Usage: ip l2tp add tunnel\n"
+ " remote ADDR local ADDR\n"
+ " tunnel_id ID peer_tunnel_id ID\n"
+ " [ encap { ip | udp } ]\n"
+ " [ udp_sport PORT ] [ udp_dport PORT ]\n"
+ " [ udp_csum { on | off } ]\n"
+ " [ udp6_csum_tx { on | off } ]\n"
+ " [ udp6_csum_rx { on | off } ]\n"
+ "Usage: ip l2tp add session [ name NAME ]\n"
+ " tunnel_id ID\n"
+ " session_id ID peer_session_id ID\n"
+ " [ cookie HEXSTR ] [ peer_cookie HEXSTR ]\n"
+ " [ seq { none | send | recv | both } ]\n"
+ " [ l2spec_type L2SPEC ]\n"
+ " ip l2tp del tunnel tunnel_id ID\n"
+ " ip l2tp del session tunnel_id ID session_id ID\n"
+ " ip l2tp show tunnel [ tunnel_id ID ]\n"
+ " ip l2tp show session [ tunnel_id ID ] [ session_id ID ]\n"
+ "\n"
+ "Where: NAME := STRING\n"
+ " ADDR := { IP_ADDRESS | any }\n"
+ " PORT := { 0..65535 }\n"
+ " ID := { 1..4294967295 }\n"
+ " HEXSTR := { 8 or 16 hex digits (4 / 8 bytes) }\n"
+ " L2SPEC := { none | default }\n");
+
+ exit(-1);
+}
+
+static int parse_args(int argc, char **argv, int cmd, struct l2tp_parm *p)
+{
+ memset(p, 0, sizeof(*p));
+
+ if (argc == 0)
+ usage();
+
+ /* Defaults */
+ p->l2spec_type = L2TP_L2SPECTYPE_DEFAULT;
+ p->l2spec_len = 4;
+ p->udp6_csum_rx = 1;
+ p->udp6_csum_tx = 1;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "encap") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "ip") == 0) {
+ p->encap = L2TP_ENCAPTYPE_IP;
+ } else if (strcmp(*argv, "udp") == 0) {
+ p->encap = L2TP_ENCAPTYPE_UDP;
+ } else {
+ fprintf(stderr, "Unknown tunnel encapsulation \"%s\"\n", *argv);
+ exit(-1);
+ }
+ } else if (strcmp(*argv, "name") == 0) {
+ NEXT_ARG();
+ if (check_ifname(*argv))
+ invarg("\"name\" not a valid ifname", *argv);
+ p->ifname = *argv;
+ } else if (strcmp(*argv, "remote") == 0) {
+ NEXT_ARG();
+ if (get_addr(&p->peer_ip, *argv, AF_UNSPEC))
+ invarg("invalid remote address\n", *argv);
+ } else if (strcmp(*argv, "local") == 0) {
+ NEXT_ARG();
+ if (get_addr(&p->local_ip, *argv, AF_UNSPEC))
+ invarg("invalid local address\n", *argv);
+ } else if ((strcmp(*argv, "tunnel_id") == 0) ||
+ (strcmp(*argv, "tid") == 0)) {
+ __u32 uval;
+
+ NEXT_ARG();
+ if (get_u32(&uval, *argv, 0))
+ invarg("invalid ID\n", *argv);
+ p->tunnel_id = uval;
+ } else if ((strcmp(*argv, "peer_tunnel_id") == 0) ||
+ (strcmp(*argv, "ptid") == 0)) {
+ __u32 uval;
+
+ NEXT_ARG();
+ if (get_u32(&uval, *argv, 0))
+ invarg("invalid ID\n", *argv);
+ p->peer_tunnel_id = uval;
+ } else if ((strcmp(*argv, "session_id") == 0) ||
+ (strcmp(*argv, "sid") == 0)) {
+ __u32 uval;
+
+ NEXT_ARG();
+ if (get_u32(&uval, *argv, 0))
+ invarg("invalid ID\n", *argv);
+ p->session_id = uval;
+ } else if ((strcmp(*argv, "peer_session_id") == 0) ||
+ (strcmp(*argv, "psid") == 0)) {
+ __u32 uval;
+
+ NEXT_ARG();
+ if (get_u32(&uval, *argv, 0))
+ invarg("invalid ID\n", *argv);
+ p->peer_session_id = uval;
+ } else if (strcmp(*argv, "udp_sport") == 0) {
+ __u16 uval;
+
+ NEXT_ARG();
+ if (get_u16(&uval, *argv, 0))
+ invarg("invalid port\n", *argv);
+ p->local_udp_port = uval;
+ } else if (strcmp(*argv, "udp_dport") == 0) {
+ __u16 uval;
+
+ NEXT_ARG();
+ if (get_u16(&uval, *argv, 0))
+ invarg("invalid port\n", *argv);
+ p->peer_udp_port = uval;
+ } else if (strcmp(*argv, "udp_csum") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "on") == 0)
+ p->udp_csum = 1;
+ else if (strcmp(*argv, "off") == 0)
+ p->udp_csum = 0;
+ else
+ invarg("invalid option for udp_csum\n", *argv);
+ } else if (strcmp(*argv, "udp6_csum_rx") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "on") == 0)
+ p->udp6_csum_rx = 1;
+ else if (strcmp(*argv, "off") == 0)
+ p->udp6_csum_rx = 0;
+ else
+ invarg("invalid option for udp6_csum_rx\n"
+ , *argv);
+ } else if (strcmp(*argv, "udp6_csum_tx") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "on") == 0)
+ p->udp6_csum_tx = 1;
+ else if (strcmp(*argv, "off") == 0)
+ p->udp6_csum_tx = 0;
+ else
+ invarg("invalid option for udp6_csum_tx\n"
+ , *argv);
+ } else if (strcmp(*argv, "offset") == 0) {
+ fprintf(stderr, "Ignoring option \"offset\"\n");
+ NEXT_ARG();
+ } else if (strcmp(*argv, "peer_offset") == 0) {
+ fprintf(stderr, "Ignoring option \"peer_offset\"\n");
+ NEXT_ARG();
+ } else if (strcmp(*argv, "cookie") == 0) {
+ int slen;
+
+ NEXT_ARG();
+ slen = strlen(*argv);
+ if ((slen != 8) && (slen != 16))
+ invarg("cookie must be either 8 or 16 hex digits\n", *argv);
+
+ p->cookie_len = slen / 2;
+ if (hex2mem(*argv, p->cookie, p->cookie_len) < 0)
+ invarg("cookie must be a hex string\n", *argv);
+ } else if (strcmp(*argv, "peer_cookie") == 0) {
+ int slen;
+
+ NEXT_ARG();
+ slen = strlen(*argv);
+ if ((slen != 8) && (slen != 16))
+ invarg("cookie must be either 8 or 16 hex digits\n", *argv);
+
+ p->peer_cookie_len = slen / 2;
+ if (hex2mem(*argv, p->peer_cookie, p->peer_cookie_len) < 0)
+ invarg("cookie must be a hex string\n", *argv);
+ } else if (strcmp(*argv, "l2spec_type") == 0) {
+ NEXT_ARG();
+ if (strcasecmp(*argv, "default") == 0) {
+ p->l2spec_type = L2TP_L2SPECTYPE_DEFAULT;
+ p->l2spec_len = 4;
+ } else if (strcasecmp(*argv, "none") == 0) {
+ p->l2spec_type = L2TP_L2SPECTYPE_NONE;
+ p->l2spec_len = 0;
+ } else {
+ fprintf(stderr,
+ "Unknown layer2specific header type \"%s\"\n",
+ *argv);
+ exit(-1);
+ }
+ } else if (strcmp(*argv, "seq") == 0) {
+ NEXT_ARG();
+ if (strcasecmp(*argv, "both") == 0) {
+ p->recv_seq = 1;
+ p->send_seq = 1;
+ } else if (strcasecmp(*argv, "recv") == 0) {
+ p->recv_seq = 1;
+ } else if (strcasecmp(*argv, "send") == 0) {
+ p->send_seq = 1;
+ } else if (strcasecmp(*argv, "none") == 0) {
+ p->recv_seq = 0;
+ p->send_seq = 0;
+ } else {
+ fprintf(stderr,
+ "Unknown seq value \"%s\"\n", *argv);
+ exit(-1);
+ }
+ } else if (strcmp(*argv, "tunnel") == 0) {
+ p->tunnel = 1;
+ } else if (strcmp(*argv, "session") == 0) {
+ p->session = 1;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ fprintf(stderr, "Unknown command: %s\n", *argv);
+ usage();
+ }
+
+ argc--; argv++;
+ }
+
+ return 0;
+}
+
+
+static int do_add(int argc, char **argv)
+{
+ struct l2tp_parm p;
+ int ret = 0;
+
+ if (parse_args(argc, argv, L2TP_ADD, &p) < 0)
+ return -1;
+
+ if (!p.tunnel && !p.session)
+ missarg("tunnel or session");
+
+ if (p.tunnel_id == 0)
+ missarg("tunnel_id");
+
+ /* session_id and peer_session_id must be provided for sessions */
+ if ((p.session) && (p.peer_session_id == 0))
+ missarg("peer_session_id");
+ if ((p.session) && (p.session_id == 0))
+ missarg("session_id");
+
+ /* peer_tunnel_id is needed for tunnels */
+ if ((p.tunnel) && (p.peer_tunnel_id == 0))
+ missarg("peer_tunnel_id");
+
+ if (p.tunnel) {
+ if (p.local_ip.family == AF_UNSPEC)
+ missarg("local");
+
+ if (p.peer_ip.family == AF_UNSPEC)
+ missarg("remote");
+
+ if (p.encap == L2TP_ENCAPTYPE_UDP) {
+ if (p.local_udp_port == 0)
+ missarg("udp_sport");
+ if (p.peer_udp_port == 0)
+ missarg("udp_dport");
+ }
+
+ ret = create_tunnel(&p);
+ }
+
+ if (p.session) {
+ /* Only ethernet pseudowires supported */
+ p.pw_type = L2TP_PWTYPE_ETH;
+
+ ret = create_session(&p);
+ }
+
+ return ret;
+}
+
+static int do_del(int argc, char **argv)
+{
+ struct l2tp_parm p;
+
+ if (parse_args(argc, argv, L2TP_DEL, &p) < 0)
+ return -1;
+
+ if (!p.tunnel && !p.session)
+ missarg("tunnel or session");
+
+ if ((p.tunnel) && (p.tunnel_id == 0))
+ missarg("tunnel_id");
+ if ((p.session) && (p.session_id == 0))
+ missarg("session_id");
+
+ if (p.session_id)
+ return delete_session(&p);
+ else
+ return delete_tunnel(&p);
+
+ return -1;
+}
+
+static int do_show(int argc, char **argv)
+{
+ struct l2tp_data data;
+ struct l2tp_parm *p = &data.config;
+
+ if (parse_args(argc, argv, L2TP_GET, p) < 0)
+ return -1;
+
+ if (!p->tunnel && !p->session)
+ missarg("tunnel or session");
+
+ if (p->session)
+ get_session(&data);
+ else
+ get_tunnel(&data);
+
+ return 0;
+}
+
+int do_ipl2tp(int argc, char **argv)
+{
+ if (argc < 1 || !matches(*argv, "help"))
+ usage();
+
+ if (genl_init_handle(&genl_rth, L2TP_GENL_NAME, &genl_family))
+ exit(1);
+
+ if (matches(*argv, "add") == 0)
+ return do_add(argc-1, argv+1);
+ if (matches(*argv, "delete") == 0)
+ return do_del(argc-1, argv+1);
+ if (matches(*argv, "show") == 0 ||
+ matches(*argv, "lst") == 0 ||
+ matches(*argv, "list") == 0)
+ return do_show(argc-1, argv+1);
+
+ fprintf(stderr,
+ "Command \"%s\" is unknown, try \"ip l2tp help\".\n", *argv);
+ exit(-1);
+}
diff --git a/ip/iplink.c b/ip/iplink.c
new file mode 100644
index 0000000..95314af
--- /dev/null
+++ b/ip/iplink.c
@@ -0,0 +1,1463 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iplink.c "ip link".
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <linux/if.h>
+#include <linux/if_packet.h>
+#include <linux/if_ether.h>
+#include <linux/sockios.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <stdbool.h>
+#include <linux/mpls.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "namespace.h"
+
+#ifndef GSO_MAX_SEGS
+#define GSO_MAX_SEGS 65535
+#endif
+
+
+static void usage(void) __attribute__((noreturn));
+
+void iplink_types_usage(void)
+{
+ /* Remember to add new entry here if new type is added. */
+ fprintf(stderr,
+ "TYPE := { amt | bareudp | bond | bond_slave | bridge | bridge_slave |\n"
+ " dsa | dummy | erspan | geneve | gre | gretap | gtp | ifb |\n"
+ " ip6erspan | ip6gre | ip6gretap | ip6tnl |\n"
+ " ipip | ipoib | ipvlan | ipvtap |\n"
+ " macsec | macvlan | macvtap | netdevsim |\n"
+ " netkit | nlmon | rmnet | sit | team | team_slave |\n"
+ " vcan | veth | vlan | vrf | vti | vxcan | vxlan | wwan |\n"
+ " xfrm | virt_wifi }\n");
+}
+
+void iplink_usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip link add [link DEV | parentdev NAME] [ name ] NAME\n"
+ " [ txqueuelen PACKETS ]\n"
+ " [ address LLADDR ]\n"
+ " [ broadcast LLADDR ]\n"
+ " [ mtu MTU ] [index IDX ]\n"
+ " [ numtxqueues QUEUE_COUNT ]\n"
+ " [ numrxqueues QUEUE_COUNT ]\n"
+ " [ netns { PID | NETNSNAME | NETNSFILE } ]\n"
+ " type TYPE [ ARGS ]\n"
+ "\n"
+ " ip link delete { DEVICE | dev DEVICE | group DEVGROUP } type TYPE [ ARGS ]\n"
+ "\n"
+ " ip link set { DEVICE | dev DEVICE | group DEVGROUP }\n"
+ " [ { up | down } ]\n"
+ " [ type TYPE ARGS ]\n");
+
+ fprintf(stderr,
+ " [ arp { on | off } ]\n"
+ " [ dynamic { on | off } ]\n"
+ " [ multicast { on | off } ]\n"
+ " [ allmulticast { on | off } ]\n"
+ " [ promisc { on | off } ]\n"
+ " [ trailers { on | off } ]\n"
+ " [ carrier { on | off } ]\n"
+ " [ txqueuelen PACKETS ]\n"
+ " [ name NEWNAME ]\n"
+ " [ address LLADDR ]\n"
+ " [ broadcast LLADDR ]\n"
+ " [ mtu MTU ]\n"
+ " [ netns { PID | NETNSNAME | NETNSFILE } ]\n"
+ " [ link-netns NAME | link-netnsid ID ]\n"
+ " [ alias NAME ]\n"
+ " [ vf NUM [ mac LLADDR ]\n"
+ " [ vlan VLANID [ qos VLAN-QOS ] [ proto VLAN-PROTO ] ]\n"
+ " [ rate TXRATE ]\n"
+ " [ max_tx_rate TXRATE ]\n"
+ " [ min_tx_rate TXRATE ]\n"
+ " [ spoofchk { on | off} ]\n"
+ " [ query_rss { on | off} ]\n"
+ " [ state { auto | enable | disable} ]\n"
+ " [ trust { on | off} ]\n"
+ " [ node_guid EUI64 ]\n"
+ " [ port_guid EUI64 ] ]\n"
+ " [ { xdp | xdpgeneric | xdpdrv | xdpoffload } { off |\n"
+#ifdef HAVE_LIBBPF
+ " object FILE [ { section | program } NAME ] [ verbose ] |\n"
+#else
+ " object FILE [ section NAME ] [ verbose ] |\n"
+#endif
+ " pinned FILE } ]\n"
+ " [ master DEVICE ][ vrf NAME ]\n"
+ " [ nomaster ]\n"
+ " [ addrgenmode { eui64 | none | stable_secret | random } ]\n"
+ " [ protodown { on | off } ]\n"
+ " [ protodown_reason PREASON { on | off } ]\n"
+ " [ gso_max_size BYTES ] [ gso_ipv4_max_size BYTES ] [ gso_max_segs PACKETS ]\n"
+ " [ gro_max_size BYTES ] [ gro_ipv4_max_size BYTES ]\n"
+ "\n"
+ " ip link show [ DEVICE | group GROUP ] [up] [master DEV] [vrf NAME] [type TYPE]\n"
+ " [nomaster]\n"
+ "\n"
+ " ip link xstats type TYPE [ ARGS ]\n"
+ "\n"
+ " ip link afstats [ dev DEVICE ]\n"
+ " ip link property add dev DEVICE [ altname NAME .. ]\n"
+ " ip link property del dev DEVICE [ altname NAME .. ]\n");
+
+ fprintf(stderr,
+ "\n"
+ " ip link help [ TYPE ]\n"
+ "\n");
+ iplink_types_usage();
+
+ exit(-1);
+}
+
+static void usage(void)
+{
+ iplink_usage();
+}
+
+static int on_off(const char *msg, const char *realval)
+{
+ fprintf(stderr,
+ "Error: argument of \"%s\" must be \"on\" or \"off\", not \"%s\"\n",
+ msg, realval);
+ return -1;
+}
+
+static void *BODY; /* cached dlopen(NULL) handle */
+static struct link_util *linkutil_list;
+
+struct link_util *get_link_kind(const char *id)
+{
+ void *dlh;
+ char buf[256];
+ struct link_util *l;
+
+ for (l = linkutil_list; l; l = l->next)
+ if (strcmp(l->id, id) == 0)
+ return l;
+
+ snprintf(buf, sizeof(buf), "%s/link_%s.so", get_ip_lib_dir(), id);
+ dlh = dlopen(buf, RTLD_LAZY);
+ if (dlh == NULL) {
+ /* look in current binary, only open once */
+ dlh = BODY;
+ if (dlh == NULL) {
+ dlh = BODY = dlopen(NULL, RTLD_LAZY);
+ if (dlh == NULL)
+ return NULL;
+ }
+ }
+
+ snprintf(buf, sizeof(buf), "%s_link_util", id);
+ l = dlsym(dlh, buf);
+ if (l == NULL)
+ return NULL;
+
+ l->next = linkutil_list;
+ linkutil_list = l;
+ return l;
+}
+
+static int get_link_mode(const char *mode)
+{
+ if (strcasecmp(mode, "default") == 0)
+ return IF_LINK_MODE_DEFAULT;
+ if (strcasecmp(mode, "dormant") == 0)
+ return IF_LINK_MODE_DORMANT;
+ return -1;
+}
+
+static int get_addr_gen_mode(const char *mode)
+{
+ if (strcasecmp(mode, "eui64") == 0)
+ return IN6_ADDR_GEN_MODE_EUI64;
+ if (strcasecmp(mode, "none") == 0)
+ return IN6_ADDR_GEN_MODE_NONE;
+ if (strcasecmp(mode, "stable_secret") == 0)
+ return IN6_ADDR_GEN_MODE_STABLE_PRIVACY;
+ if (strcasecmp(mode, "random") == 0)
+ return IN6_ADDR_GEN_MODE_RANDOM;
+ return -1;
+}
+
+static int nl_get_ll_addr_len(const char *ifname)
+{
+ int len;
+ int dev_index = ll_name_to_index(ifname);
+ struct iplink_req req = {
+ .n = {
+ .nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .nlmsg_type = RTM_GETLINK,
+ .nlmsg_flags = NLM_F_REQUEST
+ },
+ .i = {
+ .ifi_family = preferred_family,
+ .ifi_index = dev_index,
+ }
+ };
+ struct nlmsghdr *answer;
+ struct rtattr *tb[IFLA_MAX+1];
+
+ if (dev_index == 0)
+ return -1;
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0)
+ return -1;
+
+ len = answer->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifinfomsg));
+ if (len < 0) {
+ free(answer);
+ return -1;
+ }
+
+ parse_rtattr_flags(tb, IFLA_MAX, IFLA_RTA(NLMSG_DATA(answer)),
+ len, NLA_F_NESTED);
+ if (!tb[IFLA_ADDRESS]) {
+ free(answer);
+ return -1;
+ }
+
+ len = RTA_PAYLOAD(tb[IFLA_ADDRESS]);
+ free(answer);
+ return len;
+}
+
+static void iplink_parse_vf_vlan_info(int vf, int *argcp, char ***argvp,
+ struct ifla_vf_vlan_info *ivvip)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+ unsigned int vci;
+
+ NEXT_ARG();
+ if (get_unsigned(&vci, *argv, 0) || vci > 4095)
+ invarg("Invalid \"vlan\" value\n", *argv);
+
+ ivvip->vlan = vci;
+ ivvip->vf = vf;
+ ivvip->qos = 0;
+ ivvip->vlan_proto = htons(ETH_P_8021Q);
+ if (NEXT_ARG_OK()) {
+ NEXT_ARG();
+ if (matches(*argv, "qos") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&ivvip->qos, *argv, 0))
+ invarg("Invalid \"qos\" value\n", *argv);
+ } else {
+ /* rewind arg */
+ PREV_ARG();
+ }
+ }
+ if (NEXT_ARG_OK()) {
+ NEXT_ARG();
+ if (matches(*argv, "proto") == 0) {
+ NEXT_ARG();
+ if (ll_proto_a2n(&ivvip->vlan_proto, *argv))
+ invarg("protocol is invalid\n", *argv);
+ if (ivvip->vlan_proto != htons(ETH_P_8021AD) &&
+ ivvip->vlan_proto != htons(ETH_P_8021Q)) {
+ SPRINT_BUF(b1);
+ SPRINT_BUF(b2);
+ char msg[64 + sizeof(b1) + sizeof(b2)];
+
+ sprintf(msg,
+ "Invalid \"vlan protocol\" value - supported %s, %s\n",
+ ll_proto_n2a(htons(ETH_P_8021Q),
+ b1, sizeof(b1)),
+ ll_proto_n2a(htons(ETH_P_8021AD),
+ b2, sizeof(b2)));
+ invarg(msg, *argv);
+ }
+ } else {
+ /* rewind arg */
+ PREV_ARG();
+ }
+ }
+
+ *argcp = argc;
+ *argvp = argv;
+}
+
+static int iplink_parse_vf(int vf, int *argcp, char ***argvp,
+ struct iplink_req *req, const char *dev)
+{
+ char new_rate_api = 0, count = 0, override_legacy_rate = 0;
+ struct ifla_vf_rate tivt;
+ int len, argc = *argcp;
+ char **argv = *argvp;
+ struct rtattr *vfinfo;
+ int ret;
+
+ tivt.min_tx_rate = -1;
+ tivt.max_tx_rate = -1;
+
+ vfinfo = addattr_nest(&req->n, sizeof(*req), IFLA_VF_INFO);
+
+ while (NEXT_ARG_OK()) {
+ NEXT_ARG();
+ count++;
+ if (!matches(*argv, "max_tx_rate")) {
+ /* new API in use */
+ new_rate_api = 1;
+ /* override legacy rate */
+ override_legacy_rate = 1;
+ } else if (!matches(*argv, "min_tx_rate")) {
+ /* new API in use */
+ new_rate_api = 1;
+ }
+ }
+
+ while (count--) {
+ /* rewind arg */
+ PREV_ARG();
+ }
+
+ while (NEXT_ARG_OK()) {
+ NEXT_ARG();
+ if (matches(*argv, "mac") == 0) {
+ struct ifla_vf_mac ivm = { 0 };
+ int halen = nl_get_ll_addr_len(dev);
+
+ NEXT_ARG();
+ ivm.vf = vf;
+ len = ll_addr_a2n((char *)ivm.mac, 32, *argv);
+ if (len < 0)
+ return -1;
+ if (halen > 0 && len != halen) {
+ fprintf(stderr,
+ "Invalid address length %d - must be %d bytes\n",
+ len, halen);
+ return -1;
+ }
+ addattr_l(&req->n, sizeof(*req), IFLA_VF_MAC,
+ &ivm, sizeof(ivm));
+ } else if (matches(*argv, "vlan") == 0) {
+ struct ifla_vf_vlan_info ivvi;
+
+ iplink_parse_vf_vlan_info(vf, &argc, &argv, &ivvi);
+ /* support the old interface in case of older kernel*/
+ if (ivvi.vlan_proto == htons(ETH_P_8021Q)) {
+ struct ifla_vf_vlan ivv;
+
+ ivv.vf = ivvi.vf;
+ ivv.vlan = ivvi.vlan;
+ ivv.qos = ivvi.qos;
+ addattr_l(&req->n, sizeof(*req),
+ IFLA_VF_VLAN, &ivv, sizeof(ivv));
+ } else {
+ struct rtattr *vfvlanlist;
+
+ vfvlanlist = addattr_nest(&req->n, sizeof(*req),
+ IFLA_VF_VLAN_LIST);
+ addattr_l(&req->n, sizeof(*req),
+ IFLA_VF_VLAN_INFO, &ivvi,
+ sizeof(ivvi));
+
+ while (NEXT_ARG_OK()) {
+ NEXT_ARG();
+ if (matches(*argv, "vlan") != 0) {
+ PREV_ARG();
+ break;
+ }
+ iplink_parse_vf_vlan_info(vf, &argc,
+ &argv, &ivvi);
+ addattr_l(&req->n, sizeof(*req),
+ IFLA_VF_VLAN_INFO, &ivvi,
+ sizeof(ivvi));
+ }
+ addattr_nest_end(&req->n, vfvlanlist);
+ }
+ } else if (matches(*argv, "rate") == 0) {
+ struct ifla_vf_tx_rate ivt;
+
+ NEXT_ARG();
+ if (get_unsigned(&ivt.rate, *argv, 0))
+ invarg("Invalid \"rate\" value\n", *argv);
+
+ ivt.vf = vf;
+ if (!new_rate_api)
+ addattr_l(&req->n, sizeof(*req),
+ IFLA_VF_TX_RATE, &ivt, sizeof(ivt));
+ else if (!override_legacy_rate)
+ tivt.max_tx_rate = ivt.rate;
+
+ } else if (matches(*argv, "max_tx_rate") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&tivt.max_tx_rate, *argv, 0))
+ invarg("Invalid \"max tx rate\" value\n",
+ *argv);
+ tivt.vf = vf;
+
+ } else if (matches(*argv, "min_tx_rate") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&tivt.min_tx_rate, *argv, 0))
+ invarg("Invalid \"min tx rate\" value\n",
+ *argv);
+ tivt.vf = vf;
+
+ } else if (matches(*argv, "spoofchk") == 0) {
+ struct ifla_vf_spoofchk ivs;
+
+ NEXT_ARG();
+ ivs.setting = parse_on_off("spoofchk", *argv, &ret);
+ if (ret)
+ return ret;
+ ivs.vf = vf;
+ addattr_l(&req->n, sizeof(*req), IFLA_VF_SPOOFCHK,
+ &ivs, sizeof(ivs));
+
+ } else if (matches(*argv, "query_rss") == 0) {
+ struct ifla_vf_rss_query_en ivs;
+
+ NEXT_ARG();
+ ivs.setting = parse_on_off("query_rss", *argv, &ret);
+ if (ret)
+ return ret;
+ ivs.vf = vf;
+ addattr_l(&req->n, sizeof(*req), IFLA_VF_RSS_QUERY_EN,
+ &ivs, sizeof(ivs));
+
+ } else if (matches(*argv, "trust") == 0) {
+ struct ifla_vf_trust ivt;
+
+ NEXT_ARG();
+ ivt.setting = parse_on_off("trust", *argv, &ret);
+ if (ret)
+ return ret;
+ ivt.vf = vf;
+ addattr_l(&req->n, sizeof(*req), IFLA_VF_TRUST,
+ &ivt, sizeof(ivt));
+
+ } else if (matches(*argv, "state") == 0) {
+ struct ifla_vf_link_state ivl;
+
+ NEXT_ARG();
+ if (matches(*argv, "auto") == 0)
+ ivl.link_state = IFLA_VF_LINK_STATE_AUTO;
+ else if (matches(*argv, "enable") == 0)
+ ivl.link_state = IFLA_VF_LINK_STATE_ENABLE;
+ else if (matches(*argv, "disable") == 0)
+ ivl.link_state = IFLA_VF_LINK_STATE_DISABLE;
+ else
+ invarg("Invalid \"state\" value\n", *argv);
+ ivl.vf = vf;
+ addattr_l(&req->n, sizeof(*req), IFLA_VF_LINK_STATE,
+ &ivl, sizeof(ivl));
+ } else if (matches(*argv, "node_guid") == 0) {
+ struct ifla_vf_guid ivg;
+
+ NEXT_ARG();
+ ivg.vf = vf;
+ if (get_guid(&ivg.guid, *argv)) {
+ invarg("Invalid GUID format\n", *argv);
+ return -1;
+ }
+ addattr_l(&req->n, sizeof(*req), IFLA_VF_IB_NODE_GUID,
+ &ivg, sizeof(ivg));
+ } else if (matches(*argv, "port_guid") == 0) {
+ struct ifla_vf_guid ivg;
+
+ NEXT_ARG();
+ ivg.vf = vf;
+ if (get_guid(&ivg.guid, *argv)) {
+ invarg("Invalid GUID format\n", *argv);
+ return -1;
+ }
+ addattr_l(&req->n, sizeof(*req), IFLA_VF_IB_PORT_GUID,
+ &ivg, sizeof(ivg));
+ } else {
+ /* rewind arg */
+ PREV_ARG();
+ break;
+ }
+ }
+
+ if (new_rate_api) {
+ int tmin, tmax;
+
+ if (tivt.min_tx_rate == -1 || tivt.max_tx_rate == -1) {
+ ipaddr_get_vf_rate(tivt.vf, &tmin, &tmax, dev);
+ if (tivt.min_tx_rate == -1)
+ tivt.min_tx_rate = tmin;
+ if (tivt.max_tx_rate == -1)
+ tivt.max_tx_rate = tmax;
+ }
+
+ if (tivt.max_tx_rate && tivt.min_tx_rate > tivt.max_tx_rate) {
+ fprintf(stderr,
+ "Invalid min_tx_rate %d - must be <= max_tx_rate %d\n",
+ tivt.min_tx_rate, tivt.max_tx_rate);
+ return -1;
+ }
+
+ addattr_l(&req->n, sizeof(*req), IFLA_VF_RATE, &tivt,
+ sizeof(tivt));
+ }
+
+ if (argc == *argcp)
+ incomplete_command();
+
+ addattr_nest_end(&req->n, vfinfo);
+
+ *argcp = argc;
+ *argvp = argv;
+ return 0;
+}
+
+int iplink_parse(int argc, char **argv, struct iplink_req *req, char **type)
+{
+ bool move_netns = false;
+ char *name = NULL;
+ char *dev = NULL;
+ char *link = NULL;
+ int ret, len;
+ char abuf[32];
+ int qlen = -1;
+ int mtu = -1;
+ int netns = -1;
+ int vf = -1;
+ int numtxqueues = -1;
+ int numrxqueues = -1;
+ int link_netnsid = -1;
+ int index = 0;
+ int group = -1;
+ int addr_len = 0;
+ int err;
+
+ ret = argc;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "up") == 0) {
+ req->i.ifi_change |= IFF_UP;
+ req->i.ifi_flags |= IFF_UP;
+ } else if (strcmp(*argv, "down") == 0) {
+ req->i.ifi_change |= IFF_UP;
+ req->i.ifi_flags &= ~IFF_UP;
+ } else if (strcmp(*argv, "name") == 0) {
+ NEXT_ARG();
+ if (name)
+ duparg("name", *argv);
+ if (check_ifname(*argv))
+ invarg("\"name\" not a valid ifname", *argv);
+ name = *argv;
+ if (!dev)
+ dev = name;
+ } else if (strcmp(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (index)
+ duparg("index", *argv);
+ index = atoi(*argv);
+ if (index <= 0)
+ invarg("Invalid \"index\" value", *argv);
+ } else if (matches(*argv, "link") == 0) {
+ NEXT_ARG();
+ link = *argv;
+ } else if (matches(*argv, "address") == 0) {
+ NEXT_ARG();
+ addr_len = ll_addr_a2n(abuf, sizeof(abuf), *argv);
+ if (addr_len < 0)
+ return -1;
+ addattr_l(&req->n, sizeof(*req),
+ IFLA_ADDRESS, abuf, addr_len);
+ } else if (matches(*argv, "broadcast") == 0 ||
+ strcmp(*argv, "brd") == 0) {
+ NEXT_ARG();
+ len = ll_addr_a2n(abuf, sizeof(abuf), *argv);
+ if (len < 0)
+ return -1;
+ addattr_l(&req->n, sizeof(*req),
+ IFLA_BROADCAST, abuf, len);
+ } else if (matches(*argv, "txqueuelen") == 0 ||
+ strcmp(*argv, "qlen") == 0 ||
+ matches(*argv, "txqlen") == 0) {
+ NEXT_ARG();
+ if (qlen != -1)
+ duparg("txqueuelen", *argv);
+ if (get_integer(&qlen, *argv, 0))
+ invarg("Invalid \"txqueuelen\" value\n", *argv);
+ addattr_l(&req->n, sizeof(*req),
+ IFLA_TXQLEN, &qlen, 4);
+ } else if (strcmp(*argv, "mtu") == 0) {
+ NEXT_ARG();
+ if (mtu != -1)
+ duparg("mtu", *argv);
+ if (get_integer(&mtu, *argv, 0))
+ invarg("Invalid \"mtu\" value\n", *argv);
+ addattr_l(&req->n, sizeof(*req), IFLA_MTU, &mtu, 4);
+ } else if (strcmp(*argv, "xdpgeneric") == 0 ||
+ strcmp(*argv, "xdpdrv") == 0 ||
+ strcmp(*argv, "xdpoffload") == 0 ||
+ strcmp(*argv, "xdp") == 0) {
+ bool generic = strcmp(*argv, "xdpgeneric") == 0;
+ bool drv = strcmp(*argv, "xdpdrv") == 0;
+ bool offload = strcmp(*argv, "xdpoffload") == 0;
+
+ NEXT_ARG();
+ if (xdp_parse(&argc, &argv, req, dev,
+ generic, drv, offload))
+ exit(-1);
+
+ if (offload && name == dev)
+ dev = NULL;
+ } else if (strcmp(*argv, "netns") == 0) {
+ NEXT_ARG();
+ if (netns != -1)
+ duparg("netns", *argv);
+ netns = netns_get_fd(*argv);
+ if (netns >= 0)
+ addattr_l(&req->n, sizeof(*req), IFLA_NET_NS_FD,
+ &netns, 4);
+ else if (get_integer(&netns, *argv, 0) == 0)
+ addattr_l(&req->n, sizeof(*req),
+ IFLA_NET_NS_PID, &netns, 4);
+ else
+ invarg("Invalid \"netns\" value\n", *argv);
+ move_netns = true;
+ } else if (strcmp(*argv, "multicast") == 0) {
+ NEXT_ARG();
+ req->i.ifi_change |= IFF_MULTICAST;
+
+ if (strcmp(*argv, "on") == 0)
+ req->i.ifi_flags |= IFF_MULTICAST;
+ else if (strcmp(*argv, "off") == 0)
+ req->i.ifi_flags &= ~IFF_MULTICAST;
+ else
+ return on_off("multicast", *argv);
+ } else if (strcmp(*argv, "allmulticast") == 0) {
+ NEXT_ARG();
+ req->i.ifi_change |= IFF_ALLMULTI;
+
+ if (strcmp(*argv, "on") == 0)
+ req->i.ifi_flags |= IFF_ALLMULTI;
+ else if (strcmp(*argv, "off") == 0)
+ req->i.ifi_flags &= ~IFF_ALLMULTI;
+ else
+ return on_off("allmulticast", *argv);
+ } else if (strcmp(*argv, "promisc") == 0) {
+ NEXT_ARG();
+ req->i.ifi_change |= IFF_PROMISC;
+
+ if (strcmp(*argv, "on") == 0)
+ req->i.ifi_flags |= IFF_PROMISC;
+ else if (strcmp(*argv, "off") == 0)
+ req->i.ifi_flags &= ~IFF_PROMISC;
+ else
+ return on_off("promisc", *argv);
+ } else if (strcmp(*argv, "trailers") == 0) {
+ NEXT_ARG();
+ req->i.ifi_change |= IFF_NOTRAILERS;
+
+ if (strcmp(*argv, "off") == 0)
+ req->i.ifi_flags |= IFF_NOTRAILERS;
+ else if (strcmp(*argv, "on") == 0)
+ req->i.ifi_flags &= ~IFF_NOTRAILERS;
+ else
+ return on_off("trailers", *argv);
+ } else if (strcmp(*argv, "arp") == 0) {
+ NEXT_ARG();
+ req->i.ifi_change |= IFF_NOARP;
+
+ if (strcmp(*argv, "on") == 0)
+ req->i.ifi_flags &= ~IFF_NOARP;
+ else if (strcmp(*argv, "off") == 0)
+ req->i.ifi_flags |= IFF_NOARP;
+ else
+ return on_off("arp", *argv);
+ } else if (strcmp(*argv, "carrier") == 0) {
+ int carrier;
+
+ NEXT_ARG();
+ carrier = parse_on_off("carrier", *argv, &err);
+ if (err)
+ return err;
+
+ addattr8(&req->n, sizeof(*req), IFLA_CARRIER, carrier);
+ } else if (strcmp(*argv, "vf") == 0) {
+ struct rtattr *vflist;
+
+ NEXT_ARG();
+ if (get_integer(&vf, *argv, 0))
+ invarg("Invalid \"vf\" value\n", *argv);
+
+ vflist = addattr_nest(&req->n, sizeof(*req),
+ IFLA_VFINFO_LIST);
+ if (!dev)
+ missarg("dev");
+
+ len = iplink_parse_vf(vf, &argc, &argv, req, dev);
+ if (len < 0)
+ return -1;
+ addattr_nest_end(&req->n, vflist);
+
+ if (name == dev)
+ dev = NULL;
+ } else if (matches(*argv, "master") == 0) {
+ int ifindex;
+
+ NEXT_ARG();
+ ifindex = ll_name_to_index(*argv);
+ if (!ifindex)
+ invarg("Device does not exist\n", *argv);
+ addattr_l(&req->n, sizeof(*req), IFLA_MASTER,
+ &ifindex, 4);
+ } else if (strcmp(*argv, "vrf") == 0) {
+ int ifindex;
+
+ NEXT_ARG();
+ ifindex = ll_name_to_index(*argv);
+ if (!ifindex)
+ invarg("Not a valid VRF name\n", *argv);
+ if (!name_is_vrf(*argv))
+ invarg("Not a valid VRF name\n", *argv);
+ addattr_l(&req->n, sizeof(*req), IFLA_MASTER,
+ &ifindex, sizeof(ifindex));
+ } else if (matches(*argv, "nomaster") == 0) {
+ int ifindex = 0;
+
+ addattr_l(&req->n, sizeof(*req), IFLA_MASTER,
+ &ifindex, 4);
+ } else if (matches(*argv, "dynamic") == 0) {
+ NEXT_ARG();
+ req->i.ifi_change |= IFF_DYNAMIC;
+
+ if (strcmp(*argv, "on") == 0)
+ req->i.ifi_flags |= IFF_DYNAMIC;
+ else if (strcmp(*argv, "off") == 0)
+ req->i.ifi_flags &= ~IFF_DYNAMIC;
+ else
+ return on_off("dynamic", *argv);
+ } else if (matches(*argv, "type") == 0) {
+ NEXT_ARG();
+ *type = *argv;
+ argc--; argv++;
+ break;
+ } else if (matches(*argv, "alias") == 0) {
+ NEXT_ARG();
+ len = strlen(*argv);
+ if (len >= IFALIASZ)
+ invarg("alias too long\n", *argv);
+ addattr_l(&req->n, sizeof(*req), IFLA_IFALIAS,
+ *argv, len);
+ } else if (strcmp(*argv, "group") == 0) {
+ NEXT_ARG();
+ if (group != -1)
+ duparg("group", *argv);
+ if (rtnl_group_a2n(&group, *argv))
+ invarg("Invalid \"group\" value\n", *argv);
+ addattr32(&req->n, sizeof(*req), IFLA_GROUP, group);
+ } else if (strcmp(*argv, "mode") == 0) {
+ int mode;
+
+ NEXT_ARG();
+ mode = get_link_mode(*argv);
+ if (mode < 0)
+ invarg("Invalid link mode\n", *argv);
+ addattr8(&req->n, sizeof(*req), IFLA_LINKMODE, mode);
+ } else if (strcmp(*argv, "state") == 0) {
+ int state;
+
+ NEXT_ARG();
+ state = get_operstate(*argv);
+ if (state < 0)
+ invarg("Invalid operstate\n", *argv);
+
+ addattr8(&req->n, sizeof(*req), IFLA_OPERSTATE, state);
+ } else if (matches(*argv, "numtxqueues") == 0) {
+ NEXT_ARG();
+ if (numtxqueues != -1)
+ duparg("numtxqueues", *argv);
+ if (get_integer(&numtxqueues, *argv, 0))
+ invarg("Invalid \"numtxqueues\" value\n",
+ *argv);
+ addattr_l(&req->n, sizeof(*req), IFLA_NUM_TX_QUEUES,
+ &numtxqueues, 4);
+ } else if (matches(*argv, "numrxqueues") == 0) {
+ NEXT_ARG();
+ if (numrxqueues != -1)
+ duparg("numrxqueues", *argv);
+ if (get_integer(&numrxqueues, *argv, 0))
+ invarg("Invalid \"numrxqueues\" value\n",
+ *argv);
+ addattr_l(&req->n, sizeof(*req), IFLA_NUM_RX_QUEUES,
+ &numrxqueues, 4);
+ } else if (matches(*argv, "addrgenmode") == 0) {
+ struct rtattr *afs, *afs6;
+ int mode;
+
+ NEXT_ARG();
+ mode = get_addr_gen_mode(*argv);
+ if (mode < 0)
+ invarg("Invalid address generation mode\n",
+ *argv);
+ afs = addattr_nest(&req->n, sizeof(*req), IFLA_AF_SPEC);
+ afs6 = addattr_nest(&req->n, sizeof(*req), AF_INET6);
+ addattr8(&req->n, sizeof(*req),
+ IFLA_INET6_ADDR_GEN_MODE, mode);
+ addattr_nest_end(&req->n, afs6);
+ addattr_nest_end(&req->n, afs);
+ } else if (matches(*argv, "link-netns") == 0) {
+ NEXT_ARG();
+ if (link_netnsid != -1)
+ duparg("link-netns/link-netnsid", *argv);
+ link_netnsid = get_netnsid_from_name(*argv);
+ /* No nsid? Try to assign one. */
+ if (link_netnsid < 0)
+ set_netnsid_from_name(*argv, -1);
+ link_netnsid = get_netnsid_from_name(*argv);
+ if (link_netnsid < 0)
+ invarg("Invalid \"link-netns\" value\n",
+ *argv);
+ addattr32(&req->n, sizeof(*req), IFLA_LINK_NETNSID,
+ link_netnsid);
+ } else if (matches(*argv, "link-netnsid") == 0) {
+ NEXT_ARG();
+ if (link_netnsid != -1)
+ duparg("link-netns/link-netnsid", *argv);
+ if (get_integer(&link_netnsid, *argv, 0))
+ invarg("Invalid \"link-netnsid\" value\n",
+ *argv);
+ addattr32(&req->n, sizeof(*req), IFLA_LINK_NETNSID,
+ link_netnsid);
+ } else if (strcmp(*argv, "protodown") == 0) {
+ unsigned int proto_down;
+
+ NEXT_ARG();
+ proto_down = parse_on_off("protodown", *argv, &err);
+ if (err)
+ return err;
+ addattr8(&req->n, sizeof(*req), IFLA_PROTO_DOWN,
+ proto_down);
+ } else if (strcmp(*argv, "protodown_reason") == 0) {
+ struct rtattr *pr;
+ __u32 preason = 0, prvalue = 0, prmask = 0;
+
+ NEXT_ARG();
+ if (protodown_reason_a2n(&preason, *argv))
+ invarg("invalid protodown reason\n", *argv);
+ NEXT_ARG();
+ prmask = 1 << preason;
+ if (matches(*argv, "on") == 0)
+ prvalue |= prmask;
+ else if (matches(*argv, "off") == 0)
+ prvalue &= ~prmask;
+ else
+ return on_off("protodown_reason", *argv);
+ pr = addattr_nest(&req->n, sizeof(*req),
+ IFLA_PROTO_DOWN_REASON | NLA_F_NESTED);
+ addattr32(&req->n, sizeof(*req),
+ IFLA_PROTO_DOWN_REASON_MASK, prmask);
+ addattr32(&req->n, sizeof(*req),
+ IFLA_PROTO_DOWN_REASON_VALUE, prvalue);
+ addattr_nest_end(&req->n, pr);
+ } else if (strcmp(*argv, "gso_max_size") == 0) {
+ unsigned int max_size;
+
+ NEXT_ARG();
+ if (get_unsigned(&max_size, *argv, 0))
+ invarg("Invalid \"gso_max_size\" value\n",
+ *argv);
+ addattr32(&req->n, sizeof(*req),
+ IFLA_GSO_MAX_SIZE, max_size);
+ } else if (strcmp(*argv, "gso_max_segs") == 0) {
+ unsigned int max_segs;
+
+ NEXT_ARG();
+ if (get_unsigned(&max_segs, *argv, 0) ||
+ max_segs > GSO_MAX_SEGS)
+ invarg("Invalid \"gso_max_segs\" value\n",
+ *argv);
+ addattr32(&req->n, sizeof(*req),
+ IFLA_GSO_MAX_SEGS, max_segs);
+ } else if (strcmp(*argv, "gro_max_size") == 0) {
+ unsigned int max_size;
+
+ NEXT_ARG();
+ if (get_unsigned(&max_size, *argv, 0))
+ invarg("Invalid \"gro_max_size\" value\n",
+ *argv);
+ addattr32(&req->n, sizeof(*req),
+ IFLA_GRO_MAX_SIZE, max_size);
+ } else if (strcmp(*argv, "gso_ipv4_max_size") == 0) {
+ unsigned int max_size;
+
+ NEXT_ARG();
+ if (get_unsigned(&max_size, *argv, 0))
+ invarg("Invalid \"gso_ipv4_max_size\" value\n",
+ *argv);
+ addattr32(&req->n, sizeof(*req),
+ IFLA_GSO_IPV4_MAX_SIZE, max_size);
+ } else if (strcmp(*argv, "gro_ipv4_max_size") == 0) {
+ unsigned int max_size;
+
+ NEXT_ARG();
+ if (get_unsigned(&max_size, *argv, 0))
+ invarg("Invalid \"gro_ipv4_max_size\" value\n",
+ *argv);
+ addattr32(&req->n, sizeof(*req),
+ IFLA_GRO_IPV4_MAX_SIZE, max_size);
+ } else if (strcmp(*argv, "parentdev") == 0) {
+ NEXT_ARG();
+ addattr_l(&req->n, sizeof(*req), IFLA_PARENT_DEV_NAME,
+ *argv, strlen(*argv) + 1);
+ } else {
+ if (matches(*argv, "help") == 0)
+ usage();
+
+ if (strcmp(*argv, "dev") == 0)
+ NEXT_ARG();
+ if (dev != name)
+ duparg2("dev", *argv);
+ if (check_altifname(*argv))
+ invarg("\"dev\" not a valid ifname", *argv);
+ dev = *argv;
+ }
+ argc--; argv++;
+ }
+
+ ret -= argc;
+
+ /* Allow "ip link add dev" and "ip link add name" */
+ if (!name)
+ name = dev;
+ else if (!dev)
+ dev = name;
+ else if (!strcmp(name, dev))
+ name = dev;
+
+ if (dev && addr_len &&
+ !(req->n.nlmsg_flags & NLM_F_CREATE)) {
+ int halen = nl_get_ll_addr_len(dev);
+
+ if (halen >= 0 && halen != addr_len) {
+ fprintf(stderr,
+ "Invalid address length %d - must be %d bytes\n",
+ addr_len, halen);
+ return -1;
+ }
+ }
+
+ if (index &&
+ (!(req->n.nlmsg_flags & NLM_F_CREATE) &&
+ !move_netns)) {
+ fprintf(stderr,
+ "index can be used only when creating devices or when moving device to another netns.\n");
+ exit(-1);
+ }
+
+ if (group != -1) {
+ if (!dev) {
+ if (argc) {
+ fprintf(stderr,
+ "Garbage instead of arguments \"%s ...\". Try \"ip link help\".\n",
+ *argv);
+ exit(-1);
+ }
+ if (req->n.nlmsg_flags & NLM_F_CREATE) {
+ fprintf(stderr,
+ "group cannot be used when creating devices.\n");
+ exit(-1);
+ }
+
+ *type = NULL;
+ return ret;
+ }
+ }
+
+ if (!(req->n.nlmsg_flags & NLM_F_CREATE)) {
+ if (!dev) {
+ fprintf(stderr,
+ "Not enough information: \"dev\" argument is required.\n");
+ exit(-1);
+ }
+
+ req->i.ifi_index = ll_name_to_index(dev);
+ if (!req->i.ifi_index)
+ return nodev(dev);
+
+ /* Not renaming to the same name */
+ if (name == dev)
+ name = NULL;
+
+ if (index)
+ addattr32(&req->n, sizeof(*req), IFLA_NEW_IFINDEX, index);
+ } else {
+ if (name != dev) {
+ fprintf(stderr,
+ "both \"name\" and \"dev\" cannot be used when creating devices.\n");
+ exit(-1);
+ }
+
+ if (link) {
+ int ifindex;
+
+ ifindex = ll_name_to_index(link);
+ if (!ifindex)
+ return nodev(link);
+ addattr32(&req->n, sizeof(*req), IFLA_LINK, ifindex);
+ }
+
+ req->i.ifi_index = index;
+ }
+
+ if (name) {
+ addattr_l(&req->n, sizeof(*req),
+ IFLA_IFNAME, name, strlen(name) + 1);
+ }
+
+ return ret;
+}
+
+static int iplink_modify(int cmd, unsigned int flags, int argc, char **argv)
+{
+ char *type = NULL;
+ struct iplink_req req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST | flags,
+ .n.nlmsg_type = cmd,
+ .i.ifi_family = preferred_family,
+ };
+ int ret;
+
+ ret = iplink_parse(argc, argv, &req, &type);
+ if (ret < 0)
+ return ret;
+
+ if (type) {
+ struct link_util *lu;
+ struct rtattr *linkinfo;
+ char *ulinep = strchr(type, '_');
+ int iflatype;
+
+ linkinfo = addattr_nest(&req.n, sizeof(req), IFLA_LINKINFO);
+ addattr_l(&req.n, sizeof(req), IFLA_INFO_KIND, type,
+ strlen(type));
+
+ lu = get_link_kind(type);
+ if (ulinep && !strcmp(ulinep, "_slave"))
+ iflatype = IFLA_INFO_SLAVE_DATA;
+ else
+ iflatype = IFLA_INFO_DATA;
+
+ argc -= ret;
+ argv += ret;
+
+ if (lu && lu->parse_opt && argc) {
+ struct rtattr *data;
+
+ data = addattr_nest(&req.n, sizeof(req), iflatype);
+
+ if (lu->parse_opt(lu, argc, argv, &req.n))
+ return -1;
+
+ addattr_nest_end(&req.n, data);
+ } else if (argc) {
+ if (matches(*argv, "help") == 0)
+ usage();
+ fprintf(stderr,
+ "Garbage instead of arguments \"%s ...\". Try \"ip link help\".\n",
+ *argv);
+ return -1;
+ }
+ addattr_nest_end(&req.n, linkinfo);
+ } else if (flags & NLM_F_CREATE) {
+ fprintf(stderr,
+ "Not enough information: \"type\" argument is required\n");
+ return -1;
+ }
+
+ if (echo_request)
+ ret = rtnl_echo_talk(&rth, &req.n, json, print_linkinfo);
+ else
+ ret = rtnl_talk(&rth, &req.n, NULL);
+
+ if (ret)
+ return -2;
+
+ /* remove device from cache; next use can refresh with new data */
+ ll_drop_by_index(req.i.ifi_index);
+
+ return 0;
+}
+
+int iplink_get(char *name, __u32 filt_mask)
+{
+ struct iplink_req req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_GETLINK,
+ .i.ifi_family = preferred_family,
+ };
+ struct nlmsghdr *answer;
+
+ if (name) {
+ addattr_l(&req.n, sizeof(req),
+ !check_ifname(name) ? IFLA_IFNAME : IFLA_ALT_IFNAME,
+ name, strlen(name) + 1);
+ }
+
+ if (!show_stats)
+ filt_mask |= RTEXT_FILTER_SKIP_STATS;
+ addattr32(&req.n, sizeof(req), IFLA_EXT_MASK, filt_mask);
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0)
+ return -2;
+
+ open_json_object(NULL);
+ print_linkinfo(answer, stdout);
+ close_json_object();
+
+ free(answer);
+ return 0;
+}
+
+
+void print_mpls_link_stats(FILE *fp, const struct mpls_link_stats *stats,
+ const char *indent)
+{
+ unsigned int cols[] = {
+ strlen("*X: bytes"),
+ strlen("packets"),
+ strlen("errors"),
+ strlen("dropped"),
+ strlen("noroute"),
+ };
+
+ if (is_json_context()) {
+ /* RX stats */
+ open_json_object("rx");
+ print_u64(PRINT_JSON, "bytes", NULL, stats->rx_bytes);
+ print_u64(PRINT_JSON, "packets", NULL, stats->rx_packets);
+ print_u64(PRINT_JSON, "errors", NULL, stats->rx_errors);
+ print_u64(PRINT_JSON, "dropped", NULL, stats->rx_dropped);
+ print_u64(PRINT_JSON, "noroute", NULL, stats->rx_noroute);
+ close_json_object();
+
+ /* TX stats */
+ open_json_object("tx");
+ print_u64(PRINT_JSON, "bytes", NULL, stats->tx_bytes);
+ print_u64(PRINT_JSON, "packets", NULL, stats->tx_packets);
+ print_u64(PRINT_JSON, "errors", NULL, stats->tx_errors);
+ print_u64(PRINT_JSON, "dropped", NULL, stats->tx_dropped);
+ close_json_object();
+ } else {
+ size_columns(cols, ARRAY_SIZE(cols), stats->rx_bytes,
+ stats->rx_packets, stats->rx_errors,
+ stats->rx_dropped, stats->rx_noroute);
+ size_columns(cols, ARRAY_SIZE(cols), stats->tx_bytes,
+ stats->tx_packets, stats->tx_errors,
+ stats->tx_dropped, 0);
+
+ fprintf(fp, "%sRX: %*s %*s %*s %*s %*s%s", indent,
+ cols[0] - 4, "bytes", cols[1], "packets",
+ cols[2], "errors", cols[3], "dropped",
+ cols[4], "noroute", _SL_);
+ fprintf(fp, "%s", indent);
+ print_num(fp, cols[0], stats->rx_bytes);
+ print_num(fp, cols[1], stats->rx_packets);
+ print_num(fp, cols[2], stats->rx_errors);
+ print_num(fp, cols[3], stats->rx_dropped);
+ print_num(fp, cols[4], stats->rx_noroute);
+ print_nl();
+
+ fprintf(fp, "%sTX: %*s %*s %*s %*s%s", indent,
+ cols[0] - 4, "bytes", cols[1], "packets",
+ cols[2], "errors", cols[3], "dropped", _SL_);
+ fprintf(fp, "%s", indent);
+ print_num(fp, cols[0], stats->tx_bytes);
+ print_num(fp, cols[1], stats->tx_packets);
+ print_num(fp, cols[2], stats->tx_errors);
+ print_num(fp, cols[3], stats->tx_dropped);
+ }
+}
+
+static void print_mpls_stats(FILE *fp, struct rtattr *attr)
+{
+ struct rtattr *mrtb[MPLS_STATS_MAX+1];
+ struct mpls_link_stats *stats;
+
+ parse_rtattr(mrtb, MPLS_STATS_MAX, RTA_DATA(attr),
+ RTA_PAYLOAD(attr));
+ if (!mrtb[MPLS_STATS_LINK])
+ return;
+
+ stats = RTA_DATA(mrtb[MPLS_STATS_LINK]);
+ print_string(PRINT_FP, NULL, " mpls:", NULL);
+ print_nl();
+ print_mpls_link_stats(fp, stats, " ");
+ print_string(PRINT_FP, NULL, "%s", "\n");
+ fflush(fp);
+}
+
+static void print_af_stats_attr(FILE *fp, int ifindex, struct rtattr *attr)
+{
+ bool if_printed = false;
+ struct rtattr *i;
+ int rem;
+
+ rem = RTA_PAYLOAD(attr);
+ for (i = RTA_DATA(attr); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+ if (preferred_family != AF_UNSPEC &&
+ i->rta_type != preferred_family)
+ continue;
+
+ if (!if_printed) {
+ print_uint(PRINT_ANY, "ifindex",
+ "%u:", ifindex);
+ print_color_string(PRINT_ANY, COLOR_IFNAME,
+ "ifname", "%s",
+ ll_index_to_name(ifindex));
+ print_nl();
+ if_printed = true;
+ }
+
+ switch (i->rta_type) {
+ case AF_MPLS:
+ print_mpls_stats(fp, i);
+ break;
+ default:
+ fprintf(stderr, " unknown af(%d)\n", i->rta_type);
+ break;
+ }
+ }
+}
+
+struct af_stats_ctx {
+ FILE *fp;
+ int ifindex;
+};
+
+static int print_af_stats(struct nlmsghdr *n, void *arg)
+{
+ struct if_stats_msg *ifsm = NLMSG_DATA(n);
+ struct rtattr *tb[IFLA_STATS_MAX+1];
+ int len = n->nlmsg_len;
+ struct af_stats_ctx *ctx = arg;
+ FILE *fp = ctx->fp;
+
+ len -= NLMSG_LENGTH(sizeof(*ifsm));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ if (ctx->ifindex && ifsm->ifindex != ctx->ifindex)
+ return 0;
+
+ parse_rtattr(tb, IFLA_STATS_MAX, IFLA_STATS_RTA(ifsm), len);
+
+ if (tb[IFLA_STATS_AF_SPEC])
+ print_af_stats_attr(fp, ifsm->ifindex, tb[IFLA_STATS_AF_SPEC]);
+
+ fflush(fp);
+ return 0;
+}
+
+static int iplink_afstats(int argc, char **argv)
+{
+ __u32 filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_AF_SPEC);
+ const char *filter_dev = NULL;
+ struct af_stats_ctx ctx = {
+ .fp = stdout,
+ .ifindex = 0,
+ };
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (filter_dev)
+ duparg2("dev", *argv);
+ filter_dev = *argv;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ fprintf(stderr,
+ "Command \"%s\" is unknown, try \"ip link help\".\n",
+ *argv);
+ exit(-1);
+ }
+
+ argv++; argc--;
+ }
+
+ if (filter_dev) {
+ ctx.ifindex = ll_name_to_index(filter_dev);
+ if (ctx.ifindex <= 0) {
+ fprintf(stderr,
+ "Device \"%s\" does not exist.\n",
+ filter_dev);
+ return -1;
+ }
+ }
+
+ new_json_obj(json);
+
+ if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask,
+ NULL, NULL) < 0) {
+ perror("Cannot send dump request");
+ delete_json_obj();
+ return 1;
+ }
+
+ if (rtnl_dump_filter(&rth, print_af_stats, &ctx) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ delete_json_obj();
+ return 1;
+ }
+
+ delete_json_obj();
+ return 0;
+}
+
+static int iplink_prop_mod(int argc, char **argv, struct iplink_req *req)
+{
+ struct rtattr *proplist;
+ char *dev = NULL;
+ char *name;
+
+ proplist = addattr_nest(&req->n, sizeof(*req),
+ IFLA_PROP_LIST | NLA_F_NESTED);
+
+ while (argc > 0) {
+ if (matches(*argv, "altname") == 0) {
+ NEXT_ARG();
+ if (check_altifname(*argv))
+ invarg("not a valid altname", *argv);
+ name = *argv;
+ addattr_l(&req->n, sizeof(*req), IFLA_ALT_IFNAME,
+ name, strlen(name) + 1);
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ if (strcmp(*argv, "dev") == 0)
+ NEXT_ARG();
+ if (dev)
+ duparg2("dev", *argv);
+ if (check_altifname(*argv))
+ invarg("\"dev\" not a valid ifname", *argv);
+ dev = *argv;
+ }
+ argv++; argc--;
+ }
+ addattr_nest_end(&req->n, proplist);
+
+ if (!dev) {
+ fprintf(stderr, "Not enough of information: \"dev\" argument is required.\n");
+ exit(-1);
+ }
+
+ req->i.ifi_index = ll_name_to_index(dev);
+ if (!req->i.ifi_index)
+ return nodev(dev);
+
+ if (rtnl_talk(&rth, &req->n, NULL) < 0)
+ return -2;
+
+ return 0;
+}
+
+static int iplink_prop(int argc, char **argv)
+{
+ struct iplink_req req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .i.ifi_family = preferred_family,
+ };
+
+ if (argc <= 0) {
+ usage();
+ exit(-1);
+ }
+
+ if (matches(*argv, "add") == 0) {
+ req.n.nlmsg_flags |= NLM_F_EXCL | NLM_F_CREATE | NLM_F_APPEND;
+ req.n.nlmsg_type = RTM_NEWLINKPROP;
+ } else if (matches(*argv, "del") == 0) {
+ req.n.nlmsg_type = RTM_DELLINKPROP;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ fprintf(stderr, "Operator required\n");
+ exit(-1);
+ }
+ return iplink_prop_mod(argc - 1, argv + 1, &req);
+}
+
+static void do_help(int argc, char **argv)
+{
+ struct link_util *lu = NULL;
+
+ if (argc <= 0) {
+ usage();
+ return;
+ }
+
+ lu = get_link_kind(*argv);
+ if (lu && lu->print_help)
+ lu->print_help(lu, argc-1, argv+1, stdout);
+ else
+ usage();
+}
+
+int do_iplink(int argc, char **argv)
+{
+ if (argc < 1)
+ return ipaddr_list_link(0, NULL);
+
+ if (matches(*argv, "add") == 0)
+ return iplink_modify(RTM_NEWLINK,
+ NLM_F_CREATE|NLM_F_EXCL,
+ argc-1, argv+1);
+ if (matches(*argv, "set") == 0 ||
+ matches(*argv, "change") == 0)
+ return iplink_modify(RTM_NEWLINK, 0,
+ argc-1, argv+1);
+ if (matches(*argv, "replace") == 0)
+ return iplink_modify(RTM_NEWLINK,
+ NLM_F_CREATE|NLM_F_REPLACE,
+ argc-1, argv+1);
+ if (matches(*argv, "delete") == 0)
+ return iplink_modify(RTM_DELLINK, 0,
+ argc-1, argv+1);
+ if (matches(*argv, "show") == 0 ||
+ matches(*argv, "lst") == 0 ||
+ matches(*argv, "list") == 0)
+ return ipaddr_list_link(argc-1, argv+1);
+
+ if (matches(*argv, "xstats") == 0)
+ return iplink_ifla_xstats(argc-1, argv+1);
+
+ if (matches(*argv, "afstats") == 0) {
+ iplink_afstats(argc-1, argv+1);
+ return 0;
+ }
+
+ if (matches(*argv, "property") == 0)
+ return iplink_prop(argc-1, argv+1);
+
+ if (matches(*argv, "help") == 0) {
+ do_help(argc-1, argv+1);
+ return 0;
+ }
+
+ fprintf(stderr, "Command \"%s\" is unknown, try \"ip link help\".\n",
+ *argv);
+ exit(-1);
+}
diff --git a/ip/iplink_amt.c b/ip/iplink_amt.c
new file mode 100644
index 0000000..3a35bd9
--- /dev/null
+++ b/ip/iplink_amt.c
@@ -0,0 +1,196 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iplink_amt.c AMT device support
+ *
+ * Authors: Taehee Yoo <ap420073@gmail.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <net/if.h>
+#include <linux/ip.h>
+#include <linux/if_link.h>
+#include <arpa/inet.h>
+#include <linux/amt.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+#define AMT_ATTRSET(attrs, type) (((attrs) & (1L << (type))) != 0)
+
+static void print_usage(FILE *f)
+{
+ fprintf(f,
+ "Usage: ... amt\n"
+ " [ discovery IP_ADDRESS ]\n"
+ " [ mode MODE ]\n"
+ " [ local ADDR ]\n"
+ " [ dev PHYS_DEV ]\n"
+ " [ relay_port PORT ]\n"
+ " [ gateway_port PORT ]\n"
+ " [ max_tunnels NUMBER ]\n"
+ "\n"
+ "Where: ADDR := { IP_ADDRESS }\n"
+ " MODE := { gateway | relay }\n"
+ );
+}
+
+static char *modename[] = {"gateway", "relay"};
+
+static void usage(void)
+{
+ print_usage(stderr);
+}
+
+static void check_duparg(__u64 *attrs, int type, const char *key,
+ const char *argv)
+{
+ if (!AMT_ATTRSET(*attrs, type)) {
+ *attrs |= (1L << type);
+ return;
+ }
+ duparg2(key, argv);
+}
+
+static int amt_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ unsigned int mode, max_tunnels;
+ inet_prefix saddr, daddr;
+ __u64 attrs = 0;
+ __u16 port;
+
+ saddr.family = daddr.family = AF_UNSPEC;
+
+ inet_prefix_reset(&saddr);
+ inet_prefix_reset(&daddr);
+
+ while (argc > 0) {
+ if (strcmp(*argv, "mode") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "gateway") == 0) {
+ mode = 0;
+ } else if (strcmp(*argv, "relay") == 0) {
+ mode = 1;
+ } else {
+ usage();
+ return -1;
+ }
+ addattr32(n, 1024, IFLA_AMT_MODE, mode);
+ } else if (strcmp(*argv, "relay_port") == 0) {
+ NEXT_ARG();
+ if (get_u16(&port, *argv, 0))
+ invarg("relay_port", *argv);
+ addattr16(n, 1024, IFLA_AMT_RELAY_PORT, htons(port));
+ } else if (strcmp(*argv, "gateway_port") == 0) {
+ NEXT_ARG();
+ if (get_u16(&port, *argv, 0))
+ invarg("gateway_port", *argv);
+ addattr16(n, 1024, IFLA_AMT_GATEWAY_PORT, htons(port));
+ } else if (strcmp(*argv, "max_tunnels") == 0) {
+ NEXT_ARG();
+ if (get_u32(&max_tunnels, *argv, 0))
+ invarg("max_tunnels", *argv);
+ addattr32(n, 1024, IFLA_AMT_MAX_TUNNELS, max_tunnels);
+ } else if (strcmp(*argv, "dev") == 0) {
+ unsigned int link;
+
+ NEXT_ARG();
+ link = ll_name_to_index(*argv);
+ if (!link)
+ exit(nodev(*argv));
+ addattr32(n, 1024, IFLA_AMT_LINK, link);
+ } else if (strcmp(*argv, "local") == 0) {
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_AMT_LOCAL_IP, "local", *argv);
+ get_addr(&saddr, *argv, daddr.family);
+
+ if (is_addrtype_inet(&saddr))
+ addattr_l(n, 1024, IFLA_AMT_LOCAL_IP,
+ saddr.data, saddr.bytelen);
+ } else if (strcmp(*argv, "discovery") == 0) {
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_AMT_DISCOVERY_IP,
+ "discovery", *argv);
+ get_addr(&daddr, *argv, daddr.family);
+ if (is_addrtype_inet(&daddr))
+ addattr_l(n, 1024, IFLA_AMT_DISCOVERY_IP,
+ daddr.data, daddr.bytelen);
+ } else if (strcmp(*argv, "help") == 0) {
+ usage();
+ return -1;
+ } else {
+ fprintf(stderr, "amt: unknown command \"%s\"?\n", *argv);
+ usage();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ return 0;
+}
+
+static void amt_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ if (!tb)
+ return;
+
+ if (tb[IFLA_AMT_MODE])
+ print_string(PRINT_ANY, "mode", "%s ",
+ modename[rta_getattr_u32(tb[IFLA_AMT_MODE])]);
+
+ if (tb[IFLA_AMT_GATEWAY_PORT])
+ print_uint(PRINT_ANY, "gateway_port", "gateway_port %u ",
+ rta_getattr_be16(tb[IFLA_AMT_GATEWAY_PORT]));
+
+ if (tb[IFLA_AMT_RELAY_PORT])
+ print_uint(PRINT_ANY, "relay_port", "relay_port %u ",
+ rta_getattr_be16(tb[IFLA_AMT_RELAY_PORT]));
+
+ if (tb[IFLA_AMT_LOCAL_IP]) {
+ __be32 addr = rta_getattr_u32(tb[IFLA_AMT_LOCAL_IP]);
+
+ print_string(PRINT_ANY, "local", "local %s ",
+ format_host(AF_INET, 4, &addr));
+ }
+
+ if (tb[IFLA_AMT_REMOTE_IP]) {
+ __be32 addr = rta_getattr_u32(tb[IFLA_AMT_REMOTE_IP]);
+
+ print_string(PRINT_ANY, "remote", "remote %s ",
+ format_host(AF_INET, 4, &addr));
+ }
+
+ if (tb[IFLA_AMT_DISCOVERY_IP]) {
+ __be32 addr = rta_getattr_u32(tb[IFLA_AMT_DISCOVERY_IP]);
+
+ print_string(PRINT_ANY, "discovery", "discovery %s ",
+ format_host(AF_INET, 4, &addr));
+ }
+
+ if (tb[IFLA_AMT_LINK]) {
+ unsigned int link = rta_getattr_u32(tb[IFLA_AMT_LINK]);
+
+ print_string(PRINT_ANY, "link", "dev %s ",
+ ll_index_to_name(link));
+ }
+
+ if (tb[IFLA_AMT_MAX_TUNNELS])
+ print_uint(PRINT_ANY, "max_tunnels", "max_tunnels %u ",
+ rta_getattr_u32(tb[IFLA_AMT_MAX_TUNNELS]));
+}
+
+static void amt_print_help(struct link_util *lu, int argc, char **argv, FILE *f)
+{
+ print_usage(f);
+}
+
+struct link_util amt_link_util = {
+ .id = "amt",
+ .maxattr = IFLA_AMT_MAX,
+ .parse_opt = amt_parse_opt,
+ .print_opt = amt_print_opt,
+ .print_help = amt_print_help,
+};
diff --git a/ip/iplink_bareudp.c b/ip/iplink_bareudp.c
new file mode 100644
index 0000000..aa31110
--- /dev/null
+++ b/ip/iplink_bareudp.c
@@ -0,0 +1,152 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <stdio.h>
+#include <linux/if_ether.h>
+#include <linux/if_link.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include "libnetlink.h"
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+#define BAREUDP_ATTRSET(attrs, type) (((attrs) & (1L << (type))) != 0)
+
+static void print_explain(FILE *f)
+{
+ fprintf(f,
+ "Usage: ... bareudp dstport PORT\n"
+ " ethertype PROTO\n"
+ " [ srcportmin PORT ]\n"
+ " [ [no]multiproto ]\n"
+ "\n"
+ "Where: PORT := UDP_PORT\n"
+ " PROTO := ETHERTYPE\n"
+ "\n"
+ "Note: ETHERTYPE can be given as number or as protocol name (\"ipv4\", \"ipv6\",\n"
+ " \"mpls_uc\", etc.).\n"
+ );
+}
+
+static void explain(void)
+{
+ print_explain(stderr);
+}
+
+static void check_duparg(__u64 *attrs, int type, const char *key,
+ const char *argv)
+{
+ if (!BAREUDP_ATTRSET(*attrs, type)) {
+ *attrs |= (1L << type);
+ return;
+ }
+ duparg2(key, argv);
+}
+
+static int bareudp_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ bool multiproto = false;
+ __u16 srcportmin = 0;
+ __be16 ethertype = 0;
+ __be16 dstport = 0;
+ __u64 attrs = 0;
+
+ while (argc > 0) {
+ if (matches(*argv, "dstport") == 0) {
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_BAREUDP_PORT, "dstport",
+ *argv);
+ if (get_be16(&dstport, *argv, 0))
+ invarg("dstport", *argv);
+ } else if (matches(*argv, "ethertype") == 0) {
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_BAREUDP_ETHERTYPE,
+ "ethertype", *argv);
+ if (ll_proto_a2n(&ethertype, *argv))
+ invarg("ethertype", *argv);
+ } else if (matches(*argv, "srcportmin") == 0) {
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_BAREUDP_SRCPORT_MIN,
+ "srcportmin", *argv);
+ if (get_u16(&srcportmin, *argv, 0))
+ invarg("srcportmin", *argv);
+ } else if (matches(*argv, "multiproto") == 0) {
+ check_duparg(&attrs, IFLA_BAREUDP_MULTIPROTO_MODE,
+ *argv, *argv);
+ multiproto = true;
+ } else if (matches(*argv, "nomultiproto") == 0) {
+ check_duparg(&attrs, IFLA_BAREUDP_MULTIPROTO_MODE,
+ *argv, *argv);
+ multiproto = false;
+ } else if (matches(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "bareudp: unknown command \"%s\"?\n",
+ *argv);
+ explain();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ if (!BAREUDP_ATTRSET(attrs, IFLA_BAREUDP_PORT))
+ missarg("dstport");
+ if (!BAREUDP_ATTRSET(attrs, IFLA_BAREUDP_ETHERTYPE))
+ missarg("ethertype");
+
+ addattr16(n, 1024, IFLA_BAREUDP_PORT, dstport);
+ addattr16(n, 1024, IFLA_BAREUDP_ETHERTYPE, ethertype);
+ if (BAREUDP_ATTRSET(attrs, IFLA_BAREUDP_SRCPORT_MIN))
+ addattr16(n, 1024, IFLA_BAREUDP_SRCPORT_MIN, srcportmin);
+ if (multiproto)
+ addattr(n, 1024, IFLA_BAREUDP_MULTIPROTO_MODE);
+
+ return 0;
+}
+
+static void bareudp_print_opt(struct link_util *lu, FILE *f,
+ struct rtattr *tb[])
+{
+ if (!tb)
+ return;
+
+ if (tb[IFLA_BAREUDP_PORT])
+ print_uint(PRINT_ANY, "dstport", "dstport %u ",
+ rta_getattr_be16(tb[IFLA_BAREUDP_PORT]));
+
+ if (tb[IFLA_BAREUDP_ETHERTYPE]) {
+ struct rtattr *attr = tb[IFLA_BAREUDP_ETHERTYPE];
+ SPRINT_BUF(ethertype);
+
+ print_string(PRINT_ANY, "ethertype", "ethertype %s ",
+ ll_proto_n2a(rta_getattr_u16(attr),
+ ethertype, sizeof(ethertype)));
+ }
+
+ if (tb[IFLA_BAREUDP_SRCPORT_MIN])
+ print_uint(PRINT_ANY, "srcportmin", "srcportmin %u ",
+ rta_getattr_u16(tb[IFLA_BAREUDP_SRCPORT_MIN]));
+
+ if (tb[IFLA_BAREUDP_MULTIPROTO_MODE])
+ print_bool(PRINT_ANY, "multiproto", "multiproto ", true);
+ else
+ print_bool(PRINT_ANY, "multiproto", "nomultiproto ", false);
+}
+
+static void bareudp_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ print_explain(f);
+}
+
+struct link_util bareudp_link_util = {
+ .id = "bareudp",
+ .maxattr = IFLA_BAREUDP_MAX,
+ .parse_opt = bareudp_parse_opt,
+ .print_opt = bareudp_print_opt,
+ .print_help = bareudp_print_help,
+};
diff --git a/ip/iplink_batadv.c b/ip/iplink_batadv.c
new file mode 100644
index 0000000..27d3167
--- /dev/null
+++ b/ip/iplink_batadv.c
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iplink_batadv.c Batman-adv support
+ *
+ * Authors: Nicolas Escande <nico.escande@gmail.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/batman_adv.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_explain(FILE *f)
+{
+ fprintf(f,
+ "Usage: ... batadv [ ra ROUTING_ALG ]\n"
+ "\n"
+ "Where: ROUTING_ALG := { BATMAN_IV | BATMAN_V }\n"
+ );
+}
+
+static void explain(void)
+{
+ print_explain(stderr);
+}
+
+static int batadv_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ while (argc > 0) {
+ if (matches(*argv, "ra") == 0) {
+ NEXT_ARG();
+ addattrstrz(n, 1024, IFLA_BATADV_ALGO_NAME, *argv);
+ } else if (matches(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr,
+ "batadv: unknown command \"%s\"?\n",
+ *argv);
+ explain();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ return 0;
+}
+
+static void batadv_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ print_explain(f);
+}
+
+struct link_util batadv_link_util = {
+ .id = "batadv",
+ .maxattr = IFLA_BATADV_MAX,
+ .parse_opt = batadv_parse_opt,
+ .print_help = batadv_print_help,
+};
diff --git a/ip/iplink_bond.c b/ip/iplink_bond.c
new file mode 100644
index 0000000..214244d
--- /dev/null
+++ b/ip/iplink_bond.c
@@ -0,0 +1,964 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iplink_bond.c Bonding device support
+ *
+ * Authors: Jiri Pirko <jiri@resnulli.us>
+ * Scott Feldman <sfeldma@cumulusnetworks.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/if_bonding.h>
+
+#include "list.h"
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+#define BOND_MAX_ARP_TARGETS 16
+#define BOND_MAX_NS_TARGETS BOND_MAX_ARP_TARGETS
+
+static unsigned int xstats_print_attr;
+static int filter_index;
+
+static const char *mode_tbl[] = {
+ "balance-rr",
+ "active-backup",
+ "balance-xor",
+ "broadcast",
+ "802.3ad",
+ "balance-tlb",
+ "balance-alb",
+ NULL,
+};
+
+static const char *arp_validate_tbl[] = {
+ "none",
+ "active",
+ "backup",
+ "all",
+ "filter",
+ "filter_active",
+ "filter_backup",
+ NULL,
+};
+
+static const char *arp_all_targets_tbl[] = {
+ "any",
+ "all",
+ NULL,
+};
+
+static const char *primary_reselect_tbl[] = {
+ "always",
+ "better",
+ "failure",
+ NULL,
+};
+
+static const char *fail_over_mac_tbl[] = {
+ "none",
+ "active",
+ "follow",
+ NULL,
+};
+
+static const char *xmit_hash_policy_tbl[] = {
+ "layer2",
+ "layer3+4",
+ "layer2+3",
+ "encap2+3",
+ "encap3+4",
+ "vlan+srcmac",
+ NULL,
+};
+
+static const char *lacp_active_tbl[] = {
+ "off",
+ "on",
+ NULL,
+};
+
+static const char *lacp_rate_tbl[] = {
+ "slow",
+ "fast",
+ NULL,
+};
+
+static const char *ad_select_tbl[] = {
+ "stable",
+ "bandwidth",
+ "count",
+ NULL,
+};
+
+static const char *get_name(const char **tbl, int index)
+{
+ int i;
+
+ for (i = 0; tbl[i]; i++)
+ if (i == index)
+ return tbl[i];
+
+ return "UNKNOWN";
+}
+
+static int get_index(const char **tbl, char *name)
+{
+ int i, index;
+
+ /* check for integer index passed in instead of name */
+ if (get_integer(&index, name, 10) == 0)
+ for (i = 0; tbl[i]; i++)
+ if (i == index)
+ return i;
+
+ for (i = 0; tbl[i]; i++)
+ if (strcmp(tbl[i], name) == 0)
+ return i;
+
+ return -1;
+}
+
+static void print_explain(FILE *f)
+{
+ fprintf(f,
+ "Usage: ... bond [ mode BONDMODE ] [ active_slave SLAVE_DEV ]\n"
+ " [ clear_active_slave ] [ miimon MIIMON ]\n"
+ " [ updelay UPDELAY ] [ downdelay DOWNDELAY ]\n"
+ " [ peer_notify_delay DELAY ]\n"
+ " [ use_carrier USE_CARRIER ]\n"
+ " [ arp_interval ARP_INTERVAL ]\n"
+ " [ arp_validate ARP_VALIDATE ]\n"
+ " [ arp_all_targets ARP_ALL_TARGETS ]\n"
+ " [ arp_ip_target [ ARP_IP_TARGET, ... ] ]\n"
+ " [ ns_ip6_target [ NS_IP6_TARGET, ... ] ]\n"
+ " [ primary SLAVE_DEV ]\n"
+ " [ primary_reselect PRIMARY_RESELECT ]\n"
+ " [ fail_over_mac FAIL_OVER_MAC ]\n"
+ " [ xmit_hash_policy XMIT_HASH_POLICY ]\n"
+ " [ resend_igmp RESEND_IGMP ]\n"
+ " [ num_grat_arp|num_unsol_na NUM_GRAT_ARP|NUM_UNSOL_NA ]\n"
+ " [ all_slaves_active ALL_SLAVES_ACTIVE ]\n"
+ " [ min_links MIN_LINKS ]\n"
+ " [ lp_interval LP_INTERVAL ]\n"
+ " [ packets_per_slave PACKETS_PER_SLAVE ]\n"
+ " [ tlb_dynamic_lb TLB_DYNAMIC_LB ]\n"
+ " [ lacp_rate LACP_RATE ]\n"
+ " [ lacp_active LACP_ACTIVE]\n"
+ " [ ad_select AD_SELECT ]\n"
+ " [ ad_user_port_key PORTKEY ]\n"
+ " [ ad_actor_sys_prio SYSPRIO ]\n"
+ " [ ad_actor_system LLADDR ]\n"
+ " [ arp_missed_max MISSED_MAX ]\n"
+ "\n"
+ "BONDMODE := balance-rr|active-backup|balance-xor|broadcast|802.3ad|balance-tlb|balance-alb\n"
+ "ARP_VALIDATE := none|active|backup|all|filter|filter_active|filter_backup\n"
+ "ARP_ALL_TARGETS := any|all\n"
+ "PRIMARY_RESELECT := always|better|failure\n"
+ "FAIL_OVER_MAC := none|active|follow\n"
+ "XMIT_HASH_POLICY := layer2|layer2+3|layer3+4|encap2+3|encap3+4|vlan+srcmac\n"
+ "LACP_ACTIVE := off|on\n"
+ "LACP_RATE := slow|fast\n"
+ "AD_SELECT := stable|bandwidth|count\n"
+ );
+}
+
+static void explain(void)
+{
+ print_explain(stderr);
+}
+
+static int bond_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ __u8 mode, use_carrier, primary_reselect, fail_over_mac;
+ __u8 xmit_hash_policy, num_peer_notif, all_slaves_active;
+ __u8 lacp_active, lacp_rate, ad_select, tlb_dynamic_lb;
+ __u16 ad_user_port_key, ad_actor_sys_prio;
+ __u32 miimon, updelay, downdelay, peer_notify_delay, arp_interval, arp_validate;
+ __u32 arp_all_targets, resend_igmp, min_links, lp_interval;
+ __u32 packets_per_slave;
+ __u8 missed_max;
+ unsigned int ifindex;
+
+ while (argc > 0) {
+ if (matches(*argv, "mode") == 0) {
+ NEXT_ARG();
+ if (get_index(mode_tbl, *argv) < 0)
+ invarg("invalid mode", *argv);
+ mode = get_index(mode_tbl, *argv);
+ addattr8(n, 1024, IFLA_BOND_MODE, mode);
+ } else if (matches(*argv, "active_slave") == 0) {
+ NEXT_ARG();
+ ifindex = ll_name_to_index(*argv);
+ if (!ifindex)
+ return nodev(*argv);
+ addattr32(n, 1024, IFLA_BOND_ACTIVE_SLAVE, ifindex);
+ } else if (matches(*argv, "clear_active_slave") == 0) {
+ addattr32(n, 1024, IFLA_BOND_ACTIVE_SLAVE, 0);
+ } else if (matches(*argv, "miimon") == 0) {
+ NEXT_ARG();
+ if (get_u32(&miimon, *argv, 0))
+ invarg("invalid miimon", *argv);
+ addattr32(n, 1024, IFLA_BOND_MIIMON, miimon);
+ } else if (matches(*argv, "updelay") == 0) {
+ NEXT_ARG();
+ if (get_u32(&updelay, *argv, 0))
+ invarg("invalid updelay", *argv);
+ addattr32(n, 1024, IFLA_BOND_UPDELAY, updelay);
+ } else if (matches(*argv, "downdelay") == 0) {
+ NEXT_ARG();
+ if (get_u32(&downdelay, *argv, 0))
+ invarg("invalid downdelay", *argv);
+ addattr32(n, 1024, IFLA_BOND_DOWNDELAY, downdelay);
+ } else if (matches(*argv, "peer_notify_delay") == 0) {
+ NEXT_ARG();
+ if (get_u32(&peer_notify_delay, *argv, 0))
+ invarg("invalid peer_notify_delay", *argv);
+ addattr32(n, 1024, IFLA_BOND_PEER_NOTIF_DELAY, peer_notify_delay);
+ } else if (matches(*argv, "use_carrier") == 0) {
+ NEXT_ARG();
+ if (get_u8(&use_carrier, *argv, 0))
+ invarg("invalid use_carrier", *argv);
+ addattr8(n, 1024, IFLA_BOND_USE_CARRIER, use_carrier);
+ } else if (matches(*argv, "arp_interval") == 0) {
+ NEXT_ARG();
+ if (get_u32(&arp_interval, *argv, 0))
+ invarg("invalid arp_interval", *argv);
+ addattr32(n, 1024, IFLA_BOND_ARP_INTERVAL, arp_interval);
+ } else if (matches(*argv, "arp_ip_target") == 0) {
+ struct rtattr *nest = addattr_nest(n, 1024,
+ IFLA_BOND_ARP_IP_TARGET);
+ if (NEXT_ARG_OK()) {
+ NEXT_ARG();
+ char *targets = strdupa(*argv);
+ char *target = strtok(targets, ",");
+ int i;
+
+ for (i = 0; target && i < BOND_MAX_ARP_TARGETS; i++) {
+ __u32 addr = get_addr32(target);
+
+ addattr32(n, 1024, i, addr);
+ target = strtok(NULL, ",");
+ }
+ addattr_nest_end(n, nest);
+ }
+ addattr_nest_end(n, nest);
+ } else if (strcmp(*argv, "ns_ip6_target") == 0) {
+ struct rtattr *nest = addattr_nest(n, 1024,
+ IFLA_BOND_NS_IP6_TARGET);
+ if (NEXT_ARG_OK()) {
+ NEXT_ARG();
+ char *targets = strdupa(*argv);
+ char *target = strtok(targets, ",");
+ int i;
+
+ for (i = 0; target && i < BOND_MAX_NS_TARGETS; i++) {
+ inet_prefix ip6_addr;
+
+ get_addr(&ip6_addr, target, AF_INET6);
+ addattr_l(n, 1024, i, ip6_addr.data, sizeof(struct in6_addr));
+ target = strtok(NULL, ",");
+ }
+ addattr_nest_end(n, nest);
+ }
+ addattr_nest_end(n, nest);
+ } else if (matches(*argv, "arp_validate") == 0) {
+ NEXT_ARG();
+ if (get_index(arp_validate_tbl, *argv) < 0)
+ invarg("invalid arp_validate", *argv);
+ arp_validate = get_index(arp_validate_tbl, *argv);
+ addattr32(n, 1024, IFLA_BOND_ARP_VALIDATE, arp_validate);
+ } else if (matches(*argv, "arp_all_targets") == 0) {
+ NEXT_ARG();
+ if (get_index(arp_all_targets_tbl, *argv) < 0)
+ invarg("invalid arp_all_targets", *argv);
+ arp_all_targets = get_index(arp_all_targets_tbl, *argv);
+ addattr32(n, 1024, IFLA_BOND_ARP_ALL_TARGETS, arp_all_targets);
+ } else if (strcmp(*argv, "arp_missed_max") == 0) {
+ NEXT_ARG();
+ if (get_u8(&missed_max, *argv, 0))
+ invarg("invalid arp_missed_max", *argv);
+
+ addattr8(n, 1024, IFLA_BOND_MISSED_MAX, missed_max);
+ } else if (matches(*argv, "primary") == 0) {
+ NEXT_ARG();
+ ifindex = ll_name_to_index(*argv);
+ if (!ifindex)
+ return nodev(*argv);
+ addattr32(n, 1024, IFLA_BOND_PRIMARY, ifindex);
+ } else if (matches(*argv, "primary_reselect") == 0) {
+ NEXT_ARG();
+ if (get_index(primary_reselect_tbl, *argv) < 0)
+ invarg("invalid primary_reselect", *argv);
+ primary_reselect = get_index(primary_reselect_tbl, *argv);
+ addattr8(n, 1024, IFLA_BOND_PRIMARY_RESELECT,
+ primary_reselect);
+ } else if (matches(*argv, "fail_over_mac") == 0) {
+ NEXT_ARG();
+ if (get_index(fail_over_mac_tbl, *argv) < 0)
+ invarg("invalid fail_over_mac", *argv);
+ fail_over_mac = get_index(fail_over_mac_tbl, *argv);
+ addattr8(n, 1024, IFLA_BOND_FAIL_OVER_MAC,
+ fail_over_mac);
+ } else if (matches(*argv, "xmit_hash_policy") == 0) {
+ NEXT_ARG();
+ if (get_index(xmit_hash_policy_tbl, *argv) < 0)
+ invarg("invalid xmit_hash_policy", *argv);
+
+ xmit_hash_policy = get_index(xmit_hash_policy_tbl, *argv);
+ addattr8(n, 1024, IFLA_BOND_XMIT_HASH_POLICY,
+ xmit_hash_policy);
+ } else if (matches(*argv, "resend_igmp") == 0) {
+ NEXT_ARG();
+ if (get_u32(&resend_igmp, *argv, 0))
+ invarg("invalid resend_igmp", *argv);
+
+ addattr32(n, 1024, IFLA_BOND_RESEND_IGMP, resend_igmp);
+ } else if (matches(*argv, "num_grat_arp") == 0 ||
+ matches(*argv, "num_unsol_na") == 0) {
+ NEXT_ARG();
+ if (get_u8(&num_peer_notif, *argv, 0))
+ invarg("invalid num_grat_arp|num_unsol_na",
+ *argv);
+
+ addattr8(n, 1024, IFLA_BOND_NUM_PEER_NOTIF,
+ num_peer_notif);
+ } else if (matches(*argv, "all_slaves_active") == 0) {
+ NEXT_ARG();
+ if (get_u8(&all_slaves_active, *argv, 0))
+ invarg("invalid all_slaves_active", *argv);
+
+ addattr8(n, 1024, IFLA_BOND_ALL_SLAVES_ACTIVE,
+ all_slaves_active);
+ } else if (matches(*argv, "min_links") == 0) {
+ NEXT_ARG();
+ if (get_u32(&min_links, *argv, 0))
+ invarg("invalid min_links", *argv);
+
+ addattr32(n, 1024, IFLA_BOND_MIN_LINKS, min_links);
+ } else if (matches(*argv, "lp_interval") == 0) {
+ NEXT_ARG();
+ if (get_u32(&lp_interval, *argv, 0))
+ invarg("invalid lp_interval", *argv);
+
+ addattr32(n, 1024, IFLA_BOND_LP_INTERVAL, lp_interval);
+ } else if (matches(*argv, "packets_per_slave") == 0) {
+ NEXT_ARG();
+ if (get_u32(&packets_per_slave, *argv, 0))
+ invarg("invalid packets_per_slave", *argv);
+
+ addattr32(n, 1024, IFLA_BOND_PACKETS_PER_SLAVE,
+ packets_per_slave);
+ } else if (matches(*argv, "lacp_rate") == 0) {
+ NEXT_ARG();
+ if (get_index(lacp_rate_tbl, *argv) < 0)
+ invarg("invalid lacp_rate", *argv);
+
+ lacp_rate = get_index(lacp_rate_tbl, *argv);
+ addattr8(n, 1024, IFLA_BOND_AD_LACP_RATE, lacp_rate);
+ } else if (strcmp(*argv, "lacp_active") == 0) {
+ NEXT_ARG();
+ if (get_index(lacp_active_tbl, *argv) < 0)
+ invarg("invalid lacp_active", *argv);
+
+ lacp_active = get_index(lacp_active_tbl, *argv);
+ addattr8(n, 1024, IFLA_BOND_AD_LACP_ACTIVE, lacp_active);
+ } else if (matches(*argv, "ad_select") == 0) {
+ NEXT_ARG();
+ if (get_index(ad_select_tbl, *argv) < 0)
+ invarg("invalid ad_select", *argv);
+
+ ad_select = get_index(ad_select_tbl, *argv);
+ addattr8(n, 1024, IFLA_BOND_AD_SELECT, ad_select);
+ } else if (matches(*argv, "ad_user_port_key") == 0) {
+ NEXT_ARG();
+ if (get_u16(&ad_user_port_key, *argv, 0))
+ invarg("invalid ad_user_port_key", *argv);
+
+ addattr16(n, 1024, IFLA_BOND_AD_USER_PORT_KEY,
+ ad_user_port_key);
+ } else if (matches(*argv, "ad_actor_sys_prio") == 0) {
+ NEXT_ARG();
+ if (get_u16(&ad_actor_sys_prio, *argv, 0))
+ invarg("invalid ad_actor_sys_prio", *argv);
+
+ addattr16(n, 1024, IFLA_BOND_AD_ACTOR_SYS_PRIO,
+ ad_actor_sys_prio);
+ } else if (matches(*argv, "ad_actor_system") == 0) {
+ int len;
+ char abuf[32];
+
+ NEXT_ARG();
+ len = ll_addr_a2n(abuf, sizeof(abuf), *argv);
+ if (len < 0)
+ return -1;
+ addattr_l(n, 1024, IFLA_BOND_AD_ACTOR_SYSTEM,
+ abuf, len);
+ } else if (matches(*argv, "tlb_dynamic_lb") == 0) {
+ NEXT_ARG();
+ if (get_u8(&tlb_dynamic_lb, *argv, 0)) {
+ invarg("invalid tlb_dynamic_lb", *argv);
+ return -1;
+ }
+ addattr8(n, 1024, IFLA_BOND_TLB_DYNAMIC_LB,
+ tlb_dynamic_lb);
+ } else if (matches(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "bond: unknown command \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ return 0;
+}
+
+static void bond_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ int i;
+
+ if (!tb)
+ return;
+
+ if (tb[IFLA_BOND_MODE]) {
+ const char *mode = get_name(mode_tbl,
+ rta_getattr_u8(tb[IFLA_BOND_MODE]));
+ print_string(PRINT_ANY, "mode", "mode %s ", mode);
+ }
+
+ if (tb[IFLA_BOND_ACTIVE_SLAVE]) {
+ unsigned int ifindex =
+ rta_getattr_u32(tb[IFLA_BOND_ACTIVE_SLAVE]);
+
+ if (ifindex) {
+ print_string(PRINT_ANY,
+ "active_slave",
+ "active_slave %s ",
+ ll_index_to_name(ifindex));
+ }
+ }
+
+ if (tb[IFLA_BOND_MIIMON])
+ print_uint(PRINT_ANY,
+ "miimon",
+ "miimon %u ",
+ rta_getattr_u32(tb[IFLA_BOND_MIIMON]));
+
+ if (tb[IFLA_BOND_UPDELAY])
+ print_uint(PRINT_ANY,
+ "updelay",
+ "updelay %u ",
+ rta_getattr_u32(tb[IFLA_BOND_UPDELAY]));
+
+ if (tb[IFLA_BOND_DOWNDELAY])
+ print_uint(PRINT_ANY,
+ "downdelay",
+ "downdelay %u ",
+ rta_getattr_u32(tb[IFLA_BOND_DOWNDELAY]));
+
+ if (tb[IFLA_BOND_PEER_NOTIF_DELAY])
+ print_uint(PRINT_ANY,
+ "peer_notify_delay",
+ "peer_notify_delay %u ",
+ rta_getattr_u32(tb[IFLA_BOND_PEER_NOTIF_DELAY]));
+
+ if (tb[IFLA_BOND_USE_CARRIER])
+ print_uint(PRINT_ANY,
+ "use_carrier",
+ "use_carrier %u ",
+ rta_getattr_u8(tb[IFLA_BOND_USE_CARRIER]));
+
+ if (tb[IFLA_BOND_ARP_INTERVAL])
+ print_uint(PRINT_ANY,
+ "arp_interval",
+ "arp_interval %u ",
+ rta_getattr_u32(tb[IFLA_BOND_ARP_INTERVAL]));
+
+ if (tb[IFLA_BOND_MISSED_MAX])
+ print_uint(PRINT_ANY,
+ "arp_missed_max",
+ "arp_missed_max %u ",
+ rta_getattr_u8(tb[IFLA_BOND_MISSED_MAX]));
+
+ if (tb[IFLA_BOND_ARP_IP_TARGET]) {
+ struct rtattr *iptb[BOND_MAX_ARP_TARGETS + 1];
+
+ parse_rtattr_nested(iptb, BOND_MAX_ARP_TARGETS,
+ tb[IFLA_BOND_ARP_IP_TARGET]);
+
+ if (iptb[0]) {
+ open_json_array(PRINT_JSON, "arp_ip_target");
+ print_string(PRINT_FP, NULL, "arp_ip_target ", NULL);
+ }
+
+ for (i = 0; i < BOND_MAX_ARP_TARGETS; i++) {
+ if (iptb[i])
+ print_string(PRINT_ANY,
+ NULL,
+ "%s",
+ rt_addr_n2a_rta(AF_INET, iptb[i]));
+ if (!is_json_context()
+ && i < BOND_MAX_ARP_TARGETS-1
+ && iptb[i+1])
+ fprintf(f, ",");
+ }
+
+ if (iptb[0]) {
+ print_string(PRINT_FP, NULL, " ", NULL);
+ close_json_array(PRINT_JSON, NULL);
+ }
+ }
+
+ if (tb[IFLA_BOND_NS_IP6_TARGET]) {
+ struct rtattr *ip6tb[BOND_MAX_NS_TARGETS + 1];
+
+ parse_rtattr_nested(ip6tb, BOND_MAX_NS_TARGETS,
+ tb[IFLA_BOND_NS_IP6_TARGET]);
+
+ if (ip6tb[0]) {
+ open_json_array(PRINT_JSON, "ns_ip6_target");
+ print_string(PRINT_FP, NULL, "ns_ip6_target ", NULL);
+ }
+
+ for (i = 0; i < BOND_MAX_NS_TARGETS; i++) {
+ if (ip6tb[i])
+ print_string(PRINT_ANY,
+ NULL,
+ "%s",
+ rt_addr_n2a_rta(AF_INET6, ip6tb[i]));
+ if (!is_json_context()
+ && i < BOND_MAX_NS_TARGETS-1
+ && ip6tb[i+1])
+ fprintf(f, ",");
+ }
+
+ if (ip6tb[0]) {
+ print_string(PRINT_FP, NULL, " ", NULL);
+ close_json_array(PRINT_JSON, NULL);
+ }
+ }
+
+ if (tb[IFLA_BOND_ARP_VALIDATE]) {
+ __u32 arp_v = rta_getattr_u32(tb[IFLA_BOND_ARP_VALIDATE]);
+ const char *arp_validate = get_name(arp_validate_tbl, arp_v);
+
+ if (!arp_v && is_json_context())
+ print_null(PRINT_JSON, "arp_validate", NULL, NULL);
+ else
+ print_string(PRINT_ANY,
+ "arp_validate",
+ "arp_validate %s ",
+ arp_validate);
+ }
+
+ if (tb[IFLA_BOND_ARP_ALL_TARGETS]) {
+ const char *arp_all_targets = get_name(arp_all_targets_tbl,
+ rta_getattr_u32(tb[IFLA_BOND_ARP_ALL_TARGETS]));
+ print_string(PRINT_ANY,
+ "arp_all_targets",
+ "arp_all_targets %s ",
+ arp_all_targets);
+ }
+
+ if (tb[IFLA_BOND_PRIMARY]) {
+ unsigned int ifindex = rta_getattr_u32(tb[IFLA_BOND_PRIMARY]);
+
+ if (ifindex) {
+ print_string(PRINT_ANY,
+ "primary",
+ "primary %s ",
+ ll_index_to_name(ifindex));
+ }
+ }
+
+ if (tb[IFLA_BOND_PRIMARY_RESELECT]) {
+ const char *primary_reselect = get_name(primary_reselect_tbl,
+ rta_getattr_u8(tb[IFLA_BOND_PRIMARY_RESELECT]));
+ print_string(PRINT_ANY,
+ "primary_reselect",
+ "primary_reselect %s ",
+ primary_reselect);
+ }
+
+ if (tb[IFLA_BOND_FAIL_OVER_MAC]) {
+ const char *fail_over_mac = get_name(fail_over_mac_tbl,
+ rta_getattr_u8(tb[IFLA_BOND_FAIL_OVER_MAC]));
+ print_string(PRINT_ANY,
+ "fail_over_mac",
+ "fail_over_mac %s ",
+ fail_over_mac);
+ }
+
+ if (tb[IFLA_BOND_XMIT_HASH_POLICY]) {
+ const char *xmit_hash_policy = get_name(xmit_hash_policy_tbl,
+ rta_getattr_u8(tb[IFLA_BOND_XMIT_HASH_POLICY]));
+ print_string(PRINT_ANY,
+ "xmit_hash_policy",
+ "xmit_hash_policy %s ",
+ xmit_hash_policy);
+ }
+
+ if (tb[IFLA_BOND_RESEND_IGMP])
+ print_uint(PRINT_ANY,
+ "resend_igmp",
+ "resend_igmp %u ",
+ rta_getattr_u32(tb[IFLA_BOND_RESEND_IGMP]));
+
+ if (tb[IFLA_BOND_NUM_PEER_NOTIF])
+ print_uint(PRINT_ANY,
+ "num_peer_notif",
+ "num_grat_arp %u ",
+ rta_getattr_u8(tb[IFLA_BOND_NUM_PEER_NOTIF]));
+
+ if (tb[IFLA_BOND_ALL_SLAVES_ACTIVE])
+ print_uint(PRINT_ANY,
+ "all_slaves_active",
+ "all_slaves_active %u ",
+ rta_getattr_u8(tb[IFLA_BOND_ALL_SLAVES_ACTIVE]));
+
+ if (tb[IFLA_BOND_MIN_LINKS])
+ print_uint(PRINT_ANY,
+ "min_links",
+ "min_links %u ",
+ rta_getattr_u32(tb[IFLA_BOND_MIN_LINKS]));
+
+ if (tb[IFLA_BOND_LP_INTERVAL])
+ print_uint(PRINT_ANY,
+ "lp_interval",
+ "lp_interval %u ",
+ rta_getattr_u32(tb[IFLA_BOND_LP_INTERVAL]));
+
+ if (tb[IFLA_BOND_PACKETS_PER_SLAVE])
+ print_uint(PRINT_ANY,
+ "packets_per_slave",
+ "packets_per_slave %u ",
+ rta_getattr_u32(tb[IFLA_BOND_PACKETS_PER_SLAVE]));
+
+ if (tb[IFLA_BOND_AD_LACP_ACTIVE]) {
+ const char *lacp_active = get_name(lacp_active_tbl,
+ rta_getattr_u8(tb[IFLA_BOND_AD_LACP_ACTIVE]));
+ print_string(PRINT_ANY,
+ "ad_lacp_active",
+ "lacp_active %s ",
+ lacp_active);
+ }
+
+ if (tb[IFLA_BOND_AD_LACP_RATE]) {
+ const char *lacp_rate = get_name(lacp_rate_tbl,
+ rta_getattr_u8(tb[IFLA_BOND_AD_LACP_RATE]));
+ print_string(PRINT_ANY,
+ "ad_lacp_rate",
+ "lacp_rate %s ",
+ lacp_rate);
+ }
+
+ if (tb[IFLA_BOND_AD_SELECT]) {
+ const char *ad_select = get_name(ad_select_tbl,
+ rta_getattr_u8(tb[IFLA_BOND_AD_SELECT]));
+ print_string(PRINT_ANY,
+ "ad_select",
+ "ad_select %s ",
+ ad_select);
+ }
+
+ if (tb[IFLA_BOND_AD_INFO]) {
+ struct rtattr *adtb[IFLA_BOND_AD_INFO_MAX + 1];
+
+ parse_rtattr_nested(adtb, IFLA_BOND_AD_INFO_MAX,
+ tb[IFLA_BOND_AD_INFO]);
+
+ open_json_object("ad_info");
+
+ if (adtb[IFLA_BOND_AD_INFO_AGGREGATOR])
+ print_int(PRINT_ANY,
+ "aggregator",
+ "ad_aggregator %d ",
+ rta_getattr_u16(adtb[IFLA_BOND_AD_INFO_AGGREGATOR]));
+
+ if (adtb[IFLA_BOND_AD_INFO_NUM_PORTS])
+ print_int(PRINT_ANY,
+ "num_ports",
+ "ad_num_ports %d ",
+ rta_getattr_u16(adtb[IFLA_BOND_AD_INFO_NUM_PORTS]));
+
+ if (adtb[IFLA_BOND_AD_INFO_ACTOR_KEY])
+ print_int(PRINT_ANY,
+ "actor_key",
+ "ad_actor_key %d ",
+ rta_getattr_u16(adtb[IFLA_BOND_AD_INFO_ACTOR_KEY]));
+
+ if (adtb[IFLA_BOND_AD_INFO_PARTNER_KEY])
+ print_int(PRINT_ANY,
+ "partner_key",
+ "ad_partner_key %d ",
+ rta_getattr_u16(adtb[IFLA_BOND_AD_INFO_PARTNER_KEY]));
+
+ if (adtb[IFLA_BOND_AD_INFO_PARTNER_MAC]) {
+ unsigned char *p =
+ RTA_DATA(adtb[IFLA_BOND_AD_INFO_PARTNER_MAC]);
+ SPRINT_BUF(b);
+ print_string(PRINT_ANY,
+ "partner_mac",
+ "ad_partner_mac %s ",
+ ll_addr_n2a(p, ETH_ALEN, 0, b, sizeof(b)));
+ }
+
+ close_json_object();
+ }
+
+ if (tb[IFLA_BOND_AD_ACTOR_SYS_PRIO]) {
+ print_uint(PRINT_ANY,
+ "ad_actor_sys_prio",
+ "ad_actor_sys_prio %u ",
+ rta_getattr_u16(tb[IFLA_BOND_AD_ACTOR_SYS_PRIO]));
+ }
+
+ if (tb[IFLA_BOND_AD_USER_PORT_KEY]) {
+ print_uint(PRINT_ANY,
+ "ad_user_port_key",
+ "ad_user_port_key %u ",
+ rta_getattr_u16(tb[IFLA_BOND_AD_USER_PORT_KEY]));
+ }
+
+ if (tb[IFLA_BOND_AD_ACTOR_SYSTEM]) {
+ /* We assume the l2 address is an Ethernet MAC address */
+ SPRINT_BUF(b1);
+
+ print_string(PRINT_ANY,
+ "ad_actor_system",
+ "ad_actor_system %s ",
+ ll_addr_n2a(RTA_DATA(tb[IFLA_BOND_AD_ACTOR_SYSTEM]),
+ RTA_PAYLOAD(tb[IFLA_BOND_AD_ACTOR_SYSTEM]),
+ 1 /*ARPHDR_ETHER*/, b1, sizeof(b1)));
+ }
+
+ if (tb[IFLA_BOND_TLB_DYNAMIC_LB]) {
+ print_uint(PRINT_ANY,
+ "tlb_dynamic_lb",
+ "tlb_dynamic_lb %u ",
+ rta_getattr_u8(tb[IFLA_BOND_TLB_DYNAMIC_LB]));
+ }
+}
+
+static void bond_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ print_explain(f);
+}
+
+static void bond_print_xstats_help(struct link_util *lu, FILE *f)
+{
+ fprintf(f, "Usage: ... %s [ 802.3ad ] [ dev DEVICE ]\n", lu->id);
+}
+
+static void bond_print_3ad_stats(const struct rtattr *lacpattr)
+{
+ struct rtattr *lacptb[BOND_3AD_STAT_MAX+1];
+ __u64 val;
+
+ parse_rtattr(lacptb, BOND_3AD_STAT_MAX, RTA_DATA(lacpattr),
+ RTA_PAYLOAD(lacpattr));
+ open_json_object("802.3ad");
+ if (lacptb[BOND_3AD_STAT_LACPDU_RX]) {
+ print_string(PRINT_FP, NULL, "%-16s ", "");
+ print_u64(PRINT_ANY, "lacpdu_rx", "LACPDU Rx %llu\n",
+ rta_getattr_u64(lacptb[BOND_3AD_STAT_LACPDU_RX]));
+ }
+ if (lacptb[BOND_3AD_STAT_LACPDU_TX]) {
+ print_string(PRINT_FP, NULL, "%-16s ", "");
+ print_u64(PRINT_ANY, "lacpdu_tx", "LACPDU Tx %llu\n",
+ rta_getattr_u64(lacptb[BOND_3AD_STAT_LACPDU_TX]));
+ }
+ if (lacptb[BOND_3AD_STAT_LACPDU_UNKNOWN_RX]) {
+ print_string(PRINT_FP, NULL, "%-16s ", "");
+ val = rta_getattr_u64(lacptb[BOND_3AD_STAT_LACPDU_UNKNOWN_RX]);
+ print_u64(PRINT_ANY,
+ "lacpdu_unknown_rx",
+ "LACPDU Unknown type Rx %llu\n",
+ val);
+ }
+ if (lacptb[BOND_3AD_STAT_LACPDU_ILLEGAL_RX]) {
+ print_string(PRINT_FP, NULL, "%-16s ", "");
+ val = rta_getattr_u64(lacptb[BOND_3AD_STAT_LACPDU_ILLEGAL_RX]);
+ print_u64(PRINT_ANY,
+ "lacpdu_illegal_rx",
+ "LACPDU Illegal Rx %llu\n",
+ val);
+ }
+ if (lacptb[BOND_3AD_STAT_MARKER_RX]) {
+ print_string(PRINT_FP, NULL, "%-16s ", "");
+ print_u64(PRINT_ANY, "marker_rx", "Marker Rx %llu\n",
+ rta_getattr_u64(lacptb[BOND_3AD_STAT_MARKER_RX]));
+ }
+ if (lacptb[BOND_3AD_STAT_MARKER_TX]) {
+ print_string(PRINT_FP, NULL, "%-16s ", "");
+ print_u64(PRINT_ANY, "marker_tx", "Marker Tx %llu\n",
+ rta_getattr_u64(lacptb[BOND_3AD_STAT_MARKER_TX]));
+ }
+ if (lacptb[BOND_3AD_STAT_MARKER_RESP_RX]) {
+ print_string(PRINT_FP, NULL, "%-16s ", "");
+ val = rta_getattr_u64(lacptb[BOND_3AD_STAT_MARKER_RESP_RX]);
+ print_u64(PRINT_ANY,
+ "marker_response_rx",
+ "Marker response Rx %llu\n",
+ val);
+ }
+ if (lacptb[BOND_3AD_STAT_MARKER_RESP_TX]) {
+ print_string(PRINT_FP, NULL, "%-16s ", "");
+ val = rta_getattr_u64(lacptb[BOND_3AD_STAT_MARKER_RESP_TX]);
+ print_u64(PRINT_ANY,
+ "marker_response_tx",
+ "Marker response Tx %llu\n",
+ val);
+ }
+ if (lacptb[BOND_3AD_STAT_MARKER_UNKNOWN_RX]) {
+ print_string(PRINT_FP, NULL, "%-16s ", "");
+ val = rta_getattr_u64(lacptb[BOND_3AD_STAT_MARKER_UNKNOWN_RX]);
+ print_u64(PRINT_ANY,
+ "marker_unknown_rx",
+ "Marker unknown type Rx %llu\n",
+ val);
+ }
+ close_json_object();
+}
+
+static void bond_print_stats_attr(struct rtattr *attr, int ifindex)
+{
+ struct rtattr *bondtb[LINK_XSTATS_TYPE_MAX+1];
+ struct rtattr *i, *list;
+ const char *ifname = "";
+ int rem;
+
+ parse_rtattr(bondtb, LINK_XSTATS_TYPE_MAX+1, RTA_DATA(attr),
+ RTA_PAYLOAD(attr));
+ if (!bondtb[LINK_XSTATS_TYPE_BOND])
+ return;
+
+ list = bondtb[LINK_XSTATS_TYPE_BOND];
+ rem = RTA_PAYLOAD(list);
+ open_json_object(NULL);
+ ifname = ll_index_to_name(ifindex);
+ print_string(PRINT_ANY, "ifname", "%-16s\n", ifname);
+ for (i = RTA_DATA(list); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+ if (xstats_print_attr && i->rta_type != xstats_print_attr)
+ continue;
+
+ switch (i->rta_type) {
+ case BOND_XSTATS_3AD:
+ bond_print_3ad_stats(i);
+ break;
+ }
+ break;
+ }
+ close_json_object();
+}
+
+int bond_print_xstats(struct nlmsghdr *n, void *arg)
+{
+ struct if_stats_msg *ifsm = NLMSG_DATA(n);
+ struct rtattr *tb[IFLA_STATS_MAX+1];
+ int len = n->nlmsg_len;
+
+ len -= NLMSG_LENGTH(sizeof(*ifsm));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+ if (filter_index && filter_index != ifsm->ifindex)
+ return 0;
+
+ parse_rtattr(tb, IFLA_STATS_MAX, IFLA_STATS_RTA(ifsm), len);
+ if (tb[IFLA_STATS_LINK_XSTATS])
+ bond_print_stats_attr(tb[IFLA_STATS_LINK_XSTATS],
+ ifsm->ifindex);
+
+ if (tb[IFLA_STATS_LINK_XSTATS_SLAVE])
+ bond_print_stats_attr(tb[IFLA_STATS_LINK_XSTATS_SLAVE],
+ ifsm->ifindex);
+
+ return 0;
+}
+
+int bond_parse_xstats(struct link_util *lu, int argc, char **argv)
+{
+ while (argc > 0) {
+ if (strcmp(*argv, "lacp") == 0 ||
+ strcmp(*argv, "802.3ad") == 0) {
+ xstats_print_attr = BOND_XSTATS_3AD;
+ } else if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ filter_index = ll_name_to_index(*argv);
+ if (!filter_index)
+ return nodev(*argv);
+ } else if (strcmp(*argv, "help") == 0) {
+ bond_print_xstats_help(lu, stdout);
+ exit(0);
+ } else {
+ invarg("unknown attribute", *argv);
+ }
+ argc--; argv++;
+ }
+
+ return 0;
+}
+
+struct link_util bond_link_util = {
+ .id = "bond",
+ .maxattr = IFLA_BOND_MAX,
+ .parse_opt = bond_parse_opt,
+ .print_opt = bond_print_opt,
+ .print_help = bond_print_help,
+ .parse_ifla_xstats = bond_parse_xstats,
+ .print_ifla_xstats = bond_print_xstats,
+};
+
+static const struct ipstats_stat_desc_xstats
+ipstats_stat_desc_xstats_bond_lacp = {
+ .desc = IPSTATS_STAT_DESC_XSTATS_LEAF("802.3ad"),
+ .xstats_at = IFLA_STATS_LINK_XSTATS,
+ .link_type_at = LINK_XSTATS_TYPE_BOND,
+ .inner_max = BOND_XSTATS_MAX,
+ .inner_at = BOND_XSTATS_3AD,
+ .show_cb = &bond_print_3ad_stats,
+};
+
+static const struct ipstats_stat_desc *
+ipstats_stat_desc_xstats_bond_subs[] = {
+ &ipstats_stat_desc_xstats_bond_lacp.desc,
+};
+
+const struct ipstats_stat_desc ipstats_stat_desc_xstats_bond_group = {
+ .name = "bond",
+ .kind = IPSTATS_STAT_DESC_KIND_GROUP,
+ .subs = ipstats_stat_desc_xstats_bond_subs,
+ .nsubs = ARRAY_SIZE(ipstats_stat_desc_xstats_bond_subs),
+};
+
+static const struct ipstats_stat_desc_xstats
+ipstats_stat_desc_xstats_slave_bond_lacp = {
+ .desc = IPSTATS_STAT_DESC_XSTATS_LEAF("802.3ad"),
+ .xstats_at = IFLA_STATS_LINK_XSTATS_SLAVE,
+ .link_type_at = LINK_XSTATS_TYPE_BOND,
+ .inner_max = BOND_XSTATS_MAX,
+ .inner_at = BOND_XSTATS_3AD,
+ .show_cb = &bond_print_3ad_stats,
+};
+
+static const struct ipstats_stat_desc *
+ipstats_stat_desc_xstats_slave_bond_subs[] = {
+ &ipstats_stat_desc_xstats_slave_bond_lacp.desc,
+};
+
+const struct ipstats_stat_desc ipstats_stat_desc_xstats_slave_bond_group = {
+ .name = "bond",
+ .kind = IPSTATS_STAT_DESC_KIND_GROUP,
+ .subs = ipstats_stat_desc_xstats_slave_bond_subs,
+ .nsubs = ARRAY_SIZE(ipstats_stat_desc_xstats_slave_bond_subs),
+};
diff --git a/ip/iplink_bond_slave.c b/ip/iplink_bond_slave.c
new file mode 100644
index 0000000..ad68750
--- /dev/null
+++ b/ip/iplink_bond_slave.c
@@ -0,0 +1,195 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iplink_bond_slave.c Bonding slave device support
+ *
+ * Authors: Jiri Pirko <jiri@resnulli.us>
+ */
+
+#include <stdio.h>
+#include <sys/socket.h>
+#include <linux/if_bonding.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_explain(FILE *f)
+{
+ fprintf(f, "Usage: ... bond_slave [ queue_id ID ] [ prio PRIORITY ]\n");
+}
+
+static void explain(void)
+{
+ print_explain(stderr);
+}
+
+static const char *slave_states[] = {
+ [BOND_STATE_ACTIVE] = "ACTIVE",
+ [BOND_STATE_BACKUP] = "BACKUP",
+};
+
+static void print_slave_state(FILE *f, struct rtattr *tb)
+{
+ unsigned int state = rta_getattr_u8(tb);
+
+ if (state >= ARRAY_SIZE(slave_states))
+ print_int(PRINT_ANY, "state_index", "state %d ", state);
+ else
+ print_string(PRINT_ANY,
+ "state",
+ "state %s ",
+ slave_states[state]);
+}
+
+static const char *slave_mii_status[] = {
+ [BOND_LINK_UP] = "UP",
+ [BOND_LINK_FAIL] = "GOING_DOWN",
+ [BOND_LINK_DOWN] = "DOWN",
+ [BOND_LINK_BACK] = "GOING_BACK",
+};
+
+static void print_slave_mii_status(FILE *f, struct rtattr *tb)
+{
+ unsigned int status = rta_getattr_u8(tb);
+
+ if (status >= ARRAY_SIZE(slave_mii_status))
+ print_int(PRINT_ANY,
+ "mii_status_index",
+ "mii_status %d ",
+ status);
+ else
+ print_string(PRINT_ANY,
+ "mii_status",
+ "mii_status %s ",
+ slave_mii_status[status]);
+}
+
+static void print_slave_oper_state(FILE *fp, const char *name, __u16 state)
+{
+ open_json_array(PRINT_ANY, name);
+ print_string(PRINT_FP, NULL, " <", NULL);
+#define _PF(s, str) if (state & LACP_STATE_##s) { \
+ state &= ~LACP_STATE_##s; \
+ print_string(PRINT_ANY, NULL, \
+ state ? "%s," : "%s", str); }
+ _PF(LACP_ACTIVITY, "active");
+ _PF(LACP_TIMEOUT, "short_timeout");
+ _PF(AGGREGATION, "aggregating");
+ _PF(SYNCHRONIZATION, "in_sync");
+ _PF(COLLECTING, "collecting");
+ _PF(DISTRIBUTING, "distributing");
+ _PF(DEFAULTED, "defaulted");
+ _PF(EXPIRED, "expired");
+#undef _PF
+ close_json_array(PRINT_ANY, "> ");
+}
+
+static void bond_slave_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ SPRINT_BUF(b1);
+ if (!tb)
+ return;
+
+ if (tb[IFLA_BOND_SLAVE_STATE])
+ print_slave_state(f, tb[IFLA_BOND_SLAVE_STATE]);
+
+ if (tb[IFLA_BOND_SLAVE_MII_STATUS])
+ print_slave_mii_status(f, tb[IFLA_BOND_SLAVE_MII_STATUS]);
+
+ if (tb[IFLA_BOND_SLAVE_LINK_FAILURE_COUNT])
+ print_int(PRINT_ANY,
+ "link_failure_count",
+ "link_failure_count %d ",
+ rta_getattr_u32(tb[IFLA_BOND_SLAVE_LINK_FAILURE_COUNT]));
+
+ if (tb[IFLA_BOND_SLAVE_PERM_HWADDR])
+ print_string(PRINT_ANY,
+ "perm_hwaddr",
+ "perm_hwaddr %s ",
+ ll_addr_n2a(RTA_DATA(tb[IFLA_BOND_SLAVE_PERM_HWADDR]),
+ RTA_PAYLOAD(tb[IFLA_BOND_SLAVE_PERM_HWADDR]),
+ 0, b1, sizeof(b1)));
+
+ if (tb[IFLA_BOND_SLAVE_QUEUE_ID])
+ print_int(PRINT_ANY,
+ "queue_id",
+ "queue_id %d ",
+ rta_getattr_u16(tb[IFLA_BOND_SLAVE_QUEUE_ID]));
+
+ if (tb[IFLA_BOND_SLAVE_PRIO])
+ print_int(PRINT_ANY, "prio", "prio %d ",
+ rta_getattr_s32(tb[IFLA_BOND_SLAVE_PRIO]));
+
+ if (tb[IFLA_BOND_SLAVE_AD_AGGREGATOR_ID])
+ print_int(PRINT_ANY,
+ "ad_aggregator_id",
+ "ad_aggregator_id %d ",
+ rta_getattr_u16(tb[IFLA_BOND_SLAVE_AD_AGGREGATOR_ID]));
+
+ if (tb[IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE]) {
+ __u8 state = rta_getattr_u8(tb[IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE]);
+
+ print_int(PRINT_ANY,
+ "ad_actor_oper_port_state",
+ "ad_actor_oper_port_state %d ",
+ state);
+ print_slave_oper_state(f, "ad_actor_oper_port_state_str", state);
+ }
+
+ if (tb[IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE]) {
+ __u16 state = rta_getattr_u8(tb[IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE]);
+
+ print_int(PRINT_ANY,
+ "ad_partner_oper_port_state",
+ "ad_partner_oper_port_state %d ",
+ state);
+ print_slave_oper_state(f, "ad_partner_oper_port_state_str", state);
+ }
+}
+
+static int bond_slave_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ __u16 queue_id;
+ int prio;
+
+ while (argc > 0) {
+ if (matches(*argv, "queue_id") == 0) {
+ NEXT_ARG();
+ if (get_u16(&queue_id, *argv, 0))
+ invarg("queue_id is invalid", *argv);
+ addattr16(n, 1024, IFLA_BOND_SLAVE_QUEUE_ID, queue_id);
+ } else if (strcmp(*argv, "prio") == 0) {
+ NEXT_ARG();
+ if (get_s32(&prio, *argv, 0))
+ invarg("prio is invalid", *argv);
+ addattr32(n, 1024, IFLA_BOND_SLAVE_PRIO, prio);
+ } else {
+ if (matches(*argv, "help") != 0)
+ fprintf(stderr,
+ "bond_slave: unknown option \"%s\"?\n",
+ *argv);
+ explain();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ return 0;
+}
+
+static void bond_slave_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ print_explain(f);
+}
+
+struct link_util bond_slave_link_util = {
+ .id = "bond_slave",
+ .maxattr = IFLA_BOND_SLAVE_MAX,
+ .print_opt = bond_slave_print_opt,
+ .parse_opt = bond_slave_parse_opt,
+ .print_help = bond_slave_print_help,
+ .parse_ifla_xstats = bond_parse_xstats,
+ .print_ifla_xstats = bond_print_xstats,
+};
diff --git a/ip/iplink_bridge.c b/ip/iplink_bridge.c
new file mode 100644
index 0000000..6b70ffb
--- /dev/null
+++ b/ip/iplink_bridge.c
@@ -0,0 +1,1040 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iplink_bridge.c Bridge device support
+ *
+ * Authors: Jiri Pirko <jiri@resnulli.us>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <netinet/ether.h>
+#include <linux/if_link.h>
+#include <linux/if_bridge.h>
+#include <net/if.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static unsigned int xstats_print_attr;
+static int filter_index;
+
+static void print_explain(FILE *f)
+{
+ fprintf(f,
+ "Usage: ... bridge [ fdb_flush ]\n"
+ " [ forward_delay FORWARD_DELAY ]\n"
+ " [ hello_time HELLO_TIME ]\n"
+ " [ max_age MAX_AGE ]\n"
+ " [ ageing_time AGEING_TIME ]\n"
+ " [ stp_state STP_STATE ]\n"
+ " [ priority PRIORITY ]\n"
+ " [ group_fwd_mask MASK ]\n"
+ " [ group_address ADDRESS ]\n"
+ " [ no_linklocal_learn NO_LINKLOCAL_LEARN ]\n"
+ " [ fdb_max_learned FDB_MAX_LEARNED ]\n"
+ " [ vlan_filtering VLAN_FILTERING ]\n"
+ " [ vlan_protocol VLAN_PROTOCOL ]\n"
+ " [ vlan_default_pvid VLAN_DEFAULT_PVID ]\n"
+ " [ vlan_stats_enabled VLAN_STATS_ENABLED ]\n"
+ " [ vlan_stats_per_port VLAN_STATS_PER_PORT ]\n"
+ " [ mcast_snooping MULTICAST_SNOOPING ]\n"
+ " [ mcast_vlan_snooping MULTICAST_VLAN_SNOOPING ]\n"
+ " [ mcast_router MULTICAST_ROUTER ]\n"
+ " [ mcast_query_use_ifaddr MCAST_QUERY_USE_IFADDR ]\n"
+ " [ mcast_querier MULTICAST_QUERIER ]\n"
+ " [ mcast_hash_elasticity HASH_ELASTICITY ]\n"
+ " [ mcast_hash_max HASH_MAX ]\n"
+ " [ mcast_last_member_count LAST_MEMBER_COUNT ]\n"
+ " [ mcast_startup_query_count STARTUP_QUERY_COUNT ]\n"
+ " [ mcast_last_member_interval LAST_MEMBER_INTERVAL ]\n"
+ " [ mcast_membership_interval MEMBERSHIP_INTERVAL ]\n"
+ " [ mcast_querier_interval QUERIER_INTERVAL ]\n"
+ " [ mcast_query_interval QUERY_INTERVAL ]\n"
+ " [ mcast_query_response_interval QUERY_RESPONSE_INTERVAL ]\n"
+ " [ mcast_startup_query_interval STARTUP_QUERY_INTERVAL ]\n"
+ " [ mcast_stats_enabled MCAST_STATS_ENABLED ]\n"
+ " [ mcast_igmp_version IGMP_VERSION ]\n"
+ " [ mcast_mld_version MLD_VERSION ]\n"
+ " [ nf_call_iptables NF_CALL_IPTABLES ]\n"
+ " [ nf_call_ip6tables NF_CALL_IP6TABLES ]\n"
+ " [ nf_call_arptables NF_CALL_ARPTABLES ]\n"
+ "\n"
+ "Where: VLAN_PROTOCOL := { 802.1Q | 802.1ad }\n"
+ );
+}
+
+static void explain(void)
+{
+ print_explain(stderr);
+}
+
+void br_dump_bridge_id(const struct ifla_bridge_id *id, char *buf, size_t len)
+{
+ char eaddr[18];
+
+ ether_ntoa_r((const struct ether_addr *)id->addr, eaddr);
+ snprintf(buf, len, "%.2x%.2x.%s", id->prio[0], id->prio[1], eaddr);
+}
+
+static int bridge_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ struct br_boolopt_multi bm = {};
+ __u32 val;
+
+ while (argc > 0) {
+ if (matches(*argv, "forward_delay") == 0) {
+ NEXT_ARG();
+ if (get_u32(&val, *argv, 0))
+ invarg("invalid forward_delay", *argv);
+
+ addattr32(n, 1024, IFLA_BR_FORWARD_DELAY, val);
+ } else if (matches(*argv, "hello_time") == 0) {
+ NEXT_ARG();
+ if (get_u32(&val, *argv, 0))
+ invarg("invalid hello_time", *argv);
+
+ addattr32(n, 1024, IFLA_BR_HELLO_TIME, val);
+ } else if (matches(*argv, "max_age") == 0) {
+ NEXT_ARG();
+ if (get_u32(&val, *argv, 0))
+ invarg("invalid max_age", *argv);
+
+ addattr32(n, 1024, IFLA_BR_MAX_AGE, val);
+ } else if (matches(*argv, "ageing_time") == 0) {
+ NEXT_ARG();
+ if (get_u32(&val, *argv, 0))
+ invarg("invalid ageing_time", *argv);
+
+ addattr32(n, 1024, IFLA_BR_AGEING_TIME, val);
+ } else if (matches(*argv, "stp_state") == 0) {
+ NEXT_ARG();
+ if (get_u32(&val, *argv, 0))
+ invarg("invalid stp_state", *argv);
+
+ addattr32(n, 1024, IFLA_BR_STP_STATE, val);
+ } else if (matches(*argv, "priority") == 0) {
+ __u16 prio;
+
+ NEXT_ARG();
+ if (get_u16(&prio, *argv, 0))
+ invarg("invalid priority", *argv);
+
+ addattr16(n, 1024, IFLA_BR_PRIORITY, prio);
+ } else if (matches(*argv, "vlan_filtering") == 0) {
+ __u8 vlan_filter;
+
+ NEXT_ARG();
+ if (get_u8(&vlan_filter, *argv, 0))
+ invarg("invalid vlan_filtering", *argv);
+
+ addattr8(n, 1024, IFLA_BR_VLAN_FILTERING, vlan_filter);
+ } else if (matches(*argv, "vlan_protocol") == 0) {
+ __u16 vlan_proto;
+
+ NEXT_ARG();
+ if (ll_proto_a2n(&vlan_proto, *argv))
+ invarg("invalid vlan_protocol", *argv);
+
+ addattr16(n, 1024, IFLA_BR_VLAN_PROTOCOL, vlan_proto);
+ } else if (matches(*argv, "group_fwd_mask") == 0) {
+ __u16 fwd_mask;
+
+ NEXT_ARG();
+ if (get_u16(&fwd_mask, *argv, 0))
+ invarg("invalid group_fwd_mask", *argv);
+
+ addattr16(n, 1024, IFLA_BR_GROUP_FWD_MASK, fwd_mask);
+ } else if (matches(*argv, "group_address") == 0) {
+ char llabuf[32];
+ int len;
+
+ NEXT_ARG();
+ len = ll_addr_a2n(llabuf, sizeof(llabuf), *argv);
+ if (len < 0)
+ return -1;
+ addattr_l(n, 1024, IFLA_BR_GROUP_ADDR, llabuf, len);
+ } else if (strcmp(*argv, "no_linklocal_learn") == 0) {
+ __u32 no_ll_learn_bit = 1 << BR_BOOLOPT_NO_LL_LEARN;
+ __u8 no_ll_learn;
+
+ NEXT_ARG();
+ if (get_u8(&no_ll_learn, *argv, 0))
+ invarg("invalid no_linklocal_learn", *argv);
+ bm.optmask |= 1 << BR_BOOLOPT_NO_LL_LEARN;
+ if (no_ll_learn)
+ bm.optval |= no_ll_learn_bit;
+ else
+ bm.optval &= ~no_ll_learn_bit;
+ } else if (strcmp(*argv, "fdb_max_learned") == 0) {
+ __u32 fdb_max_learned;
+
+ NEXT_ARG();
+ if (get_u32(&fdb_max_learned, *argv, 0))
+ invarg("invalid fdb_max_learned", *argv);
+
+ addattr32(n, 1024, IFLA_BR_FDB_MAX_LEARNED, fdb_max_learned);
+ } else if (matches(*argv, "fdb_flush") == 0) {
+ addattr(n, 1024, IFLA_BR_FDB_FLUSH);
+ } else if (matches(*argv, "vlan_default_pvid") == 0) {
+ __u16 default_pvid;
+
+ NEXT_ARG();
+ if (get_u16(&default_pvid, *argv, 0))
+ invarg("invalid vlan_default_pvid", *argv);
+
+ addattr16(n, 1024, IFLA_BR_VLAN_DEFAULT_PVID,
+ default_pvid);
+ } else if (matches(*argv, "vlan_stats_enabled") == 0) {
+ __u8 vlan_stats_enabled;
+
+ NEXT_ARG();
+ if (get_u8(&vlan_stats_enabled, *argv, 0))
+ invarg("invalid vlan_stats_enabled", *argv);
+ addattr8(n, 1024, IFLA_BR_VLAN_STATS_ENABLED,
+ vlan_stats_enabled);
+ } else if (matches(*argv, "vlan_stats_per_port") == 0) {
+ __u8 vlan_stats_per_port;
+
+ NEXT_ARG();
+ if (get_u8(&vlan_stats_per_port, *argv, 0))
+ invarg("invalid vlan_stats_per_port", *argv);
+ addattr8(n, 1024, IFLA_BR_VLAN_STATS_PER_PORT,
+ vlan_stats_per_port);
+ } else if (matches(*argv, "mcast_router") == 0) {
+ __u8 mcast_router;
+
+ NEXT_ARG();
+ if (get_u8(&mcast_router, *argv, 0))
+ invarg("invalid mcast_router", *argv);
+
+ addattr8(n, 1024, IFLA_BR_MCAST_ROUTER, mcast_router);
+ } else if (matches(*argv, "mcast_snooping") == 0) {
+ __u8 mcast_snoop;
+
+ NEXT_ARG();
+ if (get_u8(&mcast_snoop, *argv, 0))
+ invarg("invalid mcast_snooping", *argv);
+
+ addattr8(n, 1024, IFLA_BR_MCAST_SNOOPING, mcast_snoop);
+ } else if (strcmp(*argv, "mcast_vlan_snooping") == 0) {
+ __u32 mcvl_bit = 1 << BR_BOOLOPT_MCAST_VLAN_SNOOPING;
+ __u8 mcast_vlan_snooping;
+
+ NEXT_ARG();
+ if (get_u8(&mcast_vlan_snooping, *argv, 0))
+ invarg("invalid mcast_vlan_snooping", *argv);
+ bm.optmask |= 1 << BR_BOOLOPT_MCAST_VLAN_SNOOPING;
+ if (mcast_vlan_snooping)
+ bm.optval |= mcvl_bit;
+ else
+ bm.optval &= ~mcvl_bit;
+ } else if (matches(*argv, "mcast_query_use_ifaddr") == 0) {
+ __u8 mcast_qui;
+
+ NEXT_ARG();
+ if (get_u8(&mcast_qui, *argv, 0))
+ invarg("invalid mcast_query_use_ifaddr",
+ *argv);
+
+ addattr8(n, 1024, IFLA_BR_MCAST_QUERY_USE_IFADDR,
+ mcast_qui);
+ } else if (matches(*argv, "mcast_querier") == 0) {
+ __u8 mcast_querier;
+
+ NEXT_ARG();
+ if (get_u8(&mcast_querier, *argv, 0))
+ invarg("invalid mcast_querier", *argv);
+
+ addattr8(n, 1024, IFLA_BR_MCAST_QUERIER, mcast_querier);
+ } else if (matches(*argv, "mcast_hash_elasticity") == 0) {
+ __u32 mcast_hash_el;
+
+ NEXT_ARG();
+ if (get_u32(&mcast_hash_el, *argv, 0))
+ invarg("invalid mcast_hash_elasticity",
+ *argv);
+
+ addattr32(n, 1024, IFLA_BR_MCAST_HASH_ELASTICITY,
+ mcast_hash_el);
+ } else if (matches(*argv, "mcast_hash_max") == 0) {
+ __u32 mcast_hash_max;
+
+ NEXT_ARG();
+ if (get_u32(&mcast_hash_max, *argv, 0))
+ invarg("invalid mcast_hash_max", *argv);
+
+ addattr32(n, 1024, IFLA_BR_MCAST_HASH_MAX,
+ mcast_hash_max);
+ } else if (matches(*argv, "mcast_last_member_count") == 0) {
+ __u32 mcast_lmc;
+
+ NEXT_ARG();
+ if (get_u32(&mcast_lmc, *argv, 0))
+ invarg("invalid mcast_last_member_count",
+ *argv);
+
+ addattr32(n, 1024, IFLA_BR_MCAST_LAST_MEMBER_CNT,
+ mcast_lmc);
+ } else if (matches(*argv, "mcast_startup_query_count") == 0) {
+ __u32 mcast_sqc;
+
+ NEXT_ARG();
+ if (get_u32(&mcast_sqc, *argv, 0))
+ invarg("invalid mcast_startup_query_count",
+ *argv);
+
+ addattr32(n, 1024, IFLA_BR_MCAST_STARTUP_QUERY_CNT,
+ mcast_sqc);
+ } else if (matches(*argv, "mcast_last_member_interval") == 0) {
+ __u64 mcast_last_member_intvl;
+
+ NEXT_ARG();
+ if (get_u64(&mcast_last_member_intvl, *argv, 0))
+ invarg("invalid mcast_last_member_interval",
+ *argv);
+
+ addattr64(n, 1024, IFLA_BR_MCAST_LAST_MEMBER_INTVL,
+ mcast_last_member_intvl);
+ } else if (matches(*argv, "mcast_membership_interval") == 0) {
+ __u64 mcast_membership_intvl;
+
+ NEXT_ARG();
+ if (get_u64(&mcast_membership_intvl, *argv, 0))
+ invarg("invalid mcast_membership_interval",
+ *argv);
+
+ addattr64(n, 1024, IFLA_BR_MCAST_MEMBERSHIP_INTVL,
+ mcast_membership_intvl);
+ } else if (matches(*argv, "mcast_querier_interval") == 0) {
+ __u64 mcast_querier_intvl;
+
+ NEXT_ARG();
+ if (get_u64(&mcast_querier_intvl, *argv, 0))
+ invarg("invalid mcast_querier_interval",
+ *argv);
+
+ addattr64(n, 1024, IFLA_BR_MCAST_QUERIER_INTVL,
+ mcast_querier_intvl);
+ } else if (matches(*argv, "mcast_query_interval") == 0) {
+ __u64 mcast_query_intvl;
+
+ NEXT_ARG();
+ if (get_u64(&mcast_query_intvl, *argv, 0))
+ invarg("invalid mcast_query_interval",
+ *argv);
+
+ addattr64(n, 1024, IFLA_BR_MCAST_QUERY_INTVL,
+ mcast_query_intvl);
+ } else if (!matches(*argv, "mcast_query_response_interval")) {
+ __u64 mcast_query_resp_intvl;
+
+ NEXT_ARG();
+ if (get_u64(&mcast_query_resp_intvl, *argv, 0))
+ invarg("invalid mcast_query_response_interval",
+ *argv);
+
+ addattr64(n, 1024, IFLA_BR_MCAST_QUERY_RESPONSE_INTVL,
+ mcast_query_resp_intvl);
+ } else if (!matches(*argv, "mcast_startup_query_interval")) {
+ __u64 mcast_startup_query_intvl;
+
+ NEXT_ARG();
+ if (get_u64(&mcast_startup_query_intvl, *argv, 0))
+ invarg("invalid mcast_startup_query_interval",
+ *argv);
+
+ addattr64(n, 1024, IFLA_BR_MCAST_STARTUP_QUERY_INTVL,
+ mcast_startup_query_intvl);
+ } else if (matches(*argv, "mcast_stats_enabled") == 0) {
+ __u8 mcast_stats_enabled;
+
+ NEXT_ARG();
+ if (get_u8(&mcast_stats_enabled, *argv, 0))
+ invarg("invalid mcast_stats_enabled", *argv);
+ addattr8(n, 1024, IFLA_BR_MCAST_STATS_ENABLED,
+ mcast_stats_enabled);
+ } else if (matches(*argv, "mcast_igmp_version") == 0) {
+ __u8 igmp_version;
+
+ NEXT_ARG();
+ if (get_u8(&igmp_version, *argv, 0))
+ invarg("invalid mcast_igmp_version", *argv);
+ addattr8(n, 1024, IFLA_BR_MCAST_IGMP_VERSION,
+ igmp_version);
+ } else if (matches(*argv, "mcast_mld_version") == 0) {
+ __u8 mld_version;
+
+ NEXT_ARG();
+ if (get_u8(&mld_version, *argv, 0))
+ invarg("invalid mcast_mld_version", *argv);
+ addattr8(n, 1024, IFLA_BR_MCAST_MLD_VERSION,
+ mld_version);
+ } else if (matches(*argv, "nf_call_iptables") == 0) {
+ __u8 nf_call_ipt;
+
+ NEXT_ARG();
+ if (get_u8(&nf_call_ipt, *argv, 0))
+ invarg("invalid nf_call_iptables", *argv);
+
+ addattr8(n, 1024, IFLA_BR_NF_CALL_IPTABLES,
+ nf_call_ipt);
+ } else if (matches(*argv, "nf_call_ip6tables") == 0) {
+ __u8 nf_call_ip6t;
+
+ NEXT_ARG();
+ if (get_u8(&nf_call_ip6t, *argv, 0))
+ invarg("invalid nf_call_ip6tables", *argv);
+
+ addattr8(n, 1024, IFLA_BR_NF_CALL_IP6TABLES,
+ nf_call_ip6t);
+ } else if (matches(*argv, "nf_call_arptables") == 0) {
+ __u8 nf_call_arpt;
+
+ NEXT_ARG();
+ if (get_u8(&nf_call_arpt, *argv, 0))
+ invarg("invalid nf_call_arptables", *argv);
+
+ addattr8(n, 1024, IFLA_BR_NF_CALL_ARPTABLES,
+ nf_call_arpt);
+ } else if (matches(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "bridge: unknown command \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ if (bm.optmask)
+ addattr_l(n, 1024, IFLA_BR_MULTI_BOOLOPT,
+ &bm, sizeof(bm));
+ return 0;
+}
+
+static void _bridge_print_timer(FILE *f,
+ const char *attr,
+ struct rtattr *timer)
+{
+ struct timeval tv;
+
+ __jiffies_to_tv(&tv, rta_getattr_u64(timer));
+ if (is_json_context()) {
+ json_writer_t *jw = get_json_writer();
+
+ jsonw_name(jw, attr);
+ jsonw_printf(jw, "%i.%.2i",
+ (int)tv.tv_sec,
+ (int)tv.tv_usec / 10000);
+ } else {
+ fprintf(f, "%s %4i.%.2i ", attr, (int)tv.tv_sec,
+ (int)tv.tv_usec / 10000);
+ }
+}
+
+static void bridge_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ if (!tb)
+ return;
+
+ if (tb[IFLA_BR_FORWARD_DELAY])
+ print_uint(PRINT_ANY,
+ "forward_delay",
+ "forward_delay %u ",
+ rta_getattr_u32(tb[IFLA_BR_FORWARD_DELAY]));
+
+ if (tb[IFLA_BR_HELLO_TIME])
+ print_uint(PRINT_ANY,
+ "hello_time",
+ "hello_time %u ",
+ rta_getattr_u32(tb[IFLA_BR_HELLO_TIME]));
+
+ if (tb[IFLA_BR_MAX_AGE])
+ print_uint(PRINT_ANY,
+ "max_age",
+ "max_age %u ",
+ rta_getattr_u32(tb[IFLA_BR_MAX_AGE]));
+
+ if (tb[IFLA_BR_AGEING_TIME])
+ print_uint(PRINT_ANY,
+ "ageing_time",
+ "ageing_time %u ",
+ rta_getattr_u32(tb[IFLA_BR_AGEING_TIME]));
+
+ if (tb[IFLA_BR_STP_STATE])
+ print_uint(PRINT_ANY,
+ "stp_state",
+ "stp_state %u ",
+ rta_getattr_u32(tb[IFLA_BR_STP_STATE]));
+
+ if (tb[IFLA_BR_PRIORITY])
+ print_uint(PRINT_ANY,
+ "priority",
+ "priority %u ",
+ rta_getattr_u16(tb[IFLA_BR_PRIORITY]));
+
+ if (tb[IFLA_BR_VLAN_FILTERING])
+ print_uint(PRINT_ANY,
+ "vlan_filtering",
+ "vlan_filtering %u ",
+ rta_getattr_u8(tb[IFLA_BR_VLAN_FILTERING]));
+
+ if (tb[IFLA_BR_VLAN_PROTOCOL]) {
+ SPRINT_BUF(b1);
+
+ print_string(PRINT_ANY,
+ "vlan_protocol",
+ "vlan_protocol %s ",
+ ll_proto_n2a(rta_getattr_u16(tb[IFLA_BR_VLAN_PROTOCOL]),
+ b1, sizeof(b1)));
+ }
+
+ if (tb[IFLA_BR_BRIDGE_ID]) {
+ char bridge_id[32];
+
+ br_dump_bridge_id(RTA_DATA(tb[IFLA_BR_BRIDGE_ID]), bridge_id,
+ sizeof(bridge_id));
+ print_string(PRINT_ANY,
+ "bridge_id",
+ "bridge_id %s ",
+ bridge_id);
+ }
+
+ if (tb[IFLA_BR_ROOT_ID]) {
+ char root_id[32];
+
+ br_dump_bridge_id(RTA_DATA(tb[IFLA_BR_ROOT_ID]), root_id,
+ sizeof(root_id));
+ print_string(PRINT_ANY,
+ "root_id",
+ "designated_root %s ",
+ root_id);
+ }
+
+ if (tb[IFLA_BR_ROOT_PORT])
+ print_uint(PRINT_ANY,
+ "root_port",
+ "root_port %u ",
+ rta_getattr_u16(tb[IFLA_BR_ROOT_PORT]));
+
+ if (tb[IFLA_BR_ROOT_PATH_COST])
+ print_uint(PRINT_ANY,
+ "root_path_cost",
+ "root_path_cost %u ",
+ rta_getattr_u32(tb[IFLA_BR_ROOT_PATH_COST]));
+
+ if (tb[IFLA_BR_TOPOLOGY_CHANGE])
+ print_uint(PRINT_ANY,
+ "topology_change",
+ "topology_change %u ",
+ rta_getattr_u8(tb[IFLA_BR_TOPOLOGY_CHANGE]));
+
+ if (tb[IFLA_BR_TOPOLOGY_CHANGE_DETECTED])
+ print_uint(PRINT_ANY,
+ "topology_change_detected",
+ "topology_change_detected %u ",
+ rta_getattr_u8(tb[IFLA_BR_TOPOLOGY_CHANGE_DETECTED]));
+
+ if (tb[IFLA_BR_HELLO_TIMER])
+ _bridge_print_timer(f, "hello_timer", tb[IFLA_BR_HELLO_TIMER]);
+
+ if (tb[IFLA_BR_TCN_TIMER])
+ _bridge_print_timer(f, "tcn_timer", tb[IFLA_BR_TCN_TIMER]);
+
+ if (tb[IFLA_BR_TOPOLOGY_CHANGE_TIMER])
+ _bridge_print_timer(f, "topology_change_timer",
+ tb[IFLA_BR_TOPOLOGY_CHANGE_TIMER]);
+
+ if (tb[IFLA_BR_GC_TIMER])
+ _bridge_print_timer(f, "gc_timer", tb[IFLA_BR_GC_TIMER]);
+
+ if (tb[IFLA_BR_FDB_N_LEARNED])
+ print_uint(PRINT_ANY,
+ "fdb_n_learned",
+ "fdb_n_learned %u ",
+ rta_getattr_u32(tb[IFLA_BR_FDB_N_LEARNED]));
+
+ if (tb[IFLA_BR_FDB_MAX_LEARNED])
+ print_uint(PRINT_ANY,
+ "fdb_max_learned",
+ "fdb_max_learned %u ",
+ rta_getattr_u32(tb[IFLA_BR_FDB_MAX_LEARNED]));
+
+ if (tb[IFLA_BR_VLAN_DEFAULT_PVID])
+ print_uint(PRINT_ANY,
+ "vlan_default_pvid",
+ "vlan_default_pvid %u ",
+ rta_getattr_u16(tb[IFLA_BR_VLAN_DEFAULT_PVID]));
+
+ if (tb[IFLA_BR_VLAN_STATS_ENABLED])
+ print_uint(PRINT_ANY,
+ "vlan_stats_enabled",
+ "vlan_stats_enabled %u ",
+ rta_getattr_u8(tb[IFLA_BR_VLAN_STATS_ENABLED]));
+
+ if (tb[IFLA_BR_VLAN_STATS_PER_PORT])
+ print_uint(PRINT_ANY,
+ "vlan_stats_per_port",
+ "vlan_stats_per_port %u ",
+ rta_getattr_u8(tb[IFLA_BR_VLAN_STATS_PER_PORT]));
+
+ if (tb[IFLA_BR_GROUP_FWD_MASK])
+ print_0xhex(PRINT_ANY,
+ "group_fwd_mask",
+ "group_fwd_mask %#llx ",
+ rta_getattr_u16(tb[IFLA_BR_GROUP_FWD_MASK]));
+
+ if (tb[IFLA_BR_GROUP_ADDR]) {
+ SPRINT_BUF(mac);
+
+ print_string(PRINT_ANY,
+ "group_addr",
+ "group_address %s ",
+ ll_addr_n2a(RTA_DATA(tb[IFLA_BR_GROUP_ADDR]),
+ RTA_PAYLOAD(tb[IFLA_BR_GROUP_ADDR]),
+ 1 /*ARPHDR_ETHER*/, mac, sizeof(mac)));
+ }
+
+ if (tb[IFLA_BR_MCAST_SNOOPING])
+ print_uint(PRINT_ANY,
+ "mcast_snooping",
+ "mcast_snooping %u ",
+ rta_getattr_u8(tb[IFLA_BR_MCAST_SNOOPING]));
+
+ if (tb[IFLA_BR_MULTI_BOOLOPT]) {
+ __u32 mcvl_bit = 1 << BR_BOOLOPT_MCAST_VLAN_SNOOPING;
+ __u32 no_ll_learn_bit = 1 << BR_BOOLOPT_NO_LL_LEARN;
+ struct br_boolopt_multi *bm;
+
+ bm = RTA_DATA(tb[IFLA_BR_MULTI_BOOLOPT]);
+ if (bm->optmask & no_ll_learn_bit)
+ print_uint(PRINT_ANY,
+ "no_linklocal_learn",
+ "no_linklocal_learn %u ",
+ !!(bm->optval & no_ll_learn_bit));
+ if (bm->optmask & mcvl_bit)
+ print_uint(PRINT_ANY,
+ "mcast_vlan_snooping",
+ "mcast_vlan_snooping %u ",
+ !!(bm->optval & mcvl_bit));
+ }
+
+ if (tb[IFLA_BR_MCAST_ROUTER])
+ print_uint(PRINT_ANY,
+ "mcast_router",
+ "mcast_router %u ",
+ rta_getattr_u8(tb[IFLA_BR_MCAST_ROUTER]));
+
+ if (tb[IFLA_BR_MCAST_QUERY_USE_IFADDR])
+ print_uint(PRINT_ANY,
+ "mcast_query_use_ifaddr",
+ "mcast_query_use_ifaddr %u ",
+ rta_getattr_u8(tb[IFLA_BR_MCAST_QUERY_USE_IFADDR]));
+
+ if (tb[IFLA_BR_MCAST_QUERIER])
+ print_uint(PRINT_ANY,
+ "mcast_querier",
+ "mcast_querier %u ",
+ rta_getattr_u8(tb[IFLA_BR_MCAST_QUERIER]));
+
+ if (tb[IFLA_BR_MCAST_HASH_ELASTICITY])
+ print_uint(PRINT_ANY,
+ "mcast_hash_elasticity",
+ "mcast_hash_elasticity %u ",
+ rta_getattr_u32(tb[IFLA_BR_MCAST_HASH_ELASTICITY]));
+
+ if (tb[IFLA_BR_MCAST_HASH_MAX])
+ print_uint(PRINT_ANY,
+ "mcast_hash_max",
+ "mcast_hash_max %u ",
+ rta_getattr_u32(tb[IFLA_BR_MCAST_HASH_MAX]));
+
+ if (tb[IFLA_BR_MCAST_LAST_MEMBER_CNT])
+ print_uint(PRINT_ANY,
+ "mcast_last_member_cnt",
+ "mcast_last_member_count %u ",
+ rta_getattr_u32(tb[IFLA_BR_MCAST_LAST_MEMBER_CNT]));
+
+ if (tb[IFLA_BR_MCAST_STARTUP_QUERY_CNT])
+ print_uint(PRINT_ANY,
+ "mcast_startup_query_cnt",
+ "mcast_startup_query_count %u ",
+ rta_getattr_u32(tb[IFLA_BR_MCAST_STARTUP_QUERY_CNT]));
+
+ if (tb[IFLA_BR_MCAST_LAST_MEMBER_INTVL])
+ print_lluint(PRINT_ANY,
+ "mcast_last_member_intvl",
+ "mcast_last_member_interval %llu ",
+ rta_getattr_u64(tb[IFLA_BR_MCAST_LAST_MEMBER_INTVL]));
+
+ if (tb[IFLA_BR_MCAST_MEMBERSHIP_INTVL])
+ print_lluint(PRINT_ANY,
+ "mcast_membership_intvl",
+ "mcast_membership_interval %llu ",
+ rta_getattr_u64(tb[IFLA_BR_MCAST_MEMBERSHIP_INTVL]));
+
+ if (tb[IFLA_BR_MCAST_QUERIER_INTVL])
+ print_lluint(PRINT_ANY,
+ "mcast_querier_intvl",
+ "mcast_querier_interval %llu ",
+ rta_getattr_u64(tb[IFLA_BR_MCAST_QUERIER_INTVL]));
+
+ if (tb[IFLA_BR_MCAST_QUERY_INTVL])
+ print_lluint(PRINT_ANY,
+ "mcast_query_intvl",
+ "mcast_query_interval %llu ",
+ rta_getattr_u64(tb[IFLA_BR_MCAST_QUERY_INTVL]));
+
+ if (tb[IFLA_BR_MCAST_QUERY_RESPONSE_INTVL])
+ print_lluint(PRINT_ANY,
+ "mcast_query_response_intvl",
+ "mcast_query_response_interval %llu ",
+ rta_getattr_u64(tb[IFLA_BR_MCAST_QUERY_RESPONSE_INTVL]));
+
+ if (tb[IFLA_BR_MCAST_STARTUP_QUERY_INTVL])
+ print_lluint(PRINT_ANY,
+ "mcast_startup_query_intvl",
+ "mcast_startup_query_interval %llu ",
+ rta_getattr_u64(tb[IFLA_BR_MCAST_STARTUP_QUERY_INTVL]));
+
+ if (tb[IFLA_BR_MCAST_STATS_ENABLED])
+ print_uint(PRINT_ANY,
+ "mcast_stats_enabled",
+ "mcast_stats_enabled %u ",
+ rta_getattr_u8(tb[IFLA_BR_MCAST_STATS_ENABLED]));
+
+ if (tb[IFLA_BR_MCAST_IGMP_VERSION])
+ print_uint(PRINT_ANY,
+ "mcast_igmp_version",
+ "mcast_igmp_version %u ",
+ rta_getattr_u8(tb[IFLA_BR_MCAST_IGMP_VERSION]));
+
+ if (tb[IFLA_BR_MCAST_MLD_VERSION])
+ print_uint(PRINT_ANY,
+ "mcast_mld_version",
+ "mcast_mld_version %u ",
+ rta_getattr_u8(tb[IFLA_BR_MCAST_MLD_VERSION]));
+
+ if (tb[IFLA_BR_NF_CALL_IPTABLES])
+ print_uint(PRINT_ANY,
+ "nf_call_iptables",
+ "nf_call_iptables %u ",
+ rta_getattr_u8(tb[IFLA_BR_NF_CALL_IPTABLES]));
+
+ if (tb[IFLA_BR_NF_CALL_IP6TABLES])
+ print_uint(PRINT_ANY,
+ "nf_call_ip6tables",
+ "nf_call_ip6tables %u ",
+ rta_getattr_u8(tb[IFLA_BR_NF_CALL_IP6TABLES]));
+
+ if (tb[IFLA_BR_NF_CALL_ARPTABLES])
+ print_uint(PRINT_ANY,
+ "nf_call_arptables",
+ "nf_call_arptables %u ",
+ rta_getattr_u8(tb[IFLA_BR_NF_CALL_ARPTABLES]));
+}
+
+static void bridge_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ print_explain(f);
+}
+
+static void bridge_print_xstats_help(struct link_util *lu, FILE *f)
+{
+ fprintf(f, "Usage: ... %s [ igmp ] [ dev DEVICE ]\n", lu->id);
+}
+
+static void bridge_print_stats_mcast(const struct rtattr *attr)
+{
+ struct br_mcast_stats *mstats;
+
+ mstats = RTA_DATA(attr);
+ open_json_object("multicast");
+ open_json_object("igmp_queries");
+ print_string(PRINT_FP, NULL,
+ "%-16s IGMP queries:\n", "");
+ print_string(PRINT_FP, NULL, "%-16s ", "");
+ print_u64(PRINT_ANY, "rx_v1", "RX: v1 %llu ",
+ mstats->igmp_v1queries[BR_MCAST_DIR_RX]);
+ print_u64(PRINT_ANY, "rx_v2", "v2 %llu ",
+ mstats->igmp_v2queries[BR_MCAST_DIR_RX]);
+ print_u64(PRINT_ANY, "rx_v3", "v3 %llu\n",
+ mstats->igmp_v3queries[BR_MCAST_DIR_RX]);
+ print_string(PRINT_FP, NULL, "%-16s ", "");
+ print_u64(PRINT_ANY, "tx_v1", "TX: v1 %llu ",
+ mstats->igmp_v1queries[BR_MCAST_DIR_TX]);
+ print_u64(PRINT_ANY, "tx_v2", "v2 %llu ",
+ mstats->igmp_v2queries[BR_MCAST_DIR_TX]);
+ print_u64(PRINT_ANY, "tx_v3", "v3 %llu\n",
+ mstats->igmp_v3queries[BR_MCAST_DIR_TX]);
+ close_json_object();
+
+ open_json_object("igmp_reports");
+ print_string(PRINT_FP, NULL,
+ "%-16s IGMP reports:\n", "");
+ print_string(PRINT_FP, NULL, "%-16s ", "");
+ print_u64(PRINT_ANY, "rx_v1", "RX: v1 %llu ",
+ mstats->igmp_v1reports[BR_MCAST_DIR_RX]);
+ print_u64(PRINT_ANY, "rx_v2", "v2 %llu ",
+ mstats->igmp_v2reports[BR_MCAST_DIR_RX]);
+ print_u64(PRINT_ANY, "rx_v3", "v3 %llu\n",
+ mstats->igmp_v3reports[BR_MCAST_DIR_RX]);
+ print_string(PRINT_FP, NULL, "%-16s ", "");
+ print_u64(PRINT_ANY, "tx_v1", "TX: v1 %llu ",
+ mstats->igmp_v1reports[BR_MCAST_DIR_TX]);
+ print_u64(PRINT_ANY, "tx_v2", "v2 %llu ",
+ mstats->igmp_v2reports[BR_MCAST_DIR_TX]);
+ print_u64(PRINT_ANY, "tx_v3", "v3 %llu\n",
+ mstats->igmp_v3reports[BR_MCAST_DIR_TX]);
+ close_json_object();
+
+ open_json_object("igmp_leaves");
+ print_string(PRINT_FP, NULL,
+ "%-16s IGMP leaves: ", "");
+ print_u64(PRINT_ANY, "rx", "RX: %llu ",
+ mstats->igmp_leaves[BR_MCAST_DIR_RX]);
+ print_u64(PRINT_ANY, "tx", "TX: %llu\n",
+ mstats->igmp_leaves[BR_MCAST_DIR_TX]);
+ close_json_object();
+
+ print_string(PRINT_FP, NULL,
+ "%-16s IGMP parse errors: ", "");
+ print_u64(PRINT_ANY, "igmp_parse_errors", "%llu\n",
+ mstats->igmp_parse_errors);
+
+ open_json_object("mld_queries");
+ print_string(PRINT_FP, NULL,
+ "%-16s MLD queries:\n", "");
+ print_string(PRINT_FP, NULL, "%-16s ", "");
+ print_u64(PRINT_ANY, "rx_v1", "RX: v1 %llu ",
+ mstats->mld_v1queries[BR_MCAST_DIR_RX]);
+ print_u64(PRINT_ANY, "rx_v2", "v2 %llu\n",
+ mstats->mld_v2queries[BR_MCAST_DIR_RX]);
+ print_string(PRINT_FP, NULL, "%-16s ", "");
+ print_u64(PRINT_ANY, "tx_v1", "TX: v1 %llu ",
+ mstats->mld_v1queries[BR_MCAST_DIR_TX]);
+ print_u64(PRINT_ANY, "tx_v2", "v2 %llu\n",
+ mstats->mld_v2queries[BR_MCAST_DIR_TX]);
+ close_json_object();
+
+ open_json_object("mld_reports");
+ print_string(PRINT_FP, NULL,
+ "%-16s MLD reports:\n", "");
+ print_string(PRINT_FP, NULL, "%-16s ", "");
+ print_u64(PRINT_ANY, "rx_v1", "RX: v1 %llu ",
+ mstats->mld_v1reports[BR_MCAST_DIR_RX]);
+ print_u64(PRINT_ANY, "rx_v2", "v2 %llu\n",
+ mstats->mld_v2reports[BR_MCAST_DIR_RX]);
+ print_string(PRINT_FP, NULL, "%-16s ", "");
+ print_u64(PRINT_ANY, "tx_v1", "TX: v1 %llu ",
+ mstats->mld_v1reports[BR_MCAST_DIR_TX]);
+ print_u64(PRINT_ANY, "tx_v2", "v2 %llu\n",
+ mstats->mld_v2reports[BR_MCAST_DIR_TX]);
+ close_json_object();
+
+ open_json_object("mld_leaves");
+ print_string(PRINT_FP, NULL,
+ "%-16s MLD leaves: ", "");
+ print_u64(PRINT_ANY, "rx", "RX: %llu ",
+ mstats->mld_leaves[BR_MCAST_DIR_RX]);
+ print_u64(PRINT_ANY, "tx", "TX: %llu\n",
+ mstats->mld_leaves[BR_MCAST_DIR_TX]);
+ close_json_object();
+
+ print_string(PRINT_FP, NULL,
+ "%-16s MLD parse errors: ", "");
+ print_u64(PRINT_ANY, "mld_parse_errors", "%llu\n",
+ mstats->mld_parse_errors);
+ close_json_object();
+}
+
+static void bridge_print_stats_stp(const struct rtattr *attr)
+{
+ struct bridge_stp_xstats *sstats;
+
+ sstats = RTA_DATA(attr);
+ open_json_object("stp");
+ print_string(PRINT_FP, NULL,
+ "%-16s STP BPDU: ", "");
+ print_u64(PRINT_ANY, "rx_bpdu", "RX: %llu ",
+ sstats->rx_bpdu);
+ print_u64(PRINT_ANY, "tx_bpdu", "TX: %llu\n",
+ sstats->tx_bpdu);
+ print_string(PRINT_FP, NULL,
+ "%-16s STP TCN: ", "");
+ print_u64(PRINT_ANY, "rx_tcn", "RX: %llu ",
+ sstats->rx_tcn);
+ print_u64(PRINT_ANY, "tx_tcn", "TX: %llu\n",
+ sstats->tx_tcn);
+ print_string(PRINT_FP, NULL,
+ "%-16s STP Transitions: ", "");
+ print_u64(PRINT_ANY, "transition_blk", "Blocked: %llu ",
+ sstats->transition_blk);
+ print_u64(PRINT_ANY, "transition_fwd", "Forwarding: %llu\n",
+ sstats->transition_fwd);
+ close_json_object();
+}
+
+static void bridge_print_stats_attr(struct rtattr *attr, int ifindex)
+{
+ struct rtattr *brtb[LINK_XSTATS_TYPE_MAX+1];
+ struct rtattr *i, *list;
+ const char *ifname = "";
+ int rem;
+
+ parse_rtattr(brtb, LINK_XSTATS_TYPE_MAX, RTA_DATA(attr),
+ RTA_PAYLOAD(attr));
+ if (!brtb[LINK_XSTATS_TYPE_BRIDGE])
+ return;
+
+ list = brtb[LINK_XSTATS_TYPE_BRIDGE];
+ rem = RTA_PAYLOAD(list);
+ open_json_object(NULL);
+ ifname = ll_index_to_name(ifindex);
+ print_string(PRINT_ANY, "ifname", "%-16s\n", ifname);
+ for (i = RTA_DATA(list); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+ if (xstats_print_attr && i->rta_type != xstats_print_attr)
+ continue;
+ switch (i->rta_type) {
+ case BRIDGE_XSTATS_MCAST:
+ bridge_print_stats_mcast(i);
+ break;
+ case BRIDGE_XSTATS_STP:
+ bridge_print_stats_stp(i);
+ break;
+ }
+ }
+ close_json_object();
+}
+
+int bridge_print_xstats(struct nlmsghdr *n, void *arg)
+{
+ struct if_stats_msg *ifsm = NLMSG_DATA(n);
+ struct rtattr *tb[IFLA_STATS_MAX+1];
+ int len = n->nlmsg_len;
+
+ len -= NLMSG_LENGTH(sizeof(*ifsm));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+ if (filter_index && filter_index != ifsm->ifindex)
+ return 0;
+
+ parse_rtattr(tb, IFLA_STATS_MAX, IFLA_STATS_RTA(ifsm), len);
+ if (tb[IFLA_STATS_LINK_XSTATS])
+ bridge_print_stats_attr(tb[IFLA_STATS_LINK_XSTATS],
+ ifsm->ifindex);
+
+ if (tb[IFLA_STATS_LINK_XSTATS_SLAVE])
+ bridge_print_stats_attr(tb[IFLA_STATS_LINK_XSTATS_SLAVE],
+ ifsm->ifindex);
+
+ return 0;
+}
+
+int bridge_parse_xstats(struct link_util *lu, int argc, char **argv)
+{
+ while (argc > 0) {
+ if (strcmp(*argv, "igmp") == 0 || strcmp(*argv, "mcast") == 0) {
+ xstats_print_attr = BRIDGE_XSTATS_MCAST;
+ } else if (strcmp(*argv, "stp") == 0) {
+ xstats_print_attr = BRIDGE_XSTATS_STP;
+ } else if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ filter_index = ll_name_to_index(*argv);
+ if (!filter_index)
+ return nodev(*argv);
+ } else if (strcmp(*argv, "help") == 0) {
+ bridge_print_xstats_help(lu, stdout);
+ exit(0);
+ } else {
+ invarg("unknown attribute", *argv);
+ }
+ argc--; argv++;
+ }
+
+ return 0;
+}
+
+struct link_util bridge_link_util = {
+ .id = "bridge",
+ .maxattr = IFLA_BR_MAX,
+ .parse_opt = bridge_parse_opt,
+ .print_opt = bridge_print_opt,
+ .print_help = bridge_print_help,
+ .parse_ifla_xstats = bridge_parse_xstats,
+ .print_ifla_xstats = bridge_print_xstats,
+};
+
+static const struct ipstats_stat_desc_xstats
+ipstats_stat_desc_xstats_bridge_stp = {
+ .desc = IPSTATS_STAT_DESC_XSTATS_LEAF("stp"),
+ .xstats_at = IFLA_STATS_LINK_XSTATS,
+ .link_type_at = LINK_XSTATS_TYPE_BRIDGE,
+ .inner_max = BRIDGE_XSTATS_MAX,
+ .inner_at = BRIDGE_XSTATS_STP,
+ .show_cb = &bridge_print_stats_stp,
+};
+
+static const struct ipstats_stat_desc_xstats
+ipstats_stat_desc_xstats_bridge_mcast = {
+ .desc = IPSTATS_STAT_DESC_XSTATS_LEAF("mcast"),
+ .xstats_at = IFLA_STATS_LINK_XSTATS,
+ .link_type_at = LINK_XSTATS_TYPE_BRIDGE,
+ .inner_max = BRIDGE_XSTATS_MAX,
+ .inner_at = BRIDGE_XSTATS_MCAST,
+ .show_cb = &bridge_print_stats_mcast,
+};
+
+static const struct ipstats_stat_desc *
+ipstats_stat_desc_xstats_bridge_subs[] = {
+ &ipstats_stat_desc_xstats_bridge_stp.desc,
+ &ipstats_stat_desc_xstats_bridge_mcast.desc,
+};
+
+const struct ipstats_stat_desc ipstats_stat_desc_xstats_bridge_group = {
+ .name = "bridge",
+ .kind = IPSTATS_STAT_DESC_KIND_GROUP,
+ .subs = ipstats_stat_desc_xstats_bridge_subs,
+ .nsubs = ARRAY_SIZE(ipstats_stat_desc_xstats_bridge_subs),
+};
+
+static const struct ipstats_stat_desc_xstats
+ipstats_stat_desc_xstats_slave_bridge_stp = {
+ .desc = IPSTATS_STAT_DESC_XSTATS_LEAF("stp"),
+ .xstats_at = IFLA_STATS_LINK_XSTATS_SLAVE,
+ .link_type_at = LINK_XSTATS_TYPE_BRIDGE,
+ .inner_max = BRIDGE_XSTATS_MAX,
+ .inner_at = BRIDGE_XSTATS_STP,
+ .show_cb = &bridge_print_stats_stp,
+};
+
+static const struct ipstats_stat_desc_xstats
+ipstats_stat_desc_xstats_slave_bridge_mcast = {
+ .desc = IPSTATS_STAT_DESC_XSTATS_LEAF("mcast"),
+ .xstats_at = IFLA_STATS_LINK_XSTATS_SLAVE,
+ .link_type_at = LINK_XSTATS_TYPE_BRIDGE,
+ .inner_max = BRIDGE_XSTATS_MAX,
+ .inner_at = BRIDGE_XSTATS_MCAST,
+ .show_cb = &bridge_print_stats_mcast,
+};
+
+static const struct ipstats_stat_desc *
+ipstats_stat_desc_xstats_slave_bridge_subs[] = {
+ &ipstats_stat_desc_xstats_slave_bridge_stp.desc,
+ &ipstats_stat_desc_xstats_slave_bridge_mcast.desc,
+};
+
+const struct ipstats_stat_desc ipstats_stat_desc_xstats_slave_bridge_group = {
+ .name = "bridge",
+ .kind = IPSTATS_STAT_DESC_KIND_GROUP,
+ .subs = ipstats_stat_desc_xstats_slave_bridge_subs,
+ .nsubs = ARRAY_SIZE(ipstats_stat_desc_xstats_slave_bridge_subs),
+};
diff --git a/ip/iplink_bridge_slave.c b/ip/iplink_bridge_slave.c
new file mode 100644
index 0000000..3821923
--- /dev/null
+++ b/ip/iplink_bridge_slave.c
@@ -0,0 +1,488 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iplink_bridge_slave.c Bridge slave device support
+ *
+ * Authors: Jiri Pirko <jiri@resnulli.us>
+ */
+
+#include <stdio.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <linux/if_link.h>
+#include <linux/if_bridge.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_explain(FILE *f)
+{
+ fprintf(f,
+ "Usage: ... bridge_slave [ fdb_flush ]\n"
+ " [ state STATE ]\n"
+ " [ priority PRIO ]\n"
+ " [ cost COST ]\n"
+ " [ guard {on | off} ]\n"
+ " [ hairpin {on | off} ]\n"
+ " [ fastleave {on | off} ]\n"
+ " [ root_block {on | off} ]\n"
+ " [ learning {on | off} ]\n"
+ " [ flood {on | off} ]\n"
+ " [ proxy_arp {on | off} ]\n"
+ " [ proxy_arp_wifi {on | off} ]\n"
+ " [ mcast_router MULTICAST_ROUTER ]\n"
+ " [ mcast_fast_leave {on | off} ]\n"
+ " [ mcast_flood {on | off} ]\n"
+ " [ bcast_flood {on | off} ]\n"
+ " [ mcast_to_unicast {on | off} ]\n"
+ " [ group_fwd_mask MASK ]\n"
+ " [ neigh_suppress {on | off} ]\n"
+ " [ neigh_vlan_suppress {on | off} ]\n"
+ " [ vlan_tunnel {on | off} ]\n"
+ " [ isolated {on | off} ]\n"
+ " [ locked {on | off} ]\n"
+ " [ mab {on | off} ]\n"
+ " [ backup_port DEVICE ] [ nobackup_port ]\n"
+ " [ backup_nhid NHID ]\n"
+ );
+}
+
+static void explain(void)
+{
+ print_explain(stderr);
+}
+
+static const char *port_states[] = {
+ [BR_STATE_DISABLED] = "disabled",
+ [BR_STATE_LISTENING] = "listening",
+ [BR_STATE_LEARNING] = "learning",
+ [BR_STATE_FORWARDING] = "forwarding",
+ [BR_STATE_BLOCKING] = "blocking",
+};
+
+static const char *fwd_mask_tbl[16] = {
+ [0] = "stp",
+ [2] = "lacp",
+ [14] = "lldp"
+};
+
+static void print_portstate(FILE *f, __u8 state)
+{
+ if (state <= BR_STATE_BLOCKING)
+ print_string(PRINT_ANY,
+ "state",
+ "state %s ",
+ port_states[state]);
+ else
+ print_int(PRINT_ANY, "state_index", "state (%d) ", state);
+}
+
+static void _print_timer(FILE *f, const char *attr, struct rtattr *timer)
+{
+ struct timeval tv;
+
+ __jiffies_to_tv(&tv, rta_getattr_u64(timer));
+ if (is_json_context()) {
+ json_writer_t *jw = get_json_writer();
+
+ jsonw_name(jw, attr);
+ jsonw_printf(jw, "%i.%.2i",
+ (int)tv.tv_sec, (int)tv.tv_usec / 10000);
+ } else {
+ fprintf(f, "%s %4i.%.2i ", attr, (int)tv.tv_sec,
+ (int)tv.tv_usec / 10000);
+ }
+}
+
+static void _bitmask2str(__u16 bitmask, char *dst, size_t dst_size,
+ const char **tbl)
+{
+ int len, i;
+
+ for (i = 0, len = 0; bitmask; i++, bitmask >>= 1) {
+ int n;
+
+ if (bitmask & 0x1) {
+ if (tbl[i])
+ n = snprintf(dst + len, dst_size - len, "%s,",
+ tbl[i]);
+ else
+ n = snprintf(dst + len, dst_size - len, "0x%x,",
+ (1 << i));
+
+ if (n < 0 || n >= dst_size - len)
+ break;
+
+ len += n;
+ }
+ }
+
+ if (!len)
+ snprintf(dst, dst_size, "0x0");
+ else
+ dst[len - 1] = 0;
+}
+
+static void bridge_slave_print_opt(struct link_util *lu, FILE *f,
+ struct rtattr *tb[])
+{
+ if (!tb)
+ return;
+
+ if (tb[IFLA_BRPORT_STATE])
+ print_portstate(f, rta_getattr_u8(tb[IFLA_BRPORT_STATE]));
+
+ if (tb[IFLA_BRPORT_PRIORITY])
+ print_int(PRINT_ANY,
+ "priority",
+ "priority %d ",
+ rta_getattr_u16(tb[IFLA_BRPORT_PRIORITY]));
+
+ if (tb[IFLA_BRPORT_COST])
+ print_int(PRINT_ANY,
+ "cost",
+ "cost %d ",
+ rta_getattr_u32(tb[IFLA_BRPORT_COST]));
+
+ if (tb[IFLA_BRPORT_MODE])
+ print_on_off(PRINT_ANY, "hairpin", "hairpin %s ",
+ rta_getattr_u8(tb[IFLA_BRPORT_MODE]));
+
+ if (tb[IFLA_BRPORT_GUARD])
+ print_on_off(PRINT_ANY, "guard", "guard %s ",
+ rta_getattr_u8(tb[IFLA_BRPORT_GUARD]));
+
+ if (tb[IFLA_BRPORT_PROTECT])
+ print_on_off(PRINT_ANY, "root_block", "root_block %s ",
+ rta_getattr_u8(tb[IFLA_BRPORT_PROTECT]));
+
+ if (tb[IFLA_BRPORT_FAST_LEAVE])
+ print_on_off(PRINT_ANY, "fastleave", "fastleave %s ",
+ rta_getattr_u8(tb[IFLA_BRPORT_FAST_LEAVE]));
+
+ if (tb[IFLA_BRPORT_LEARNING])
+ print_on_off(PRINT_ANY, "learning", "learning %s ",
+ rta_getattr_u8(tb[IFLA_BRPORT_LEARNING]));
+
+ if (tb[IFLA_BRPORT_UNICAST_FLOOD])
+ print_on_off(PRINT_ANY, "flood", "flood %s ",
+ rta_getattr_u8(tb[IFLA_BRPORT_UNICAST_FLOOD]));
+
+ if (tb[IFLA_BRPORT_ID])
+ print_0xhex(PRINT_ANY, "id", "port_id %#llx ",
+ rta_getattr_u16(tb[IFLA_BRPORT_ID]));
+
+ if (tb[IFLA_BRPORT_NO])
+ print_0xhex(PRINT_ANY, "no", "port_no %#llx ",
+ rta_getattr_u16(tb[IFLA_BRPORT_NO]));
+
+ if (tb[IFLA_BRPORT_DESIGNATED_PORT])
+ print_uint(PRINT_ANY,
+ "designated_port",
+ "designated_port %u ",
+ rta_getattr_u16(tb[IFLA_BRPORT_DESIGNATED_PORT]));
+
+ if (tb[IFLA_BRPORT_DESIGNATED_COST])
+ print_uint(PRINT_ANY,
+ "designated_cost",
+ "designated_cost %u ",
+ rta_getattr_u16(tb[IFLA_BRPORT_DESIGNATED_COST]));
+
+ if (tb[IFLA_BRPORT_BRIDGE_ID]) {
+ char bridge_id[32];
+
+ br_dump_bridge_id(RTA_DATA(tb[IFLA_BRPORT_BRIDGE_ID]),
+ bridge_id, sizeof(bridge_id));
+ print_string(PRINT_ANY,
+ "bridge_id",
+ "designated_bridge %s ",
+ bridge_id);
+ }
+
+ if (tb[IFLA_BRPORT_ROOT_ID]) {
+ char root_id[32];
+
+ br_dump_bridge_id(RTA_DATA(tb[IFLA_BRPORT_ROOT_ID]),
+ root_id, sizeof(root_id));
+ print_string(PRINT_ANY,
+ "root_id",
+ "designated_root %s ", root_id);
+ }
+
+ if (tb[IFLA_BRPORT_HOLD_TIMER])
+ _print_timer(f, "hold_timer", tb[IFLA_BRPORT_HOLD_TIMER]);
+
+ if (tb[IFLA_BRPORT_MESSAGE_AGE_TIMER])
+ _print_timer(f, "message_age_timer",
+ tb[IFLA_BRPORT_MESSAGE_AGE_TIMER]);
+
+ if (tb[IFLA_BRPORT_FORWARD_DELAY_TIMER])
+ _print_timer(f, "forward_delay_timer",
+ tb[IFLA_BRPORT_FORWARD_DELAY_TIMER]);
+
+ if (tb[IFLA_BRPORT_TOPOLOGY_CHANGE_ACK])
+ print_uint(PRINT_ANY,
+ "topology_change_ack",
+ "topology_change_ack %u ",
+ rta_getattr_u8(tb[IFLA_BRPORT_TOPOLOGY_CHANGE_ACK]));
+
+ if (tb[IFLA_BRPORT_CONFIG_PENDING])
+ print_uint(PRINT_ANY,
+ "config_pending",
+ "config_pending %u ",
+ rta_getattr_u8(tb[IFLA_BRPORT_CONFIG_PENDING]));
+
+ if (tb[IFLA_BRPORT_PROXYARP])
+ print_on_off(PRINT_ANY, "proxy_arp", "proxy_arp %s ",
+ rta_getattr_u8(tb[IFLA_BRPORT_PROXYARP]));
+
+ if (tb[IFLA_BRPORT_PROXYARP_WIFI])
+ print_on_off(PRINT_ANY, "proxy_arp_wifi", "proxy_arp_wifi %s ",
+ rta_getattr_u8(tb[IFLA_BRPORT_PROXYARP_WIFI]));
+
+ if (tb[IFLA_BRPORT_MULTICAST_ROUTER])
+ print_uint(PRINT_ANY,
+ "multicast_router",
+ "mcast_router %u ",
+ rta_getattr_u8(tb[IFLA_BRPORT_MULTICAST_ROUTER]));
+
+ if (tb[IFLA_BRPORT_FAST_LEAVE])
+ // not printing any json here because
+ // we already printed fast_leave before
+ print_string(PRINT_FP,
+ NULL,
+ "mcast_fast_leave %s ",
+ rta_getattr_u8(tb[IFLA_BRPORT_FAST_LEAVE]) ? "on" : "off");
+
+ if (tb[IFLA_BRPORT_MCAST_FLOOD])
+ print_on_off(PRINT_ANY, "mcast_flood", "mcast_flood %s ",
+ rta_getattr_u8(tb[IFLA_BRPORT_MCAST_FLOOD]));
+
+ if (tb[IFLA_BRPORT_BCAST_FLOOD])
+ print_on_off(PRINT_ANY, "bcast_flood", "bcast_flood %s ",
+ rta_getattr_u8(tb[IFLA_BRPORT_BCAST_FLOOD]));
+
+ if (tb[IFLA_BRPORT_MCAST_TO_UCAST])
+ print_on_off(PRINT_ANY, "mcast_to_unicast", "mcast_to_unicast %s ",
+ rta_getattr_u8(tb[IFLA_BRPORT_MCAST_TO_UCAST]));
+
+ if (tb[IFLA_BRPORT_NEIGH_SUPPRESS])
+ print_on_off(PRINT_ANY, "neigh_suppress", "neigh_suppress %s ",
+ rta_getattr_u8(tb[IFLA_BRPORT_NEIGH_SUPPRESS]));
+
+ if (tb[IFLA_BRPORT_NEIGH_VLAN_SUPPRESS])
+ print_on_off(PRINT_ANY, "neigh_vlan_suppress",
+ "neigh_vlan_suppress %s ",
+ rta_getattr_u8(tb[IFLA_BRPORT_NEIGH_VLAN_SUPPRESS]));
+
+ if (tb[IFLA_BRPORT_GROUP_FWD_MASK]) {
+ char convbuf[256];
+ __u16 fwd_mask;
+
+ fwd_mask = rta_getattr_u16(tb[IFLA_BRPORT_GROUP_FWD_MASK]);
+ print_0xhex(PRINT_ANY, "group_fwd_mask",
+ "group_fwd_mask %#llx ", fwd_mask);
+ _bitmask2str(fwd_mask, convbuf, sizeof(convbuf), fwd_mask_tbl);
+ print_string(PRINT_ANY, "group_fwd_mask_str",
+ "group_fwd_mask_str %s ", convbuf);
+ }
+
+ if (tb[IFLA_BRPORT_VLAN_TUNNEL])
+ print_on_off(PRINT_ANY, "vlan_tunnel", "vlan_tunnel %s ",
+ rta_getattr_u8(tb[IFLA_BRPORT_VLAN_TUNNEL]));
+
+ if (tb[IFLA_BRPORT_ISOLATED])
+ print_on_off(PRINT_ANY, "isolated", "isolated %s ",
+ rta_getattr_u8(tb[IFLA_BRPORT_ISOLATED]));
+
+ if (tb[IFLA_BRPORT_LOCKED])
+ print_on_off(PRINT_ANY, "locked", "locked %s ",
+ rta_getattr_u8(tb[IFLA_BRPORT_LOCKED]));
+
+ if (tb[IFLA_BRPORT_MAB])
+ print_on_off(PRINT_ANY, "mab", "mab %s ",
+ rta_getattr_u8(tb[IFLA_BRPORT_MAB]));
+
+ if (tb[IFLA_BRPORT_BACKUP_PORT]) {
+ int backup_p = rta_getattr_u32(tb[IFLA_BRPORT_BACKUP_PORT]);
+
+ print_string(PRINT_ANY, "backup_port", "backup_port %s ",
+ ll_index_to_name(backup_p));
+ }
+
+ if (tb[IFLA_BRPORT_BACKUP_NHID])
+ print_uint(PRINT_ANY, "backup_nhid", "backup_nhid %u ",
+ rta_getattr_u32(tb[IFLA_BRPORT_BACKUP_NHID]));
+}
+
+static void bridge_slave_parse_on_off(char *arg_name, char *arg_val,
+ struct nlmsghdr *n, int type)
+{
+ int ret;
+ __u8 val = parse_on_off(arg_name, arg_val, &ret);
+
+ if (ret)
+ exit(1);
+ addattr8(n, 1024, type, val);
+}
+
+static int bridge_slave_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ __u8 state;
+ __u16 priority;
+ __u32 cost;
+
+ while (argc > 0) {
+ if (matches(*argv, "fdb_flush") == 0) {
+ addattr(n, 1024, IFLA_BRPORT_FLUSH);
+ } else if (matches(*argv, "state") == 0) {
+ NEXT_ARG();
+ if (get_u8(&state, *argv, 0))
+ invarg("state is invalid", *argv);
+ addattr8(n, 1024, IFLA_BRPORT_STATE, state);
+ } else if (matches(*argv, "priority") == 0) {
+ NEXT_ARG();
+ if (get_u16(&priority, *argv, 0))
+ invarg("priority is invalid", *argv);
+ addattr16(n, 1024, IFLA_BRPORT_PRIORITY, priority);
+ } else if (matches(*argv, "cost") == 0) {
+ NEXT_ARG();
+ if (get_u32(&cost, *argv, 0))
+ invarg("cost is invalid", *argv);
+ addattr32(n, 1024, IFLA_BRPORT_COST, cost);
+ } else if (matches(*argv, "hairpin") == 0) {
+ NEXT_ARG();
+ bridge_slave_parse_on_off("hairpin", *argv, n,
+ IFLA_BRPORT_MODE);
+ } else if (matches(*argv, "guard") == 0) {
+ NEXT_ARG();
+ bridge_slave_parse_on_off("guard", *argv, n,
+ IFLA_BRPORT_GUARD);
+ } else if (matches(*argv, "root_block") == 0) {
+ NEXT_ARG();
+ bridge_slave_parse_on_off("root_block", *argv, n,
+ IFLA_BRPORT_PROTECT);
+ } else if (matches(*argv, "fastleave") == 0) {
+ NEXT_ARG();
+ bridge_slave_parse_on_off("fastleave", *argv, n,
+ IFLA_BRPORT_FAST_LEAVE);
+ } else if (matches(*argv, "learning") == 0) {
+ NEXT_ARG();
+ bridge_slave_parse_on_off("learning", *argv, n,
+ IFLA_BRPORT_LEARNING);
+ } else if (matches(*argv, "flood") == 0) {
+ NEXT_ARG();
+ bridge_slave_parse_on_off("flood", *argv, n,
+ IFLA_BRPORT_UNICAST_FLOOD);
+ } else if (matches(*argv, "mcast_flood") == 0) {
+ NEXT_ARG();
+ bridge_slave_parse_on_off("mcast_flood", *argv, n,
+ IFLA_BRPORT_MCAST_FLOOD);
+ } else if (matches(*argv, "bcast_flood") == 0) {
+ NEXT_ARG();
+ bridge_slave_parse_on_off("bcast_flood", *argv, n,
+ IFLA_BRPORT_BCAST_FLOOD);
+ } else if (matches(*argv, "mcast_to_unicast") == 0) {
+ NEXT_ARG();
+ bridge_slave_parse_on_off("mcast_to_unicast", *argv, n,
+ IFLA_BRPORT_MCAST_TO_UCAST);
+ } else if (matches(*argv, "proxy_arp") == 0) {
+ NEXT_ARG();
+ bridge_slave_parse_on_off("proxy_arp", *argv, n,
+ IFLA_BRPORT_PROXYARP);
+ } else if (matches(*argv, "proxy_arp_wifi") == 0) {
+ NEXT_ARG();
+ bridge_slave_parse_on_off("proxy_arp_wifi", *argv, n,
+ IFLA_BRPORT_PROXYARP_WIFI);
+ } else if (matches(*argv, "mcast_router") == 0) {
+ __u8 mcast_router;
+
+ NEXT_ARG();
+ if (get_u8(&mcast_router, *argv, 0))
+ invarg("invalid mcast_router", *argv);
+ addattr8(n, 1024, IFLA_BRPORT_MULTICAST_ROUTER,
+ mcast_router);
+ } else if (matches(*argv, "mcast_fast_leave") == 0) {
+ NEXT_ARG();
+ bridge_slave_parse_on_off("mcast_fast_leave", *argv, n,
+ IFLA_BRPORT_FAST_LEAVE);
+ } else if (matches(*argv, "neigh_suppress") == 0) {
+ NEXT_ARG();
+ bridge_slave_parse_on_off("neigh_suppress", *argv, n,
+ IFLA_BRPORT_NEIGH_SUPPRESS);
+ } else if (strcmp(*argv, "neigh_vlan_suppress") == 0) {
+ NEXT_ARG();
+ bridge_slave_parse_on_off("neigh_vlan_suppress", *argv,
+ n, IFLA_BRPORT_NEIGH_VLAN_SUPPRESS);
+ } else if (matches(*argv, "group_fwd_mask") == 0) {
+ __u16 mask;
+
+ NEXT_ARG();
+ if (get_u16(&mask, *argv, 0))
+ invarg("invalid group_fwd_mask", *argv);
+ addattr16(n, 1024, IFLA_BRPORT_GROUP_FWD_MASK, mask);
+ } else if (matches(*argv, "vlan_tunnel") == 0) {
+ NEXT_ARG();
+ bridge_slave_parse_on_off("vlan_tunnel", *argv, n,
+ IFLA_BRPORT_VLAN_TUNNEL);
+ } else if (matches(*argv, "isolated") == 0) {
+ NEXT_ARG();
+ bridge_slave_parse_on_off("isolated", *argv, n,
+ IFLA_BRPORT_ISOLATED);
+ } else if (matches(*argv, "locked") == 0) {
+ NEXT_ARG();
+ bridge_slave_parse_on_off("locked", *argv, n,
+ IFLA_BRPORT_LOCKED);
+ } else if (strcmp(*argv, "mab") == 0) {
+ NEXT_ARG();
+ bridge_slave_parse_on_off("mab", *argv, n,
+ IFLA_BRPORT_MAB);
+ } else if (matches(*argv, "backup_port") == 0) {
+ int ifindex;
+
+ NEXT_ARG();
+ ifindex = ll_name_to_index(*argv);
+ if (!ifindex)
+ invarg("Device does not exist\n", *argv);
+ addattr32(n, 1024, IFLA_BRPORT_BACKUP_PORT, ifindex);
+ } else if (matches(*argv, "nobackup_port") == 0) {
+ addattr32(n, 1024, IFLA_BRPORT_BACKUP_PORT, 0);
+ } else if (strcmp(*argv, "backup_nhid") == 0) {
+ __u32 backup_nhid;
+
+ NEXT_ARG();
+ if (get_u32(&backup_nhid, *argv, 0))
+ invarg("backup_nhid is invalid", *argv);
+ addattr32(n, 1024, IFLA_BRPORT_BACKUP_NHID,
+ backup_nhid);
+ } else if (matches(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "bridge_slave: unknown option \"%s\"?\n",
+ *argv);
+ explain();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ return 0;
+}
+
+static void bridge_slave_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ print_explain(f);
+}
+
+struct link_util bridge_slave_link_util = {
+ .id = "bridge_slave",
+ .maxattr = IFLA_BRPORT_MAX,
+ .print_opt = bridge_slave_print_opt,
+ .parse_opt = bridge_slave_parse_opt,
+ .print_help = bridge_slave_print_help,
+ .parse_ifla_xstats = bridge_parse_xstats,
+ .print_ifla_xstats = bridge_print_xstats,
+};
diff --git a/ip/iplink_can.c b/ip/iplink_can.c
new file mode 100644
index 0000000..f2967db
--- /dev/null
+++ b/ip/iplink_can.c
@@ -0,0 +1,670 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iplink_can.c CAN device support
+ *
+ * Authors: Wolfgang Grandegger <wg@grandegger.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <linux/can/netlink.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_usage(FILE *f)
+{
+ fprintf(f,
+ "Usage: ip link set DEVICE type can\n"
+ "\t[ bitrate BITRATE [ sample-point SAMPLE-POINT] ] |\n"
+ "\t[ tq TQ prop-seg PROP_SEG phase-seg1 PHASE-SEG1\n \t phase-seg2 PHASE-SEG2 [ sjw SJW ] ]\n"
+ "\n"
+ "\t[ dbitrate BITRATE [ dsample-point SAMPLE-POINT] ] |\n"
+ "\t[ dtq TQ dprop-seg PROP_SEG dphase-seg1 PHASE-SEG1\n \t dphase-seg2 PHASE-SEG2 [ dsjw SJW ] ]\n"
+ "\t[ tdcv TDCV tdco TDCO tdcf TDCF ]\n"
+ "\n"
+ "\t[ loopback { on | off } ]\n"
+ "\t[ listen-only { on | off } ]\n"
+ "\t[ triple-sampling { on | off } ]\n"
+ "\t[ one-shot { on | off } ]\n"
+ "\t[ berr-reporting { on | off } ]\n"
+ "\t[ fd { on | off } ]\n"
+ "\t[ fd-non-iso { on | off } ]\n"
+ "\t[ presume-ack { on | off } ]\n"
+ "\t[ cc-len8-dlc { on | off } ]\n"
+ "\t[ tdc-mode { auto | manual | off } ]\n"
+ "\n"
+ "\t[ restart-ms TIME-MS ]\n"
+ "\t[ restart ]\n"
+ "\n"
+ "\t[ termination { 0..65535 } ]\n"
+ "\n"
+ "\tWhere: BITRATE := { NUMBER in bps }\n"
+ "\t SAMPLE-POINT := { 0.000..0.999 }\n"
+ "\t TQ := { NUMBER in ns }\n"
+ "\t PROP-SEG := { NUMBER in tq }\n"
+ "\t PHASE-SEG1 := { NUMBER in tq }\n"
+ "\t PHASE-SEG2 := { NUMBER in tq }\n"
+ "\t SJW := { NUMBER in tq }\n"
+ "\t TDCV := { NUMBER in tc }\n"
+ "\t TDCO := { NUMBER in tc }\n"
+ "\t TDCF := { NUMBER in tc }\n"
+ "\t RESTART-MS := { 0 | NUMBER in ms }\n"
+ );
+}
+
+static void usage(void)
+{
+ print_usage(stderr);
+}
+
+static int get_float(float *val, const char *arg)
+{
+ float res;
+ char *ptr;
+
+ if (!arg || !*arg)
+ return -1;
+ res = strtof(arg, &ptr);
+ if (!ptr || ptr == arg || *ptr)
+ return -1;
+ *val = res;
+ return 0;
+}
+
+static void set_ctrlmode(char *name, char *arg,
+ struct can_ctrlmode *cm, __u32 flags)
+{
+ if (strcmp(arg, "on") == 0) {
+ cm->flags |= flags;
+ } else if (strcmp(arg, "off") != 0) {
+ fprintf(stderr,
+ "Error: argument of \"%s\" must be \"on\" or \"off\", not \"%s\"\n",
+ name, arg);
+ exit(-1);
+ }
+ cm->mask |= flags;
+}
+
+static void print_flag(enum output_type t, __u32 *flags, __u32 flag,
+ const char* name)
+{
+ if (*flags & flag) {
+ *flags &= ~flag;
+ print_string(t, NULL, *flags ? "%s," : "%s", name);
+ }
+}
+
+static void print_ctrlmode(enum output_type t, __u32 flags, const char* key)
+{
+ if (!flags)
+ return;
+
+ open_json_array(t, is_json_context() ? key : "<");
+
+ print_flag(t, &flags, CAN_CTRLMODE_LOOPBACK, "LOOPBACK");
+ print_flag(t, &flags, CAN_CTRLMODE_LISTENONLY, "LISTEN-ONLY");
+ print_flag(t, &flags, CAN_CTRLMODE_3_SAMPLES, "TRIPLE-SAMPLING");
+ print_flag(t, &flags, CAN_CTRLMODE_ONE_SHOT, "ONE-SHOT");
+ print_flag(t, &flags, CAN_CTRLMODE_BERR_REPORTING, "BERR-REPORTING");
+ print_flag(t, &flags, CAN_CTRLMODE_FD, "FD");
+ print_flag(t, &flags, CAN_CTRLMODE_FD_NON_ISO, "FD-NON-ISO");
+ print_flag(t, &flags, CAN_CTRLMODE_PRESUME_ACK, "PRESUME-ACK");
+ print_flag(t, &flags, CAN_CTRLMODE_CC_LEN8_DLC, "CC-LEN8-DLC");
+ print_flag(t, &flags, CAN_CTRLMODE_TDC_AUTO, "TDC-AUTO");
+ print_flag(t, &flags, CAN_CTRLMODE_TDC_MANUAL, "TDC-MANUAL");
+
+ if (flags)
+ print_hex(t, NULL, "%x", flags);
+
+ close_json_array(t, "> ");
+}
+
+static int can_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ struct can_bittiming bt = {}, dbt = {};
+ struct can_ctrlmode cm = { 0 };
+ struct rtattr *tdc;
+ __u32 tdcv = -1, tdco = -1, tdcf = -1;
+
+ while (argc > 0) {
+ if (matches(*argv, "bitrate") == 0) {
+ NEXT_ARG();
+ if (get_u32(&bt.bitrate, *argv, 0))
+ invarg("invalid \"bitrate\" value\n", *argv);
+ } else if (matches(*argv, "sample-point") == 0) {
+ float sp;
+
+ NEXT_ARG();
+ if (get_float(&sp, *argv))
+ invarg("invalid \"sample-point\" value\n",
+ *argv);
+ bt.sample_point = (__u32)(sp * 1000);
+ } else if (matches(*argv, "tq") == 0) {
+ NEXT_ARG();
+ if (get_u32(&bt.tq, *argv, 0))
+ invarg("invalid \"tq\" value\n", *argv);
+ } else if (matches(*argv, "prop-seg") == 0) {
+ NEXT_ARG();
+ if (get_u32(&bt.prop_seg, *argv, 0))
+ invarg("invalid \"prop-seg\" value\n", *argv);
+ } else if (matches(*argv, "phase-seg1") == 0) {
+ NEXT_ARG();
+ if (get_u32(&bt.phase_seg1, *argv, 0))
+ invarg("invalid \"phase-seg1\" value\n", *argv);
+ } else if (matches(*argv, "phase-seg2") == 0) {
+ NEXT_ARG();
+ if (get_u32(&bt.phase_seg2, *argv, 0))
+ invarg("invalid \"phase-seg2\" value\n", *argv);
+ } else if (matches(*argv, "sjw") == 0) {
+ NEXT_ARG();
+ if (get_u32(&bt.sjw, *argv, 0))
+ invarg("invalid \"sjw\" value\n", *argv);
+ } else if (matches(*argv, "dbitrate") == 0) {
+ NEXT_ARG();
+ if (get_u32(&dbt.bitrate, *argv, 0))
+ invarg("invalid \"dbitrate\" value\n", *argv);
+ } else if (matches(*argv, "dsample-point") == 0) {
+ float sp;
+
+ NEXT_ARG();
+ if (get_float(&sp, *argv))
+ invarg("invalid \"dsample-point\" value\n", *argv);
+ dbt.sample_point = (__u32)(sp * 1000);
+ } else if (matches(*argv, "dtq") == 0) {
+ NEXT_ARG();
+ if (get_u32(&dbt.tq, *argv, 0))
+ invarg("invalid \"dtq\" value\n", *argv);
+ } else if (matches(*argv, "dprop-seg") == 0) {
+ NEXT_ARG();
+ if (get_u32(&dbt.prop_seg, *argv, 0))
+ invarg("invalid \"dprop-seg\" value\n", *argv);
+ } else if (matches(*argv, "dphase-seg1") == 0) {
+ NEXT_ARG();
+ if (get_u32(&dbt.phase_seg1, *argv, 0))
+ invarg("invalid \"dphase-seg1\" value\n", *argv);
+ } else if (matches(*argv, "dphase-seg2") == 0) {
+ NEXT_ARG();
+ if (get_u32(&dbt.phase_seg2, *argv, 0))
+ invarg("invalid \"dphase-seg2\" value\n", *argv);
+ } else if (matches(*argv, "dsjw") == 0) {
+ NEXT_ARG();
+ if (get_u32(&dbt.sjw, *argv, 0))
+ invarg("invalid \"dsjw\" value\n", *argv);
+ } else if (matches(*argv, "tdcv") == 0) {
+ NEXT_ARG();
+ if (get_u32(&tdcv, *argv, 0))
+ invarg("invalid \"tdcv\" value\n", *argv);
+ } else if (matches(*argv, "tdco") == 0) {
+ NEXT_ARG();
+ if (get_u32(&tdco, *argv, 0))
+ invarg("invalid \"tdco\" value\n", *argv);
+ } else if (matches(*argv, "tdcf") == 0) {
+ NEXT_ARG();
+ if (get_u32(&tdcf, *argv, 0))
+ invarg("invalid \"tdcf\" value\n", *argv);
+ } else if (matches(*argv, "loopback") == 0) {
+ NEXT_ARG();
+ set_ctrlmode("loopback", *argv, &cm,
+ CAN_CTRLMODE_LOOPBACK);
+ } else if (matches(*argv, "listen-only") == 0) {
+ NEXT_ARG();
+ set_ctrlmode("listen-only", *argv, &cm,
+ CAN_CTRLMODE_LISTENONLY);
+ } else if (matches(*argv, "triple-sampling") == 0) {
+ NEXT_ARG();
+ set_ctrlmode("triple-sampling", *argv, &cm,
+ CAN_CTRLMODE_3_SAMPLES);
+ } else if (matches(*argv, "one-shot") == 0) {
+ NEXT_ARG();
+ set_ctrlmode("one-shot", *argv, &cm,
+ CAN_CTRLMODE_ONE_SHOT);
+ } else if (matches(*argv, "berr-reporting") == 0) {
+ NEXT_ARG();
+ set_ctrlmode("berr-reporting", *argv, &cm,
+ CAN_CTRLMODE_BERR_REPORTING);
+ } else if (matches(*argv, "fd") == 0) {
+ NEXT_ARG();
+ set_ctrlmode("fd", *argv, &cm,
+ CAN_CTRLMODE_FD);
+ } else if (matches(*argv, "fd-non-iso") == 0) {
+ NEXT_ARG();
+ set_ctrlmode("fd-non-iso", *argv, &cm,
+ CAN_CTRLMODE_FD_NON_ISO);
+ } else if (matches(*argv, "presume-ack") == 0) {
+ NEXT_ARG();
+ set_ctrlmode("presume-ack", *argv, &cm,
+ CAN_CTRLMODE_PRESUME_ACK);
+ } else if (matches(*argv, "cc-len8-dlc") == 0) {
+ NEXT_ARG();
+ set_ctrlmode("cc-len8-dlc", *argv, &cm,
+ CAN_CTRLMODE_CC_LEN8_DLC);
+ } else if (matches(*argv, "tdc-mode") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "auto") == 0) {
+ cm.flags |= CAN_CTRLMODE_TDC_AUTO;
+ cm.mask |= CAN_CTRLMODE_TDC_AUTO;
+ } else if (strcmp(*argv, "manual") == 0) {
+ cm.flags |= CAN_CTRLMODE_TDC_MANUAL;
+ cm.mask |= CAN_CTRLMODE_TDC_MANUAL;
+ } else if (strcmp(*argv, "off") == 0) {
+ cm.mask |= CAN_CTRLMODE_TDC_AUTO |
+ CAN_CTRLMODE_TDC_MANUAL;
+ } else {
+ fprintf(stderr,
+ "Error: argument of \"tdc-mode\" must be \"auto\", \"manual\" or \"off\", not \"%s\"\n",
+ *argv);
+ exit (-1);
+ }
+ } else if (matches(*argv, "restart") == 0) {
+ __u32 val = 1;
+
+ addattr32(n, 1024, IFLA_CAN_RESTART, val);
+ } else if (matches(*argv, "restart-ms") == 0) {
+ __u32 val;
+
+ NEXT_ARG();
+ if (get_u32(&val, *argv, 0))
+ invarg("invalid \"restart-ms\" value\n", *argv);
+ addattr32(n, 1024, IFLA_CAN_RESTART_MS, val);
+ } else if (matches(*argv, "termination") == 0) {
+ __u16 val;
+
+ NEXT_ARG();
+ if (get_u16(&val, *argv, 0))
+ invarg("invalid \"termination\" value\n",
+ *argv);
+ addattr16(n, 1024, IFLA_CAN_TERMINATION, val);
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ return -1;
+ } else {
+ fprintf(stderr, "can: unknown option \"%s\"\n", *argv);
+ usage();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ if (bt.bitrate || bt.tq)
+ addattr_l(n, 1024, IFLA_CAN_BITTIMING, &bt, sizeof(bt));
+ if (dbt.bitrate || dbt.tq)
+ addattr_l(n, 1024, IFLA_CAN_DATA_BITTIMING, &dbt, sizeof(dbt));
+ if (cm.mask)
+ addattr_l(n, 1024, IFLA_CAN_CTRLMODE, &cm, sizeof(cm));
+
+ if (tdcv != -1 || tdco != -1 || tdcf != -1) {
+ tdc = addattr_nest(n, 1024, IFLA_CAN_TDC | NLA_F_NESTED);
+ if (tdcv != -1)
+ addattr32(n, 1024, IFLA_CAN_TDC_TDCV, tdcv);
+ if (tdco != -1)
+ addattr32(n, 1024, IFLA_CAN_TDC_TDCO, tdco);
+ if (tdcf != -1)
+ addattr32(n, 1024, IFLA_CAN_TDC_TDCF, tdcf);
+ addattr_nest_end(n, tdc);
+ }
+
+ return 0;
+}
+
+static const char *can_state_names[CAN_STATE_MAX] = {
+ [CAN_STATE_ERROR_ACTIVE] = "ERROR-ACTIVE",
+ [CAN_STATE_ERROR_WARNING] = "ERROR-WARNING",
+ [CAN_STATE_ERROR_PASSIVE] = "ERROR-PASSIVE",
+ [CAN_STATE_BUS_OFF] = "BUS-OFF",
+ [CAN_STATE_STOPPED] = "STOPPED",
+ [CAN_STATE_SLEEPING] = "SLEEPING"
+};
+
+static void can_print_nl_indent(void)
+{
+ print_nl();
+ print_string(PRINT_FP, NULL, "%s", "\t ");
+}
+
+static void __attribute__((format(printf, 2, 0)))
+can_print_timing_min_max(const char *json_attr, const char *fp_attr,
+ int min, int max)
+{
+ print_null(PRINT_FP, NULL, fp_attr, NULL);
+ open_json_object(json_attr);
+ print_uint(PRINT_ANY, "min", " %d", min);
+ print_uint(PRINT_ANY, "max", "..%d", max);
+ close_json_object();
+}
+
+static void can_print_tdc_opt(FILE *f, struct rtattr *tdc_attr)
+{
+ struct rtattr *tb[IFLA_CAN_TDC_MAX + 1];
+
+ parse_rtattr_nested(tb, IFLA_CAN_TDC_MAX, tdc_attr);
+ if (tb[IFLA_CAN_TDC_TDCV] || tb[IFLA_CAN_TDC_TDCO] ||
+ tb[IFLA_CAN_TDC_TDCF]) {
+ open_json_object("tdc");
+ can_print_nl_indent();
+ if (tb[IFLA_CAN_TDC_TDCV]) {
+ __u32 *tdcv = RTA_DATA(tb[IFLA_CAN_TDC_TDCV]);
+
+ print_uint(PRINT_ANY, "tdcv", " tdcv %u", *tdcv);
+ }
+ if (tb[IFLA_CAN_TDC_TDCO]) {
+ __u32 *tdco = RTA_DATA(tb[IFLA_CAN_TDC_TDCO]);
+
+ print_uint(PRINT_ANY, "tdco", " tdco %u", *tdco);
+ }
+ if (tb[IFLA_CAN_TDC_TDCF]) {
+ __u32 *tdcf = RTA_DATA(tb[IFLA_CAN_TDC_TDCF]);
+
+ print_uint(PRINT_ANY, "tdcf", " tdcf %u", *tdcf);
+ }
+ close_json_object();
+ }
+}
+
+static void can_print_tdc_const_opt(FILE *f, struct rtattr *tdc_attr)
+{
+ struct rtattr *tb[IFLA_CAN_TDC_MAX + 1];
+
+ parse_rtattr_nested(tb, IFLA_CAN_TDC_MAX, tdc_attr);
+ open_json_object("tdc");
+ can_print_nl_indent();
+ if (tb[IFLA_CAN_TDC_TDCV_MIN] && tb[IFLA_CAN_TDC_TDCV_MAX]) {
+ __u32 *tdcv_min = RTA_DATA(tb[IFLA_CAN_TDC_TDCV_MIN]);
+ __u32 *tdcv_max = RTA_DATA(tb[IFLA_CAN_TDC_TDCV_MAX]);
+
+ can_print_timing_min_max("tdcv", " tdcv", *tdcv_min, *tdcv_max);
+ }
+ if (tb[IFLA_CAN_TDC_TDCO_MIN] && tb[IFLA_CAN_TDC_TDCO_MAX]) {
+ __u32 *tdco_min = RTA_DATA(tb[IFLA_CAN_TDC_TDCO_MIN]);
+ __u32 *tdco_max = RTA_DATA(tb[IFLA_CAN_TDC_TDCO_MAX]);
+
+ can_print_timing_min_max("tdco", " tdco", *tdco_min, *tdco_max);
+ }
+ if (tb[IFLA_CAN_TDC_TDCF_MIN] && tb[IFLA_CAN_TDC_TDCF_MAX]) {
+ __u32 *tdcf_min = RTA_DATA(tb[IFLA_CAN_TDC_TDCF_MIN]);
+ __u32 *tdcf_max = RTA_DATA(tb[IFLA_CAN_TDC_TDCF_MAX]);
+
+ can_print_timing_min_max("tdcf", " tdcf", *tdcf_min, *tdcf_max);
+ }
+ close_json_object();
+}
+
+static void can_print_ctrlmode_ext(FILE *f, struct rtattr *ctrlmode_ext_attr,
+ __u32 cm_flags)
+{
+ struct rtattr *tb[IFLA_CAN_CTRLMODE_MAX + 1];
+
+ parse_rtattr_nested(tb, IFLA_CAN_CTRLMODE_MAX, ctrlmode_ext_attr);
+ if (tb[IFLA_CAN_CTRLMODE_SUPPORTED]) {
+ __u32 *supported = RTA_DATA(tb[IFLA_CAN_CTRLMODE_SUPPORTED]);
+
+ print_ctrlmode(PRINT_JSON, *supported, "ctrlmode_supported");
+ print_ctrlmode(PRINT_JSON, cm_flags & ~*supported, "ctrlmode_static");
+ }
+}
+
+static void can_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ if (!tb)
+ return;
+
+ if (tb[IFLA_CAN_CTRLMODE]) {
+ struct can_ctrlmode *cm = RTA_DATA(tb[IFLA_CAN_CTRLMODE]);
+
+ print_ctrlmode(PRINT_ANY, cm->flags, "ctrlmode");
+ if (tb[IFLA_CAN_CTRLMODE_EXT])
+ can_print_ctrlmode_ext(f, tb[IFLA_CAN_CTRLMODE_EXT],
+ cm->flags);
+ }
+
+ if (tb[IFLA_CAN_STATE]) {
+ uint32_t state = rta_getattr_u32(tb[IFLA_CAN_STATE]);
+
+ print_string(PRINT_ANY, "state", "state %s ", state < CAN_STATE_MAX ?
+ can_state_names[state] : "UNKNOWN");
+ }
+
+ if (tb[IFLA_CAN_BERR_COUNTER]) {
+ struct can_berr_counter *bc =
+ RTA_DATA(tb[IFLA_CAN_BERR_COUNTER]);
+
+ open_json_object("berr_counter");
+ print_uint(PRINT_ANY, "tx", "(berr-counter tx %u", bc->txerr);
+ print_uint(PRINT_ANY, "rx", " rx %u) ", bc->rxerr);
+ close_json_object();
+ }
+
+ if (tb[IFLA_CAN_RESTART_MS]) {
+ __u32 *restart_ms = RTA_DATA(tb[IFLA_CAN_RESTART_MS]);
+
+ print_uint(PRINT_ANY, "restart_ms", "restart-ms %u ",
+ *restart_ms);
+ }
+
+ /* bittiming is irrelevant if fixed bitrate is defined */
+ if (tb[IFLA_CAN_BITTIMING] && !tb[IFLA_CAN_BITRATE_CONST]) {
+ struct can_bittiming *bt = RTA_DATA(tb[IFLA_CAN_BITTIMING]);
+ char sp[6];
+
+ open_json_object("bittiming");
+ can_print_nl_indent();
+ print_uint(PRINT_ANY, "bitrate", " bitrate %u", bt->bitrate);
+ snprintf(sp, sizeof(sp), "%.3f", bt->sample_point / 1000.);
+ print_string(PRINT_ANY, "sample_point", " sample-point %s", sp);
+ can_print_nl_indent();
+ print_uint(PRINT_ANY, "tq", " tq %u", bt->tq);
+ print_uint(PRINT_ANY, "prop_seg", " prop-seg %u", bt->prop_seg);
+ print_uint(PRINT_ANY, "phase_seg1", " phase-seg1 %u",
+ bt->phase_seg1);
+ print_uint(PRINT_ANY, "phase_seg2", " phase-seg2 %u",
+ bt->phase_seg2);
+ print_uint(PRINT_ANY, "sjw", " sjw %u", bt->sjw);
+ print_uint(PRINT_ANY, "brp", " brp %u", bt->brp);
+ close_json_object();
+ }
+
+ /* bittiming const is irrelevant if fixed bitrate is defined */
+ if (tb[IFLA_CAN_BITTIMING_CONST] && !tb[IFLA_CAN_BITRATE_CONST]) {
+ struct can_bittiming_const *btc =
+ RTA_DATA(tb[IFLA_CAN_BITTIMING_CONST]);
+
+ open_json_object("bittiming_const");
+ can_print_nl_indent();
+ print_string(PRINT_ANY, "name", " %s:", btc->name);
+ can_print_timing_min_max("tseg1", " tseg1",
+ btc->tseg1_min, btc->tseg1_max);
+ can_print_timing_min_max("tseg2", " tseg2",
+ btc->tseg2_min, btc->tseg2_max);
+ can_print_timing_min_max("sjw", " sjw", 1, btc->sjw_max);
+ can_print_timing_min_max("brp", " brp",
+ btc->brp_min, btc->brp_max);
+ print_uint(PRINT_ANY, "brp_inc", " brp_inc %u", btc->brp_inc);
+ close_json_object();
+ }
+
+ if (tb[IFLA_CAN_BITRATE_CONST]) {
+ __u32 *bitrate_const = RTA_DATA(tb[IFLA_CAN_BITRATE_CONST]);
+ int bitrate_cnt = RTA_PAYLOAD(tb[IFLA_CAN_BITRATE_CONST]) /
+ sizeof(*bitrate_const);
+ int i;
+ __u32 bitrate = 0;
+
+ if (tb[IFLA_CAN_BITTIMING]) {
+ struct can_bittiming *bt =
+ RTA_DATA(tb[IFLA_CAN_BITTIMING]);
+ bitrate = bt->bitrate;
+ }
+
+ can_print_nl_indent();
+ print_uint(PRINT_ANY, "bittiming_bitrate", " bitrate %u",
+ bitrate);
+ can_print_nl_indent();
+ open_json_array(PRINT_ANY, is_json_context() ?
+ "bitrate_const" : " [");
+ for (i = 0; i < bitrate_cnt; ++i) {
+ /* This will keep lines below 80 signs */
+ if (!(i % 6) && i) {
+ can_print_nl_indent();
+ print_string(PRINT_FP, NULL, "%s", " ");
+ }
+ print_uint(PRINT_ANY, NULL,
+ i < bitrate_cnt - 1 ? "%8u, " : "%8u",
+ bitrate_const[i]);
+ }
+ close_json_array(PRINT_ANY, " ]");
+ }
+
+ /* data bittiming is irrelevant if fixed bitrate is defined */
+ if (tb[IFLA_CAN_DATA_BITTIMING] && !tb[IFLA_CAN_DATA_BITRATE_CONST]) {
+ struct can_bittiming *dbt =
+ RTA_DATA(tb[IFLA_CAN_DATA_BITTIMING]);
+ char dsp[6];
+
+ open_json_object("data_bittiming");
+ can_print_nl_indent();
+ print_uint(PRINT_ANY, "bitrate", " dbitrate %u", dbt->bitrate);
+ snprintf(dsp, sizeof(dsp), "%.3f", dbt->sample_point / 1000.);
+ print_string(PRINT_ANY, "sample_point", " dsample-point %s",
+ dsp);
+ can_print_nl_indent();
+ print_uint(PRINT_ANY, "tq", " dtq %u", dbt->tq);
+ print_uint(PRINT_ANY, "prop_seg", " dprop-seg %u",
+ dbt->prop_seg);
+ print_uint(PRINT_ANY, "phase_seg1", " dphase-seg1 %u",
+ dbt->phase_seg1);
+ print_uint(PRINT_ANY, "phase_seg2", " dphase-seg2 %u",
+ dbt->phase_seg2);
+ print_uint(PRINT_ANY, "sjw", " dsjw %u", dbt->sjw);
+ print_uint(PRINT_ANY, "brp", " dbrp %u", dbt->brp);
+
+ if (tb[IFLA_CAN_TDC])
+ can_print_tdc_opt(f, tb[IFLA_CAN_TDC]);
+
+ close_json_object();
+ }
+
+ /* data bittiming const is irrelevant if fixed bitrate is defined */
+ if (tb[IFLA_CAN_DATA_BITTIMING_CONST] &&
+ !tb[IFLA_CAN_DATA_BITRATE_CONST]) {
+ struct can_bittiming_const *dbtc =
+ RTA_DATA(tb[IFLA_CAN_DATA_BITTIMING_CONST]);
+
+ open_json_object("data_bittiming_const");
+ can_print_nl_indent();
+ print_string(PRINT_ANY, "name", " %s:", dbtc->name);
+ can_print_timing_min_max("tseg1", " dtseg1",
+ dbtc->tseg1_min, dbtc->tseg1_max);
+ can_print_timing_min_max("tseg2", " dtseg2",
+ dbtc->tseg2_min, dbtc->tseg2_max);
+ can_print_timing_min_max("sjw", " dsjw", 1, dbtc->sjw_max);
+ can_print_timing_min_max("brp", " dbrp",
+ dbtc->brp_min, dbtc->brp_max);
+ print_uint(PRINT_ANY, "brp_inc", " dbrp_inc %u", dbtc->brp_inc);
+
+ if (tb[IFLA_CAN_TDC])
+ can_print_tdc_const_opt(f, tb[IFLA_CAN_TDC]);
+
+ close_json_object();
+ }
+
+ if (tb[IFLA_CAN_DATA_BITRATE_CONST]) {
+ __u32 *dbitrate_const =
+ RTA_DATA(tb[IFLA_CAN_DATA_BITRATE_CONST]);
+ int dbitrate_cnt =
+ RTA_PAYLOAD(tb[IFLA_CAN_DATA_BITRATE_CONST]) /
+ sizeof(*dbitrate_const);
+ int i;
+ __u32 dbitrate = 0;
+
+ if (tb[IFLA_CAN_DATA_BITTIMING]) {
+ struct can_bittiming *dbt =
+ RTA_DATA(tb[IFLA_CAN_DATA_BITTIMING]);
+ dbitrate = dbt->bitrate;
+ }
+
+ can_print_nl_indent();
+ print_uint(PRINT_ANY, "data_bittiming_bitrate", " dbitrate %u",
+ dbitrate);
+ can_print_nl_indent();
+ open_json_array(PRINT_ANY, is_json_context() ?
+ "data_bitrate_const" : " [");
+ for (i = 0; i < dbitrate_cnt; ++i) {
+ /* This will keep lines below 80 signs */
+ if (!(i % 6) && i) {
+ can_print_nl_indent();
+ print_string(PRINT_FP, NULL, "%s", " ");
+ }
+ print_uint(PRINT_ANY, NULL,
+ i < dbitrate_cnt - 1 ? "%8u, " : "%8u",
+ dbitrate_const[i]);
+ }
+ close_json_array(PRINT_ANY, " ]");
+ }
+
+ if (tb[IFLA_CAN_TERMINATION_CONST] && tb[IFLA_CAN_TERMINATION]) {
+ __u16 *trm = RTA_DATA(tb[IFLA_CAN_TERMINATION]);
+ __u16 *trm_const = RTA_DATA(tb[IFLA_CAN_TERMINATION_CONST]);
+ int trm_cnt = RTA_PAYLOAD(tb[IFLA_CAN_TERMINATION_CONST]) /
+ sizeof(*trm_const);
+ int i;
+
+ can_print_nl_indent();
+ print_hu(PRINT_ANY, "termination", " termination %hu [ ", *trm);
+ open_json_array(PRINT_JSON, "termination_const");
+ for (i = 0; i < trm_cnt; ++i)
+ print_hu(PRINT_ANY, NULL,
+ i < trm_cnt - 1 ? "%hu, " : "%hu",
+ trm_const[i]);
+ close_json_array(PRINT_ANY, " ]");
+ }
+
+ if (tb[IFLA_CAN_CLOCK]) {
+ struct can_clock *clock = RTA_DATA(tb[IFLA_CAN_CLOCK]);
+
+ can_print_nl_indent();
+ print_uint(PRINT_ANY, "clock", " clock %u ", clock->freq);
+ }
+
+}
+
+static void can_print_xstats(struct link_util *lu,
+ FILE *f, struct rtattr *xstats)
+{
+ struct can_device_stats *stats;
+
+ if (xstats && RTA_PAYLOAD(xstats) == sizeof(*stats)) {
+ stats = RTA_DATA(xstats);
+
+ can_print_nl_indent();
+ print_string(PRINT_FP, NULL, "%s",
+ " re-started bus-errors arbit-lost error-warn error-pass bus-off");
+ can_print_nl_indent();
+ print_uint(PRINT_ANY, "restarts", " %-10u", stats->restarts);
+ print_uint(PRINT_ANY, "bus_error", " %-10u", stats->bus_error);
+ print_uint(PRINT_ANY, "arbitration_lost", " %-10u",
+ stats->arbitration_lost);
+ print_uint(PRINT_ANY, "error_warning", " %-10u",
+ stats->error_warning);
+ print_uint(PRINT_ANY, "error_passive", " %-10u",
+ stats->error_passive);
+ print_uint(PRINT_ANY, "bus_off", " %-10u", stats->bus_off);
+ }
+}
+
+static void can_print_help(struct link_util *lu, int argc, char **argv, FILE *f)
+{
+ print_usage(f);
+}
+
+struct link_util can_link_util = {
+ .id = "can",
+ .maxattr = IFLA_CAN_MAX,
+ .parse_opt = can_parse_opt,
+ .print_opt = can_print_opt,
+ .print_xstats = can_print_xstats,
+ .print_help = can_print_help,
+};
diff --git a/ip/iplink_dsa.c b/ip/iplink_dsa.c
new file mode 100644
index 0000000..e3f3f8a
--- /dev/null
+++ b/ip/iplink_dsa.c
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * iplink_dsa.c DSA switch support
+ */
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_usage(FILE *f)
+{
+ fprintf(f, "Usage: ... dsa [ conduit DEVICE ]\n");
+}
+
+static int dsa_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ while (argc > 0) {
+ if (strcmp(*argv, "conduit") == 0 ||
+ strcmp(*argv, "master") == 0) {
+ __u32 ifindex;
+
+ NEXT_ARG();
+ ifindex = ll_name_to_index(*argv);
+ if (!ifindex)
+ invarg("Device does not exist\n", *argv);
+ addattr_l(n, 1024, IFLA_DSA_MASTER, &ifindex, 4);
+ } else if (strcmp(*argv, "help") == 0) {
+ print_usage(stderr);
+ return -1;
+ } else {
+ fprintf(stderr, "dsa: unknown command \"%s\"?\n", *argv);
+ print_usage(stderr);
+ return -1;
+ }
+ argc--;
+ argv++;
+ }
+
+ return 0;
+}
+
+static void dsa_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ if (!tb)
+ return;
+
+ if (tb[IFLA_DSA_MASTER]) {
+ __u32 conduit = rta_getattr_u32(tb[IFLA_DSA_MASTER]);
+
+ print_string(PRINT_ANY,
+ "conduit", "conduit %s ",
+ ll_index_to_name(conduit));
+ }
+}
+
+static void dsa_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ print_usage(f);
+}
+
+struct link_util dsa_link_util = {
+ .id = "dsa",
+ .maxattr = IFLA_DSA_MAX,
+ .parse_opt = dsa_parse_opt,
+ .print_opt = dsa_print_opt,
+ .print_help = dsa_print_help,
+};
diff --git a/ip/iplink_dummy.c b/ip/iplink_dummy.c
new file mode 100644
index 0000000..cba2295
--- /dev/null
+++ b/ip/iplink_dummy.c
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void dummy_print_help(struct link_util *lu,
+ int argc, char **argv, FILE *f)
+{
+ fprintf(f, "Usage: ... dummy\n");
+}
+
+struct link_util dummy_link_util = {
+ .id = "dummy",
+ .print_help = dummy_print_help,
+};
diff --git a/ip/iplink_geneve.c b/ip/iplink_geneve.c
new file mode 100644
index 0000000..62c61bc
--- /dev/null
+++ b/ip/iplink_geneve.c
@@ -0,0 +1,391 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iplink_geneve.c GENEVE device support
+ *
+ * Authors: John W. Linville <linville@tuxdriver.com>
+ */
+
+#include <stdio.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+#define GENEVE_ATTRSET(attrs, type) (((attrs) & (1L << (type))) != 0)
+
+static void print_explain(FILE *f)
+{
+ fprintf(f,
+ "Usage: ... geneve id VNI\n"
+ " remote ADDR\n"
+ " [ ttl TTL ]\n"
+ " [ tos TOS ]\n"
+ " [ df DF ]\n"
+ " [ flowlabel LABEL ]\n"
+ " [ dstport PORT ]\n"
+ " [ [no]external ]\n"
+ " [ [no]udpcsum ]\n"
+ " [ [no]udp6zerocsumtx ]\n"
+ " [ [no]udp6zerocsumrx ]\n"
+ " [ innerprotoinherit ]\n"
+ "\n"
+ "Where: VNI := 0-16777215\n"
+ " ADDR := IP_ADDRESS\n"
+ " TOS := { NUMBER | inherit }\n"
+ " TTL := { 1..255 | auto | inherit }\n"
+ " DF := { unset | set | inherit }\n"
+ " LABEL := 0-1048575\n"
+ );
+}
+
+static void explain(void)
+{
+ print_explain(stderr);
+}
+
+static void check_duparg(__u64 *attrs, int type, const char *key,
+ const char *argv)
+{
+ if (!GENEVE_ATTRSET(*attrs, type)) {
+ *attrs |= (1L << type);
+ return;
+ }
+ duparg2(key, argv);
+}
+
+static int geneve_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ inet_prefix daddr;
+ __u32 vni = 0;
+ __u32 label = 0;
+ __u8 ttl = 0;
+ __u8 tos = 0;
+ __u16 dstport = 0;
+ bool metadata = 0;
+ __u8 udpcsum = 0;
+ __u8 udp6zerocsumtx = 0;
+ __u8 udp6zerocsumrx = 0;
+ __u64 attrs = 0;
+ bool set_op = (n->nlmsg_type == RTM_NEWLINK &&
+ !(n->nlmsg_flags & NLM_F_CREATE));
+ bool inner_proto_inherit = false;
+
+ inet_prefix_reset(&daddr);
+
+ while (argc > 0) {
+ if (!matches(*argv, "id") ||
+ !matches(*argv, "vni")) {
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_GENEVE_ID, "id", *argv);
+ if (get_u32(&vni, *argv, 0) ||
+ vni >= 1u << 24)
+ invarg("invalid id", *argv);
+ } else if (!matches(*argv, "remote")) {
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_GENEVE_REMOTE, "remote",
+ *argv);
+ get_addr(&daddr, *argv, AF_UNSPEC);
+ if (!is_addrtype_inet_not_multi(&daddr))
+ invarg("invalid remote address", *argv);
+ } else if (!matches(*argv, "ttl") ||
+ !matches(*argv, "hoplimit")) {
+ unsigned int uval;
+
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_GENEVE_TTL, "ttl", *argv);
+ if (strcmp(*argv, "inherit") == 0) {
+ addattr8(n, 1024, IFLA_GENEVE_TTL_INHERIT, 1);
+ } else if (strcmp(*argv, "auto") != 0) {
+ if (get_unsigned(&uval, *argv, 0))
+ invarg("invalid TTL", *argv);
+ if (uval > 255)
+ invarg("TTL must be <= 255", *argv);
+ ttl = uval;
+ }
+ } else if (!matches(*argv, "tos") ||
+ !matches(*argv, "dsfield")) {
+ __u32 uval;
+
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_GENEVE_TOS, "tos", *argv);
+ if (strcmp(*argv, "inherit") != 0) {
+ if (rtnl_dsfield_a2n(&uval, *argv))
+ invarg("bad TOS value", *argv);
+ tos = uval;
+ } else
+ tos = 1;
+ } else if (!matches(*argv, "df")) {
+ enum ifla_geneve_df df;
+
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_GENEVE_DF, "df", *argv);
+ if (strcmp(*argv, "unset") == 0)
+ df = GENEVE_DF_UNSET;
+ else if (strcmp(*argv, "set") == 0)
+ df = GENEVE_DF_SET;
+ else if (strcmp(*argv, "inherit") == 0)
+ df = GENEVE_DF_INHERIT;
+ else
+ invarg("DF must be 'unset', 'set' or 'inherit'",
+ *argv);
+
+ addattr8(n, 1024, IFLA_GENEVE_DF, df);
+ } else if (!matches(*argv, "label") ||
+ !matches(*argv, "flowlabel")) {
+ __u32 uval;
+
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_GENEVE_LABEL, "flowlabel",
+ *argv);
+ if (get_u32(&uval, *argv, 0) ||
+ (uval & ~LABEL_MAX_MASK))
+ invarg("invalid flowlabel", *argv);
+ label = htonl(uval);
+ } else if (!matches(*argv, "dstport")) {
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_GENEVE_PORT, "dstport",
+ *argv);
+ if (get_u16(&dstport, *argv, 0))
+ invarg("dstport", *argv);
+ } else if (!matches(*argv, "external")) {
+ check_duparg(&attrs, IFLA_GENEVE_COLLECT_METADATA,
+ *argv, *argv);
+ metadata = true;
+ } else if (!matches(*argv, "noexternal")) {
+ check_duparg(&attrs, IFLA_GENEVE_COLLECT_METADATA,
+ *argv, *argv);
+ metadata = false;
+ } else if (!matches(*argv, "udpcsum")) {
+ check_duparg(&attrs, IFLA_GENEVE_UDP_CSUM, *argv,
+ *argv);
+ udpcsum = 1;
+ } else if (!matches(*argv, "noudpcsum")) {
+ check_duparg(&attrs, IFLA_GENEVE_UDP_CSUM, *argv,
+ *argv);
+ udpcsum = 0;
+ } else if (!matches(*argv, "udp6zerocsumtx")) {
+ check_duparg(&attrs, IFLA_GENEVE_UDP_ZERO_CSUM6_TX,
+ *argv, *argv);
+ udp6zerocsumtx = 1;
+ } else if (!matches(*argv, "noudp6zerocsumtx")) {
+ check_duparg(&attrs, IFLA_GENEVE_UDP_ZERO_CSUM6_TX,
+ *argv, *argv);
+ udp6zerocsumtx = 0;
+ } else if (!matches(*argv, "udp6zerocsumrx")) {
+ check_duparg(&attrs, IFLA_GENEVE_UDP_ZERO_CSUM6_RX,
+ *argv, *argv);
+ udp6zerocsumrx = 1;
+ } else if (!matches(*argv, "noudp6zerocsumrx")) {
+ check_duparg(&attrs, IFLA_GENEVE_UDP_ZERO_CSUM6_RX,
+ *argv, *argv);
+ udp6zerocsumrx = 0;
+ } else if (!strcmp(*argv, "innerprotoinherit")) {
+ check_duparg(&attrs, IFLA_GENEVE_INNER_PROTO_INHERIT,
+ *argv, *argv);
+ inner_proto_inherit = true;
+ } else if (matches(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "geneve: unknown command \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ if (metadata && GENEVE_ATTRSET(attrs, IFLA_GENEVE_ID)) {
+ fprintf(stderr, "geneve: both 'external' and vni cannot be specified\n");
+ return -1;
+ }
+
+ if (!metadata) {
+ /* parameter checking make sense only for full geneve tunnels */
+ if (!GENEVE_ATTRSET(attrs, IFLA_GENEVE_ID)) {
+ fprintf(stderr, "geneve: missing virtual network identifier\n");
+ return -1;
+ }
+
+ /* If we are modifying the geneve device, then we only need the
+ * ID (VNI) to identify the geneve device, and we do not need
+ * the remote IP.
+ */
+ if (!set_op && !is_addrtype_inet(&daddr)) {
+ fprintf(stderr, "geneve: remote link partner not specified\n");
+ return -1;
+ }
+ }
+
+ addattr32(n, 1024, IFLA_GENEVE_ID, vni);
+ if (is_addrtype_inet(&daddr)) {
+ int type = (daddr.family == AF_INET) ? IFLA_GENEVE_REMOTE :
+ IFLA_GENEVE_REMOTE6;
+ addattr_l(n, 1024, type, daddr.data, daddr.bytelen);
+ }
+ if (!set_op || GENEVE_ATTRSET(attrs, IFLA_GENEVE_LABEL))
+ addattr32(n, 1024, IFLA_GENEVE_LABEL, label);
+ if (!set_op || GENEVE_ATTRSET(attrs, IFLA_GENEVE_TTL))
+ addattr8(n, 1024, IFLA_GENEVE_TTL, ttl);
+ if (!set_op || GENEVE_ATTRSET(attrs, IFLA_GENEVE_TOS))
+ addattr8(n, 1024, IFLA_GENEVE_TOS, tos);
+ if (dstport)
+ addattr16(n, 1024, IFLA_GENEVE_PORT, htons(dstport));
+ if (metadata)
+ addattr(n, 1024, IFLA_GENEVE_COLLECT_METADATA);
+ if (inner_proto_inherit)
+ addattr(n, 1024, IFLA_GENEVE_INNER_PROTO_INHERIT);
+ if (GENEVE_ATTRSET(attrs, IFLA_GENEVE_UDP_CSUM))
+ addattr8(n, 1024, IFLA_GENEVE_UDP_CSUM, udpcsum);
+ if (GENEVE_ATTRSET(attrs, IFLA_GENEVE_UDP_ZERO_CSUM6_TX))
+ addattr8(n, 1024, IFLA_GENEVE_UDP_ZERO_CSUM6_TX, udp6zerocsumtx);
+ if (GENEVE_ATTRSET(attrs, IFLA_GENEVE_UDP_ZERO_CSUM6_RX))
+ addattr8(n, 1024, IFLA_GENEVE_UDP_ZERO_CSUM6_RX, udp6zerocsumrx);
+
+ return 0;
+}
+
+static void geneve_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ __u8 ttl = 0;
+ __u8 tos = 0;
+
+ if (!tb)
+ return;
+
+ if (tb[IFLA_GENEVE_COLLECT_METADATA]) {
+ print_bool(PRINT_ANY, "external", "external ", true);
+ }
+
+ if (tb[IFLA_GENEVE_ID] &&
+ RTA_PAYLOAD(tb[IFLA_GENEVE_ID]) >= sizeof(__u32)) {
+ print_uint(PRINT_ANY, "id", "id %u ", rta_getattr_u32(tb[IFLA_GENEVE_ID]));
+ }
+
+ if (tb[IFLA_GENEVE_REMOTE]) {
+ __be32 addr = rta_getattr_u32(tb[IFLA_GENEVE_REMOTE]);
+
+ if (addr)
+ print_string(PRINT_ANY,
+ "remote",
+ "remote %s ",
+ format_host(AF_INET, 4, &addr));
+ } else if (tb[IFLA_GENEVE_REMOTE6]) {
+ struct in6_addr addr;
+
+ memcpy(&addr, RTA_DATA(tb[IFLA_GENEVE_REMOTE6]), sizeof(struct in6_addr));
+ if (!IN6_IS_ADDR_UNSPECIFIED(&addr)) {
+ if (!IN6_IS_ADDR_MULTICAST(&addr))
+ print_string(PRINT_ANY,
+ "remote6",
+ "remote %s ",
+ format_host(AF_INET6,
+ sizeof(struct in6_addr),
+ &addr));
+ }
+ }
+
+ if (tb[IFLA_GENEVE_TTL_INHERIT] &&
+ rta_getattr_u8(tb[IFLA_GENEVE_TTL_INHERIT])) {
+ print_string(PRINT_FP, NULL, "ttl %s ", "inherit");
+ } else if (tb[IFLA_GENEVE_TTL]) {
+ ttl = rta_getattr_u8(tb[IFLA_GENEVE_TTL]);
+ if (is_json_context() || ttl)
+ print_uint(PRINT_ANY, "ttl", "ttl %u ", ttl);
+ else
+ print_string(PRINT_FP, NULL, "ttl %s ", "auto");
+ }
+
+ if (tb[IFLA_GENEVE_TOS])
+ tos = rta_getattr_u8(tb[IFLA_GENEVE_TOS]);
+ if (tos) {
+ if (is_json_context() || tos != 1)
+ print_0xhex(PRINT_ANY, "tos", "tos %#llx ", tos);
+ else
+ print_string(PRINT_FP, NULL, "tos %s ", "inherit");
+ }
+
+ if (tb[IFLA_GENEVE_DF]) {
+ enum ifla_geneve_df df = rta_getattr_u8(tb[IFLA_GENEVE_DF]);
+
+ if (df == GENEVE_DF_UNSET)
+ print_string(PRINT_JSON, "df", "df %s ", "unset");
+ else if (df == GENEVE_DF_SET)
+ print_string(PRINT_ANY, "df", "df %s ", "set");
+ else if (df == GENEVE_DF_INHERIT)
+ print_string(PRINT_ANY, "df", "df %s ", "inherit");
+ }
+
+ if (tb[IFLA_GENEVE_LABEL]) {
+ __u32 label = rta_getattr_u32(tb[IFLA_GENEVE_LABEL]);
+
+ if (label)
+ print_0xhex(PRINT_ANY,
+ "label", "flowlabel %#llx ",
+ ntohl(label));
+ }
+
+ if (tb[IFLA_GENEVE_PORT])
+ print_uint(PRINT_ANY,
+ "port",
+ "dstport %u ",
+ rta_getattr_be16(tb[IFLA_GENEVE_PORT]));
+
+ if (tb[IFLA_GENEVE_UDP_CSUM]) {
+ if (is_json_context()) {
+ print_bool(PRINT_JSON,
+ "udp_csum",
+ NULL,
+ rta_getattr_u8(tb[IFLA_GENEVE_UDP_CSUM]));
+ } else {
+ if (!rta_getattr_u8(tb[IFLA_GENEVE_UDP_CSUM]))
+ fputs("no", f);
+ fputs("udpcsum ", f);
+ }
+ }
+
+ if (tb[IFLA_GENEVE_UDP_ZERO_CSUM6_TX]) {
+ if (is_json_context()) {
+ print_bool(PRINT_JSON,
+ "udp_zero_csum6_tx",
+ NULL,
+ rta_getattr_u8(tb[IFLA_GENEVE_UDP_ZERO_CSUM6_TX]));
+ } else {
+ if (!rta_getattr_u8(tb[IFLA_GENEVE_UDP_ZERO_CSUM6_TX]))
+ fputs("no", f);
+ fputs("udp6zerocsumtx ", f);
+ }
+ }
+
+ if (tb[IFLA_GENEVE_UDP_ZERO_CSUM6_RX]) {
+ if (is_json_context()) {
+ print_bool(PRINT_JSON,
+ "udp_zero_csum6_rx",
+ NULL,
+ rta_getattr_u8(tb[IFLA_GENEVE_UDP_ZERO_CSUM6_RX]));
+ } else {
+ if (!rta_getattr_u8(tb[IFLA_GENEVE_UDP_ZERO_CSUM6_RX]))
+ fputs("no", f);
+ fputs("udp6zerocsumrx ", f);
+ }
+ }
+
+ if (tb[IFLA_GENEVE_INNER_PROTO_INHERIT]) {
+ print_bool(PRINT_ANY, "inner_proto_inherit",
+ "innerprotoinherit ", true);
+ }
+}
+
+static void geneve_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ print_explain(f);
+}
+
+struct link_util geneve_link_util = {
+ .id = "geneve",
+ .maxattr = IFLA_GENEVE_MAX,
+ .parse_opt = geneve_parse_opt,
+ .print_opt = geneve_print_opt,
+ .print_help = geneve_print_help,
+};
diff --git a/ip/iplink_gtp.c b/ip/iplink_gtp.c
new file mode 100644
index 0000000..086946b
--- /dev/null
+++ b/ip/iplink_gtp.c
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <stdio.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+#define GTP_ATTRSET(attrs, type) (((attrs) & (1L << (type))) != 0)
+
+static void print_explain(FILE *f)
+{
+ fprintf(f,
+ "Usage: ... gtp role ROLE\n"
+ " [ hsize HSIZE ]\n"
+ " [ restart_count RESTART_COUNT ]\n"
+ "\n"
+ "Where: ROLE := { sgsn | ggsn }\n"
+ " HSIZE := 1-131071\n"
+ " RESTART_COUNT := 0-255\n"
+ );
+}
+
+static void check_duparg(__u32 *attrs, int type, const char *key,
+ const char *argv)
+{
+ if (!GTP_ATTRSET(*attrs, type)) {
+ *attrs |= (1L << type);
+ return;
+ }
+ duparg2(key, argv);
+}
+
+static int gtp_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ __u32 attrs = 0;
+
+ /* When creating GTP device through ip link,
+ * this flag has to be set.
+ */
+ addattr8(n, 1024, IFLA_GTP_CREATE_SOCKETS, true);
+
+ while (argc > 0) {
+ if (!strcmp(*argv, "role")) {
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_GTP_ROLE, "role", *argv);
+ if (!strcmp(*argv, "sgsn"))
+ addattr32(n, 1024, IFLA_GTP_ROLE, GTP_ROLE_SGSN);
+ else if (!strcmp(*argv, "ggsn"))
+ addattr32(n, 1024, IFLA_GTP_ROLE, GTP_ROLE_GGSN);
+ else
+ invarg("invalid role, use sgsn or ggsn", *argv);
+ } else if (!strcmp(*argv, "hsize")) {
+ __u32 hsize;
+
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_GTP_PDP_HASHSIZE, "hsize", *argv);
+
+ if (get_u32(&hsize, *argv, 0))
+ invarg("invalid PDP hash size", *argv);
+ if (hsize >= 1u << 17)
+ invarg("PDP hash size too big", *argv);
+ addattr32(n, 1024, IFLA_GTP_PDP_HASHSIZE, hsize);
+ } else if (!strcmp(*argv, "restart_count")) {
+ __u8 restart_count;
+
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_GTP_RESTART_COUNT, "restart_count", *argv);
+
+ if (get_u8(&restart_count, *argv, 10))
+ invarg("invalid restart_count", *argv);
+ addattr8(n, 1024, IFLA_GTP_RESTART_COUNT, restart_count);
+ } else if (!strcmp(*argv, "help")) {
+ print_explain(stderr);
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ if (!GTP_ATTRSET(attrs, IFLA_GTP_ROLE)) {
+ fprintf(stderr, "gtp: role of the gtp device was not specified\n");
+ return -1;
+ }
+
+ if (!GTP_ATTRSET(attrs, IFLA_GTP_PDP_HASHSIZE))
+ addattr32(n, 1024, IFLA_GTP_PDP_HASHSIZE, 1024);
+
+ return 0;
+}
+
+static const char *gtp_role_to_string(__u32 role)
+{
+ switch (role) {
+ case GTP_ROLE_GGSN:
+ return "ggsn";
+ case GTP_ROLE_SGSN:
+ return "sgsn";
+ default:
+ return "unknown";
+ }
+}
+
+static void gtp_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+
+ if (tb[IFLA_GTP_ROLE]) {
+ __u32 role = rta_getattr_u32(tb[IFLA_GTP_ROLE]);
+
+ print_string(PRINT_ANY, "role", "role %s ",
+ gtp_role_to_string(role));
+ }
+
+ if (tb[IFLA_GTP_PDP_HASHSIZE]) {
+ __u32 hsize = rta_getattr_u32(tb[IFLA_GTP_PDP_HASHSIZE]);
+
+ print_uint(PRINT_ANY, "hsize", "hsize %u ", hsize);
+ }
+
+ if (tb[IFLA_GTP_RESTART_COUNT]) {
+ __u8 restart_count = rta_getattr_u8(tb[IFLA_GTP_RESTART_COUNT]);
+
+ print_uint(PRINT_ANY, "restart_count",
+ "restart_count %u ", restart_count);
+ }
+}
+
+static void gtp_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ print_explain(f);
+}
+
+struct link_util gtp_link_util = {
+ .id = "gtp",
+ .maxattr = IFLA_GTP_MAX,
+ .parse_opt = gtp_parse_opt,
+ .print_opt = gtp_print_opt,
+ .print_help = gtp_print_help,
+};
diff --git a/ip/iplink_hsr.c b/ip/iplink_hsr.c
new file mode 100644
index 0000000..76f24a6
--- /dev/null
+++ b/ip/iplink_hsr.c
@@ -0,0 +1,166 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iplink_hsr.c HSR device support
+ *
+ * Authors: Arvid Brodin <arvid.brodin@alten.se>
+ *
+ * Based on iplink_vlan.c by Patrick McHardy <kaber@trash.net>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h> /* Needed by linux/if.h for some reason */
+#include <linux/if.h>
+#include <linux/if_arp.h>
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_usage(FILE *f)
+{
+ fprintf(f,
+ "Usage:\tip link add name NAME type hsr slave1 SLAVE1-IF slave2 SLAVE2-IF\n"
+ "\t[ supervision ADDR-BYTE ] [version VERSION] [proto PROTOCOL]\n"
+ "\n"
+ "NAME\n"
+ " name of new hsr device (e.g. hsr0)\n"
+ "SLAVE1-IF, SLAVE2-IF\n"
+ " the two slave devices bound to the HSR device\n"
+ "ADDR-BYTE\n"
+ " 0-255; the last byte of the multicast address used for HSR supervision\n"
+ " frames (default = 0)\n"
+ "VERSION\n"
+ " 0,1; the protocol version to be used. (default = 0)\n"
+ "PROTOCOL\n"
+ " 0 - HSR, 1 - PRP. (default = 0 - HSR)\n");
+}
+
+static void usage(void)
+{
+ print_usage(stderr);
+}
+
+static int hsr_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ int ifindex;
+ unsigned char multicast_spec;
+ unsigned char protocol_version;
+ unsigned char protocol = HSR_PROTOCOL_HSR;
+
+ while (argc > 0) {
+ if (matches(*argv, "supervision") == 0) {
+ NEXT_ARG();
+ if (get_u8(&multicast_spec, *argv, 0))
+ invarg("ADDR-BYTE is invalid", *argv);
+ addattr_l(n, 1024, IFLA_HSR_MULTICAST_SPEC,
+ &multicast_spec, 1);
+ } else if (matches(*argv, "version") == 0) {
+ NEXT_ARG();
+ if (!(get_u8(&protocol_version, *argv, 0) == 0 ||
+ get_u8(&protocol_version, *argv, 0) == 1))
+ invarg("version is invalid", *argv);
+ addattr_l(n, 1024, IFLA_HSR_VERSION,
+ &protocol_version, 1);
+ } else if (matches(*argv, "proto") == 0) {
+ NEXT_ARG();
+ if (!(get_u8(&protocol, *argv, 0) == HSR_PROTOCOL_HSR ||
+ get_u8(&protocol, *argv, 0) == HSR_PROTOCOL_PRP))
+ invarg("protocol is invalid", *argv);
+ addattr_l(n, 1024, IFLA_HSR_PROTOCOL,
+ &protocol, 1);
+ } else if (matches(*argv, "slave1") == 0) {
+ NEXT_ARG();
+ ifindex = ll_name_to_index(*argv);
+ if (ifindex == 0)
+ invarg("No such interface", *argv);
+ addattr_l(n, 1024, IFLA_HSR_SLAVE1, &ifindex, 4);
+ } else if (matches(*argv, "slave2") == 0) {
+ NEXT_ARG();
+ ifindex = ll_name_to_index(*argv);
+ if (ifindex == 0)
+ invarg("No such interface", *argv);
+ addattr_l(n, 1024, IFLA_HSR_SLAVE2, &ifindex, 4);
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ return -1;
+ } else {
+ fprintf(stderr, "hsr: what is \"%s\"?\n", *argv);
+ usage();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ return 0;
+}
+
+static void hsr_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ SPRINT_BUF(b1);
+
+ if (!tb)
+ return;
+
+ if (tb[IFLA_HSR_SLAVE1] &&
+ RTA_PAYLOAD(tb[IFLA_HSR_SLAVE1]) < sizeof(__u32))
+ return;
+ if (tb[IFLA_HSR_SLAVE2] &&
+ RTA_PAYLOAD(tb[IFLA_HSR_SLAVE2]) < sizeof(__u32))
+ return;
+ if (tb[IFLA_HSR_SEQ_NR] &&
+ RTA_PAYLOAD(tb[IFLA_HSR_SEQ_NR]) < sizeof(__u16))
+ return;
+ if (tb[IFLA_HSR_SUPERVISION_ADDR] &&
+ RTA_PAYLOAD(tb[IFLA_HSR_SUPERVISION_ADDR]) < ETH_ALEN)
+ return;
+
+ if (tb[IFLA_HSR_SLAVE1])
+ print_string(PRINT_ANY,
+ "slave1",
+ "slave1 %s ",
+ ll_index_to_name(rta_getattr_u32(tb[IFLA_HSR_SLAVE1])));
+ else
+ print_null(PRINT_ANY, "slave1", "slave1 %s ", "<none>");
+
+ if (tb[IFLA_HSR_SLAVE2])
+ print_string(PRINT_ANY,
+ "slave2",
+ "slave2 %s ",
+ ll_index_to_name(rta_getattr_u32(tb[IFLA_HSR_SLAVE2])));
+ else
+ print_null(PRINT_ANY, "slave2", "slave2 %s ", "<none>");
+
+ if (tb[IFLA_HSR_SEQ_NR])
+ print_int(PRINT_ANY,
+ "seq_nr",
+ "sequence %d ",
+ rta_getattr_u16(tb[IFLA_HSR_SEQ_NR]));
+
+ if (tb[IFLA_HSR_SUPERVISION_ADDR])
+ print_string(PRINT_ANY,
+ "supervision_addr",
+ "supervision %s ",
+ ll_addr_n2a(RTA_DATA(tb[IFLA_HSR_SUPERVISION_ADDR]),
+ RTA_PAYLOAD(tb[IFLA_HSR_SUPERVISION_ADDR]),
+ ARPHRD_VOID,
+ b1, sizeof(b1)));
+ if (tb[IFLA_HSR_PROTOCOL])
+ print_hhu(PRINT_ANY, "proto", "proto %hhu ",
+ rta_getattr_u8(tb[IFLA_HSR_PROTOCOL]));
+}
+
+static void hsr_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ print_usage(f);
+}
+
+struct link_util hsr_link_util = {
+ .id = "hsr",
+ .maxattr = IFLA_HSR_MAX,
+ .parse_opt = hsr_parse_opt,
+ .print_opt = hsr_print_opt,
+ .print_help = hsr_print_help,
+};
diff --git a/ip/iplink_ifb.c b/ip/iplink_ifb.c
new file mode 100644
index 0000000..a2a7301
--- /dev/null
+++ b/ip/iplink_ifb.c
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void ifb_print_help(struct link_util *lu,
+ int argc, char **argv, FILE *f)
+{
+ fprintf(f, "Usage: ... ifb\n");
+}
+
+struct link_util ifb_link_util = {
+ .id = "ifb",
+ .print_help = ifb_print_help,
+};
diff --git a/ip/iplink_ipoib.c b/ip/iplink_ipoib.c
new file mode 100644
index 0000000..7bf4e32
--- /dev/null
+++ b/ip/iplink_ipoib.c
@@ -0,0 +1,141 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iplink_ipoib.c IPoIB device support
+ *
+ * Authors: Or Gerlitz <ogerlitz@mellanox.com>
+ * copied iflink_vlan.c authored by Patrick McHardy <kaber@trash.net>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/if_link.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_explain(FILE *f)
+{
+ fprintf(f,
+ "Usage: ... ipoib [ pkey PKEY ]\n"
+ " [ mode {datagram | connected} ]\n"
+ " [ umcast {0|1} ]\n"
+ "\n"
+ "PKEY := 0x8001-0xffff\n"
+ );
+}
+
+static void explain(void)
+{
+ print_explain(stderr);
+}
+
+static int mode_arg(void)
+{
+ fprintf(stderr, "Error: argument of \"mode\" must be \"datagram\"or \"connected\"\n");
+ return -1;
+}
+
+static int ipoib_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ __u16 pkey, mode, umcast;
+
+ while (argc > 0) {
+ if (matches(*argv, "pkey") == 0) {
+ NEXT_ARG();
+ if (get_u16(&pkey, *argv, 0))
+ invarg("pkey is invalid", *argv);
+ addattr_l(n, 1024, IFLA_IPOIB_PKEY, &pkey, 2);
+ } else if (matches(*argv, "mode") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "datagram") == 0)
+ mode = IPOIB_MODE_DATAGRAM;
+ else if (strcmp(*argv, "connected") == 0)
+ mode = IPOIB_MODE_CONNECTED;
+ else
+ return mode_arg();
+ addattr_l(n, 1024, IFLA_IPOIB_MODE, &mode, 2);
+ } else if (matches(*argv, "umcast") == 0) {
+ NEXT_ARG();
+ if (get_u16(&umcast, *argv, 0))
+ invarg("umcast is invalid", *argv);
+ addattr_l(n, 1024, IFLA_IPOIB_UMCAST, &umcast, 2);
+ } else if (matches(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "ipoib: unknown option \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ return 0;
+}
+
+static void ipoib_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ __u16 mode;
+
+ if (!tb)
+ return;
+
+ if (!tb[IFLA_IPOIB_PKEY] ||
+ RTA_PAYLOAD(tb[IFLA_IPOIB_PKEY]) < sizeof(__u16))
+ return;
+
+ __u16 pkey = rta_getattr_u16(tb[IFLA_IPOIB_PKEY]);
+
+ if (is_json_context()) {
+ SPRINT_BUF(b1);
+
+ snprintf(b1, sizeof(b1), "%#.4x", pkey);
+ print_string(PRINT_JSON, "key", NULL, b1);
+ } else {
+ fprintf(f, "pkey %#.4x ", pkey);
+ }
+
+ if (!tb[IFLA_IPOIB_MODE] ||
+ RTA_PAYLOAD(tb[IFLA_IPOIB_MODE]) < sizeof(__u16))
+ return;
+
+ mode = rta_getattr_u16(tb[IFLA_IPOIB_MODE]);
+
+ const char *mode_str =
+ mode == IPOIB_MODE_DATAGRAM ? "datagram" :
+ mode == IPOIB_MODE_CONNECTED ? "connected" : "unknown";
+
+ print_string(PRINT_ANY, "mode", "mode %s ", mode_str);
+
+ if (!tb[IFLA_IPOIB_UMCAST] ||
+ RTA_PAYLOAD(tb[IFLA_IPOIB_UMCAST]) < sizeof(__u16))
+ return;
+
+ __u16 umcast = rta_getattr_u16(tb[IFLA_IPOIB_UMCAST]);
+
+ if (is_json_context()) {
+ SPRINT_BUF(b1);
+
+ snprintf(b1, sizeof(b1), "%.4x", umcast);
+ print_string(PRINT_JSON, "umcast", NULL, b1);
+ } else {
+ fprintf(f, "umcast %.4x ", umcast);
+ }
+}
+
+static void ipoib_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ print_explain(f);
+}
+
+struct link_util ipoib_link_util = {
+ .id = "ipoib",
+ .maxattr = IFLA_IPOIB_MAX,
+ .parse_opt = ipoib_parse_opt,
+ .print_opt = ipoib_print_opt,
+ .print_help = ipoib_print_help,
+};
diff --git a/ip/iplink_ipvlan.c b/ip/iplink_ipvlan.c
new file mode 100644
index 0000000..f29fa4f
--- /dev/null
+++ b/ip/iplink_ipvlan.c
@@ -0,0 +1,129 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* iplink_ipvlan.c IPVLAN/IPVTAP device support
+ *
+ * Authors: Mahesh Bandewar <maheshb@google.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <linux/if_link.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_explain(struct link_util *lu, FILE *f)
+{
+ fprintf(f,
+ "Usage: ... %s [ mode MODE ] [ FLAGS ]\n"
+ "\n"
+ "MODE: l3 | l3s | l2\n"
+ "FLAGS: bridge | private | vepa\n"
+ "(first values are the defaults if nothing is specified).\n",
+ lu->id);
+}
+
+static int ipvlan_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ __u16 flags = 0;
+ bool mflag_given = false;
+
+ while (argc > 0) {
+ if (matches(*argv, "mode") == 0) {
+ __u16 mode = 0;
+
+ NEXT_ARG();
+
+ if (strcmp(*argv, "l2") == 0)
+ mode = IPVLAN_MODE_L2;
+ else if (strcmp(*argv, "l3") == 0)
+ mode = IPVLAN_MODE_L3;
+ else if (strcmp(*argv, "l3s") == 0)
+ mode = IPVLAN_MODE_L3S;
+ else {
+ fprintf(stderr, "Error: argument of \"mode\" must be either \"l2\", \"l3\" or \"l3s\"\n");
+ return -1;
+ }
+ addattr16(n, 1024, IFLA_IPVLAN_MODE, mode);
+ } else if (matches(*argv, "private") == 0 && !mflag_given) {
+ flags |= IPVLAN_F_PRIVATE;
+ mflag_given = true;
+ } else if (matches(*argv, "vepa") == 0 && !mflag_given) {
+ flags |= IPVLAN_F_VEPA;
+ mflag_given = true;
+ } else if (matches(*argv, "bridge") == 0 && !mflag_given) {
+ mflag_given = true;
+ } else if (matches(*argv, "help") == 0) {
+ print_explain(lu, stderr);
+ return -1;
+ } else {
+ fprintf(stderr, "%s: unknown option \"%s\"?\n",
+ lu->id, *argv);
+ print_explain(lu, stderr);
+ return -1;
+ }
+ argc--;
+ argv++;
+ }
+ addattr16(n, 1024, IFLA_IPVLAN_FLAGS, flags);
+
+ return 0;
+}
+
+static void ipvlan_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+
+ if (!tb)
+ return;
+
+ if (tb[IFLA_IPVLAN_MODE]) {
+ if (RTA_PAYLOAD(tb[IFLA_IPVLAN_MODE]) == sizeof(__u16)) {
+ __u16 mode = rta_getattr_u16(tb[IFLA_IPVLAN_MODE]);
+ const char *mode_str = mode == IPVLAN_MODE_L2 ? "l2" :
+ mode == IPVLAN_MODE_L3 ? "l3" :
+ mode == IPVLAN_MODE_L3S ? "l3s" : "unknown";
+
+ print_string(PRINT_ANY, "mode", " mode %s ", mode_str);
+ }
+ }
+ if (tb[IFLA_IPVLAN_FLAGS]) {
+ if (RTA_PAYLOAD(tb[IFLA_IPVLAN_FLAGS]) == sizeof(__u16)) {
+ __u16 flags = rta_getattr_u16(tb[IFLA_IPVLAN_FLAGS]);
+
+ if (flags & IPVLAN_F_PRIVATE)
+ print_bool(PRINT_ANY, "private", "private ",
+ true);
+ else if (flags & IPVLAN_F_VEPA)
+ print_bool(PRINT_ANY, "vepa", "vepa ",
+ true);
+ else
+ print_bool(PRINT_ANY, "bridge", "bridge ",
+ true);
+ }
+ }
+}
+
+static void ipvlan_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ print_explain(lu, f);
+}
+
+struct link_util ipvlan_link_util = {
+ .id = "ipvlan",
+ .maxattr = IFLA_IPVLAN_MAX,
+ .parse_opt = ipvlan_parse_opt,
+ .print_opt = ipvlan_print_opt,
+ .print_help = ipvlan_print_help,
+};
+
+struct link_util ipvtap_link_util = {
+ .id = "ipvtap",
+ .maxattr = IFLA_IPVLAN_MAX,
+ .parse_opt = ipvlan_parse_opt,
+ .print_opt = ipvlan_print_opt,
+ .print_help = ipvlan_print_help,
+};
diff --git a/ip/iplink_macvlan.c b/ip/iplink_macvlan.c
new file mode 100644
index 0000000..6bdc76d
--- /dev/null
+++ b/ip/iplink_macvlan.c
@@ -0,0 +1,329 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iplink_macvlan.c macvlan/macvtap device support
+ *
+ * Authors: Patrick McHardy <kaber@trash.net>
+ * Arnd Bergmann <arnd@arndb.de>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <linux/if_link.h>
+#include <linux/if_ether.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+#define pfx_err(lu, ...) { \
+ fprintf(stderr, "%s: ", lu->id); \
+ fprintf(stderr, __VA_ARGS__); \
+ fprintf(stderr, "\n"); \
+}
+
+static void print_explain(struct link_util *lu, FILE *f)
+{
+ fprintf(f,
+ "Usage: ... %s mode MODE [flag MODE_FLAG] MODE_OPTS [bcqueuelen BC_QUEUE_LEN] [bclim BCLIM]\n"
+ "\n"
+ "MODE: private | vepa | bridge | passthru | source\n"
+ "MODE_FLAG: null | nopromisc | nodst\n"
+ "MODE_OPTS: for mode \"source\":\n"
+ "\tmacaddr { { add | del } <macaddr> | set [ <macaddr> [ <macaddr> ... ] ] | flush }\n"
+ "BC_QUEUE_LEN: Length of the rx queue for broadcast/multicast: [0-4294967295]\n"
+ "BCLIM: Threshold for broadcast queueing: 32-bit integer\n",
+ lu->id
+ );
+}
+
+static void explain(struct link_util *lu)
+{
+ print_explain(lu, stderr);
+}
+
+
+static int mode_arg(const char *arg)
+{
+ fprintf(stderr,
+ "Error: argument of \"mode\" must be \"private\", \"vepa\", \"bridge\", \"passthru\" or \"source\", not \"%s\"\n",
+ arg);
+ return -1;
+}
+
+static int flag_arg(const char *arg)
+{
+ fprintf(stderr,
+ "Error: argument of \"flag\" must be \"nopromisc\", \"nodst\" or \"null\", not \"%s\"\n",
+ arg);
+ return -1;
+}
+
+static int bc_queue_len_arg(const char *arg)
+{
+ fprintf(stderr,
+ "Error: argument of \"bcqueuelen\" must be a positive integer [0-4294967295], not \"%s\"\n",
+ arg);
+ return -1;
+}
+
+static int bclim_arg(const char *arg)
+{
+ fprintf(stderr, "Error: illegal value for \"bclim\": \"%s\"\n", arg);
+ return -1;
+}
+
+static int macvlan_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ __u32 mode = 0;
+ __u16 flags = 0;
+ __u32 mac_mode = 0;
+ int has_flags = 0;
+ char mac[ETH_ALEN];
+ struct rtattr *nmac;
+
+ while (argc > 0) {
+ if (matches(*argv, "mode") == 0) {
+ NEXT_ARG();
+
+ if (strcmp(*argv, "private") == 0)
+ mode = MACVLAN_MODE_PRIVATE;
+ else if (strcmp(*argv, "vepa") == 0)
+ mode = MACVLAN_MODE_VEPA;
+ else if (strcmp(*argv, "bridge") == 0)
+ mode = MACVLAN_MODE_BRIDGE;
+ else if (strcmp(*argv, "passthru") == 0)
+ mode = MACVLAN_MODE_PASSTHRU;
+ else if (strcmp(*argv, "source") == 0)
+ mode = MACVLAN_MODE_SOURCE;
+ else
+ return mode_arg(*argv);
+ } else if (matches(*argv, "flag") == 0) {
+ NEXT_ARG();
+
+ if (strcmp(*argv, "nopromisc") == 0)
+ flags |= MACVLAN_FLAG_NOPROMISC;
+ else if (strcmp(*argv, "nodst") == 0)
+ flags |= MACVLAN_FLAG_NODST;
+ else if (strcmp(*argv, "null") == 0)
+ flags |= 0;
+ else
+ return flag_arg(*argv);
+
+ has_flags = 1;
+
+ } else if (matches(*argv, "macaddr") == 0) {
+ NEXT_ARG();
+
+ if (strcmp(*argv, "add") == 0) {
+ mac_mode = MACVLAN_MACADDR_ADD;
+ } else if (strcmp(*argv, "del") == 0) {
+ mac_mode = MACVLAN_MACADDR_DEL;
+ } else if (strcmp(*argv, "set") == 0) {
+ mac_mode = MACVLAN_MACADDR_SET;
+ } else if (strcmp(*argv, "flush") == 0) {
+ mac_mode = MACVLAN_MACADDR_FLUSH;
+ } else {
+ explain(lu);
+ return -1;
+ }
+
+ addattr32(n, 1024, IFLA_MACVLAN_MACADDR_MODE, mac_mode);
+
+ if (mac_mode == MACVLAN_MACADDR_ADD ||
+ mac_mode == MACVLAN_MACADDR_DEL) {
+ NEXT_ARG();
+
+ if (ll_addr_a2n(mac, sizeof(mac),
+ *argv) != ETH_ALEN)
+ return -1;
+
+ addattr_l(n, 1024, IFLA_MACVLAN_MACADDR, &mac,
+ ETH_ALEN);
+ }
+
+ if (mac_mode == MACVLAN_MACADDR_SET) {
+ nmac = addattr_nest(n, 1024,
+ IFLA_MACVLAN_MACADDR_DATA);
+ while (NEXT_ARG_OK()) {
+ NEXT_ARG_FWD();
+
+ if (ll_addr_a2n(mac, sizeof(mac),
+ *argv) != ETH_ALEN) {
+ PREV_ARG();
+ break;
+ }
+
+ addattr_l(n, 1024, IFLA_MACVLAN_MACADDR,
+ &mac, ETH_ALEN);
+ }
+ addattr_nest_end(n, nmac);
+ }
+ } else if (matches(*argv, "nopromisc") == 0) {
+ flags |= MACVLAN_FLAG_NOPROMISC;
+ has_flags = 1;
+ } else if (matches(*argv, "nodst") == 0) {
+ flags |= MACVLAN_FLAG_NODST;
+ has_flags = 1;
+ } else if (matches(*argv, "bcqueuelen") == 0) {
+ __u32 bc_queue_len;
+ NEXT_ARG();
+
+ if (get_u32(&bc_queue_len, *argv, 0)) {
+ return bc_queue_len_arg(*argv);
+ }
+ addattr32(n, 1024, IFLA_MACVLAN_BC_QUEUE_LEN, bc_queue_len);
+ } else if (!strcmp(*argv, "bclim")) {
+ __s32 bclim;
+ NEXT_ARG();
+
+ if (get_s32(&bclim, *argv, 0)) {
+ return bclim_arg(*argv);
+ }
+ addattr_l(n, 1024, IFLA_MACVLAN_BC_CUTOFF,
+ &bclim, sizeof(bclim));
+ } else if (matches(*argv, "help") == 0) {
+ explain(lu);
+ return -1;
+ } else {
+ pfx_err(lu, "unknown option \"%s\"?", *argv);
+ explain(lu);
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ if (mode)
+ addattr32(n, 1024, IFLA_MACVLAN_MODE, mode);
+
+ if (has_flags) {
+ if (flags & MACVLAN_FLAG_NOPROMISC &&
+ mode != MACVLAN_MODE_PASSTHRU) {
+ pfx_err(lu, "nopromisc flag only valid in passthru mode");
+ explain(lu);
+ return -1;
+ }
+ addattr16(n, 1024, IFLA_MACVLAN_FLAGS, flags);
+ }
+ return 0;
+}
+
+static void macvlan_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ __u32 mode;
+ __u16 flags;
+ __u32 count;
+ unsigned char *addr;
+ int len;
+ struct rtattr *rta;
+
+ if (!tb)
+ return;
+
+ if (!tb[IFLA_MACVLAN_MODE] ||
+ RTA_PAYLOAD(tb[IFLA_MACVLAN_MODE]) < sizeof(__u32))
+ return;
+
+ mode = rta_getattr_u32(tb[IFLA_MACVLAN_MODE]);
+ print_string(PRINT_ANY,
+ "mode",
+ "mode %s ",
+ mode == MACVLAN_MODE_PRIVATE ? "private"
+ : mode == MACVLAN_MODE_VEPA ? "vepa"
+ : mode == MACVLAN_MODE_BRIDGE ? "bridge"
+ : mode == MACVLAN_MODE_PASSTHRU ? "passthru"
+ : mode == MACVLAN_MODE_SOURCE ? "source"
+ : "unknown");
+
+ if (!tb[IFLA_MACVLAN_FLAGS] ||
+ RTA_PAYLOAD(tb[IFLA_MACVLAN_FLAGS]) < sizeof(__u16))
+ flags = 0;
+ else
+ flags = rta_getattr_u16(tb[IFLA_MACVLAN_FLAGS]);
+
+ if (flags & MACVLAN_FLAG_NOPROMISC)
+ print_bool(PRINT_ANY, "nopromisc", "nopromisc ", true);
+
+ if (flags & MACVLAN_FLAG_NODST)
+ print_bool(PRINT_ANY, "nodst", "nodst ", true);
+
+ if (tb[IFLA_MACVLAN_BC_QUEUE_LEN] &&
+ RTA_PAYLOAD(tb[IFLA_MACVLAN_BC_QUEUE_LEN]) >= sizeof(__u32)) {
+ __u32 bc_queue_len = rta_getattr_u32(tb[IFLA_MACVLAN_BC_QUEUE_LEN]);
+ print_luint(PRINT_ANY, "bcqueuelen", "bcqueuelen %lu ", bc_queue_len);
+ }
+
+ if (tb[IFLA_MACVLAN_BC_QUEUE_LEN_USED] &&
+ RTA_PAYLOAD(tb[IFLA_MACVLAN_BC_QUEUE_LEN_USED]) >= sizeof(__u32)) {
+ __u32 bc_queue_len = rta_getattr_u32(tb[IFLA_MACVLAN_BC_QUEUE_LEN_USED]);
+ print_luint(PRINT_ANY, "usedbcqueuelen", "usedbcqueuelen %lu ", bc_queue_len);
+ }
+
+ if (tb[IFLA_MACVLAN_BC_CUTOFF] &&
+ RTA_PAYLOAD(tb[IFLA_MACVLAN_BC_CUTOFF]) >= sizeof(__s32)) {
+ __s32 bclim = rta_getattr_s32(tb[IFLA_MACVLAN_BC_CUTOFF]);
+ print_int(PRINT_ANY, "bclim", "bclim %d ", bclim);
+ }
+
+ /* in source mode, there are more options to print */
+
+ if (mode != MACVLAN_MODE_SOURCE)
+ return;
+
+ if (!tb[IFLA_MACVLAN_MACADDR_COUNT] ||
+ RTA_PAYLOAD(tb[IFLA_MACVLAN_MACADDR_COUNT]) < sizeof(__u32))
+ return;
+
+ count = rta_getattr_u32(tb[IFLA_MACVLAN_MACADDR_COUNT]);
+ print_int(PRINT_ANY, "macaddr_count", "remotes (%d) ", count);
+
+ if (!tb[IFLA_MACVLAN_MACADDR_DATA])
+ return;
+
+ rta = RTA_DATA(tb[IFLA_MACVLAN_MACADDR_DATA]);
+ len = RTA_PAYLOAD(tb[IFLA_MACVLAN_MACADDR_DATA]);
+
+ open_json_array(PRINT_JSON, "macaddr_data");
+ for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
+ if (rta->rta_type != IFLA_MACVLAN_MACADDR ||
+ RTA_PAYLOAD(rta) < 6)
+ continue;
+ addr = RTA_DATA(rta);
+ if (is_json_context()) {
+ SPRINT_BUF(b1);
+
+ snprintf(b1, sizeof(b1),
+ "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", addr[0],
+ addr[1], addr[2], addr[3], addr[4], addr[5]);
+ print_string(PRINT_JSON, NULL, NULL, b1);
+ } else {
+ fprintf(f, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x ", addr[0],
+ addr[1], addr[2], addr[3], addr[4], addr[5]);
+ }
+ }
+ close_json_array(PRINT_JSON, NULL);
+}
+
+static void macvlan_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ print_explain(lu, f);
+}
+
+struct link_util macvlan_link_util = {
+ .id = "macvlan",
+ .maxattr = IFLA_MACVLAN_MAX,
+ .parse_opt = macvlan_parse_opt,
+ .print_opt = macvlan_print_opt,
+ .print_help = macvlan_print_help,
+};
+
+struct link_util macvtap_link_util = {
+ .id = "macvtap",
+ .maxattr = IFLA_MACVLAN_MAX,
+ .parse_opt = macvlan_parse_opt,
+ .print_opt = macvlan_print_opt,
+ .print_help = macvlan_print_help,
+};
diff --git a/ip/iplink_netdevsim.c b/ip/iplink_netdevsim.c
new file mode 100644
index 0000000..5aaa775
--- /dev/null
+++ b/ip/iplink_netdevsim.c
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void netdevsim_print_help(struct link_util *lu,
+ int argc, char **argv, FILE *f)
+{
+ fprintf(f, "Usage: ... netdevsim\n");
+}
+
+struct link_util netdevsim_link_util = {
+ .id = "netdevsim",
+ .print_help = netdevsim_print_help,
+};
diff --git a/ip/iplink_netkit.c b/ip/iplink_netkit.c
new file mode 100644
index 0000000..a838a41
--- /dev/null
+++ b/ip/iplink_netkit.c
@@ -0,0 +1,183 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iplink_netkit.c netkit device management
+ *
+ * Authors: Daniel Borkmann <daniel@iogearbox.net>
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <linux/if_link.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static const char * const netkit_mode_strings[] = {
+ [NETKIT_L2] = "l2",
+ [NETKIT_L3] = "l3",
+};
+
+static const char * const netkit_policy_strings[] = {
+ [NETKIT_PASS] = "forward",
+ [NETKIT_DROP] = "blackhole",
+};
+
+static void explain(struct link_util *lu, FILE *f)
+{
+ fprintf(f,
+ "Usage: ... %s [ mode MODE ] [ POLICY ] [ peer [ POLICY <options> ] ]\n"
+ "\n"
+ "MODE: l3 | l2\n"
+ "POLICY: forward | blackhole\n"
+ "(first values are the defaults if nothing is specified)\n"
+ "\n"
+ "To get <options> type 'ip link add help'.\n",
+ lu->id);
+}
+
+static int netkit_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ __u32 ifi_flags, ifi_change, ifi_index;
+ struct ifinfomsg *ifm, *peer_ifm;
+ static bool seen_mode, seen_peer;
+ static struct rtattr *data;
+ int err;
+
+ ifm = NLMSG_DATA(n);
+ ifi_flags = ifm->ifi_flags;
+ ifi_change = ifm->ifi_change;
+ ifi_index = ifm->ifi_index;
+ ifm->ifi_flags = 0;
+ ifm->ifi_change = 0;
+ ifm->ifi_index = 0;
+ while (argc > 0) {
+ if (strcmp(*argv, "mode") == 0) {
+ __u32 mode = 0;
+
+ NEXT_ARG();
+ if (seen_mode)
+ duparg("mode", *argv);
+ seen_mode = true;
+
+ if (strcmp(*argv, "l3") == 0) {
+ mode = NETKIT_L3;
+ } else if (strcmp(*argv, "l2") == 0) {
+ mode = NETKIT_L2;
+ } else {
+ fprintf(stderr, "Error: argument of \"mode\" must be either \"l3\" or \"l2\"\n");
+ return -1;
+ }
+ addattr32(n, 1024, IFLA_NETKIT_MODE, mode);
+ } else if (strcmp(*argv, "forward") == 0 ||
+ strcmp(*argv, "blackhole") == 0) {
+ int attr_name = seen_peer ?
+ IFLA_NETKIT_PEER_POLICY :
+ IFLA_NETKIT_POLICY;
+ __u32 policy = 0;
+
+ if (strcmp(*argv, "forward") == 0) {
+ policy = NETKIT_PASS;
+ } else if (strcmp(*argv, "blackhole") == 0) {
+ policy = NETKIT_DROP;
+ } else {
+ fprintf(stderr, "Error: policy must be either \"forward\" or \"blackhole\"\n");
+ return -1;
+ }
+ addattr32(n, 1024, attr_name, policy);
+ } else if (strcmp(*argv, "peer") == 0) {
+ if (seen_peer)
+ duparg("peer", *(argv + 1));
+ seen_peer = true;
+ } else {
+ char *type = NULL;
+
+ if (seen_peer) {
+ data = addattr_nest(n, 1024, IFLA_NETKIT_PEER_INFO);
+ n->nlmsg_len += sizeof(struct ifinfomsg);
+ err = iplink_parse(argc, argv, (struct iplink_req *)n, &type);
+ if (err < 0)
+ return err;
+ if (type)
+ duparg("type", argv[err]);
+ goto out_ok;
+ }
+ fprintf(stderr, "%s: unknown option \"%s\"?\n",
+ lu->id, *argv);
+ explain(lu, stderr);
+ return -1;
+ }
+ argc--;
+ argv++;
+ }
+out_ok:
+ if (data) {
+ peer_ifm = RTA_DATA(data);
+ peer_ifm->ifi_index = ifm->ifi_index;
+ peer_ifm->ifi_flags = ifm->ifi_flags;
+ peer_ifm->ifi_change = ifm->ifi_change;
+ addattr_nest_end(n, data);
+ }
+ ifm->ifi_flags = ifi_flags;
+ ifm->ifi_change = ifi_change;
+ ifm->ifi_index = ifi_index;
+ return 0;
+}
+
+static const char *netkit_print_policy(__u32 policy)
+{
+ const char *inv = "UNKNOWN";
+
+ if (policy >= ARRAY_SIZE(netkit_policy_strings))
+ return inv;
+ return netkit_policy_strings[policy] ? : inv;
+}
+
+static const char *netkit_print_mode(__u32 mode)
+{
+ const char *inv = "UNKNOWN";
+
+ if (mode >= ARRAY_SIZE(netkit_mode_strings))
+ return inv;
+ return netkit_mode_strings[mode] ? : inv;
+}
+
+static void netkit_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ if (!tb)
+ return;
+ if (tb[IFLA_NETKIT_MODE]) {
+ __u32 mode = rta_getattr_u32(tb[IFLA_NETKIT_MODE]);
+
+ print_string(PRINT_ANY, "mode", "mode %s ",
+ netkit_print_mode(mode));
+ }
+ if (tb[IFLA_NETKIT_PRIMARY]) {
+ __u8 primary = rta_getattr_u8(tb[IFLA_NETKIT_PRIMARY]);
+
+ print_string(PRINT_ANY, "type", "type %s ",
+ primary ? "primary" : "peer");
+ }
+ if (tb[IFLA_NETKIT_POLICY]) {
+ __u32 policy = rta_getattr_u32(tb[IFLA_NETKIT_POLICY]);
+
+ print_string(PRINT_ANY, "policy", "policy %s ",
+ netkit_print_policy(policy));
+ }
+}
+
+static void netkit_print_help(struct link_util *lu,
+ int argc, char **argv, FILE *f)
+{
+ explain(lu, f);
+}
+
+struct link_util netkit_link_util = {
+ .id = "netkit",
+ .maxattr = IFLA_NETKIT_MAX,
+ .parse_opt = netkit_parse_opt,
+ .print_opt = netkit_print_opt,
+ .print_help = netkit_print_help,
+};
diff --git a/ip/iplink_nlmon.c b/ip/iplink_nlmon.c
new file mode 100644
index 0000000..6ffb910
--- /dev/null
+++ b/ip/iplink_nlmon.c
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void nlmon_print_help(struct link_util *lu,
+ int argc, char **argv, FILE *f)
+{
+ fprintf(f, "Usage: ... nlmon\n");
+}
+
+struct link_util nlmon_link_util = {
+ .id = "nlmon",
+ .print_help = nlmon_print_help,
+};
diff --git a/ip/iplink_rmnet.c b/ip/iplink_rmnet.c
new file mode 100644
index 0000000..1d16440
--- /dev/null
+++ b/ip/iplink_rmnet.c
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * iplink_rmnet.c RMNET device support
+ *
+ * Authors: Daniele Palmas <dnlplm@gmail.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_explain(FILE *f)
+{
+ fprintf(f,
+ "Usage: ... rmnet mux_id MUXID\n"
+ "\n"
+ "MUXID := 1-254\n"
+ );
+}
+
+static void explain(void)
+{
+ print_explain(stderr);
+}
+
+static int rmnet_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ __u16 mux_id;
+
+ while (argc > 0) {
+ if (matches(*argv, "mux_id") == 0) {
+ NEXT_ARG();
+ if (get_u16(&mux_id, *argv, 0))
+ invarg("mux_id is invalid", *argv);
+ addattr16(n, 1024, IFLA_RMNET_MUX_ID, mux_id);
+ } else if (matches(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "rmnet: unknown command \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ return 0;
+}
+
+static void rmnet_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ if (!tb)
+ return;
+
+ if (!tb[IFLA_RMNET_MUX_ID] ||
+ RTA_PAYLOAD(tb[IFLA_RMNET_MUX_ID]) < sizeof(__u16))
+ return;
+
+ print_uint(PRINT_ANY,
+ "mux_id",
+ "mux_id %u ",
+ rta_getattr_u16(tb[IFLA_RMNET_MUX_ID]));
+}
+
+static void rmnet_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ print_explain(f);
+}
+
+struct link_util rmnet_link_util = {
+ .id = "rmnet",
+ .maxattr = IFLA_RMNET_MAX,
+ .parse_opt = rmnet_parse_opt,
+ .print_opt = rmnet_print_opt,
+ .print_help = rmnet_print_help,
+};
diff --git a/ip/iplink_team.c b/ip/iplink_team.c
new file mode 100644
index 0000000..58f955a
--- /dev/null
+++ b/ip/iplink_team.c
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void team_print_help(struct link_util *lu,
+ int argc, char **argv, FILE *f)
+{
+ fprintf(f, "Usage: ... team\n");
+}
+
+static void team_slave_print_help(struct link_util *lu,
+ int argc, char **argv, FILE *f)
+{
+ fprintf(f, "Usage: ... team_slave\n");
+}
+
+struct link_util team_link_util = {
+ .id = "team",
+ .print_help = team_print_help,
+}, team_slave_link_util = {
+ .id = "team_slave",
+ .print_help = team_slave_print_help,
+};
diff --git a/ip/iplink_vcan.c b/ip/iplink_vcan.c
new file mode 100644
index 0000000..74a1505
--- /dev/null
+++ b/ip/iplink_vcan.c
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void vcan_print_help(struct link_util *lu,
+ int argc, char **argv, FILE *f)
+{
+ fprintf(f, "Usage: ... vcan\n");
+}
+
+struct link_util vcan_link_util = {
+ .id = "vcan",
+ .print_help = vcan_print_help,
+};
diff --git a/ip/iplink_virt_wifi.c b/ip/iplink_virt_wifi.c
new file mode 100644
index 0000000..8d3054c
--- /dev/null
+++ b/ip/iplink_virt_wifi.c
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * iplink_virt_wifi.c A fake implementation of cfg80211_ops that can be tacked
+ * on to an ethernet net_device to make it appear as a
+ * wireless connection.
+ *
+ * Authors: Baligh Gasmi <gasmibal@gmail.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void virt_wifi_print_help(struct link_util *lu,
+ int argc, char **argv, FILE *f)
+{
+ fprintf(f, "Usage: ... virt_wifi \n");
+}
+
+struct link_util virt_wifi_link_util = {
+ .id = "virt_wifi",
+ .print_help = virt_wifi_print_help,
+};
diff --git a/ip/iplink_vlan.c b/ip/iplink_vlan.c
new file mode 100644
index 0000000..4ac5bc0
--- /dev/null
+++ b/ip/iplink_vlan.c
@@ -0,0 +1,276 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iplink_vlan.c VLAN device support
+ *
+ * Authors: Patrick McHardy <kaber@trash.net>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/if_vlan.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_explain(FILE *f)
+{
+ fprintf(f,
+ "Usage: ... vlan id VLANID\n"
+ " [ protocol VLANPROTO ]\n"
+ " [ reorder_hdr { on | off } ]\n"
+ " [ gvrp { on | off } ]\n"
+ " [ mvrp { on | off } ]\n"
+ " [ loose_binding { on | off } ]\n"
+ " [ bridge_binding { on | off } ]\n"
+ " [ ingress-qos-map QOS-MAP ]\n"
+ " [ egress-qos-map QOS-MAP ]\n"
+ "\n"
+ "VLANID := 0-4095\n"
+ "VLANPROTO: [ 802.1Q | 802.1ad ]\n"
+ "QOS-MAP := [ QOS-MAP ] QOS-MAPPING\n"
+ "QOS-MAPPING := FROM:TO\n"
+ );
+}
+
+static void explain(void)
+{
+ print_explain(stderr);
+}
+
+static int on_off(const char *msg, const char *arg)
+{
+ fprintf(stderr, "Error: argument of \"%s\" must be \"on\" or \"off\", not \"%s\"\n", msg, arg);
+ return -1;
+}
+
+static int parse_qos_mapping(__u32 key, char *value, void *data)
+{
+ struct nlmsghdr *n = data;
+ struct ifla_vlan_qos_mapping m = {
+ .from = key,
+ };
+
+ if (get_u32(&m.to, value, 0))
+ return 1;
+
+ return addattr_l(n, 1024, IFLA_VLAN_QOS_MAPPING, &m, sizeof(m));
+}
+
+static int vlan_parse_qos_map(int *argcp, char ***argvp, struct nlmsghdr *n,
+ int attrtype)
+{
+ struct rtattr *tail;
+
+ tail = addattr_nest(n, 1024, attrtype);
+
+ if (parse_mapping(argcp, argvp, false, &parse_qos_mapping, n))
+ return 1;
+
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static int vlan_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ struct ifla_vlan_flags flags = { 0 };
+ __u16 id, proto;
+
+ while (argc > 0) {
+ if (matches(*argv, "protocol") == 0) {
+ NEXT_ARG();
+ if (ll_proto_a2n(&proto, *argv))
+ invarg("protocol is invalid", *argv);
+ addattr_l(n, 1024, IFLA_VLAN_PROTOCOL, &proto, 2);
+ } else if (matches(*argv, "id") == 0) {
+ NEXT_ARG();
+ if (get_u16(&id, *argv, 0))
+ invarg("id is invalid", *argv);
+ addattr_l(n, 1024, IFLA_VLAN_ID, &id, 2);
+ } else if (matches(*argv, "reorder_hdr") == 0) {
+ NEXT_ARG();
+ flags.mask |= VLAN_FLAG_REORDER_HDR;
+ if (strcmp(*argv, "on") == 0)
+ flags.flags |= VLAN_FLAG_REORDER_HDR;
+ else if (strcmp(*argv, "off") == 0)
+ flags.flags &= ~VLAN_FLAG_REORDER_HDR;
+ else
+ return on_off("reorder_hdr", *argv);
+ } else if (matches(*argv, "gvrp") == 0) {
+ NEXT_ARG();
+ flags.mask |= VLAN_FLAG_GVRP;
+ if (strcmp(*argv, "on") == 0)
+ flags.flags |= VLAN_FLAG_GVRP;
+ else if (strcmp(*argv, "off") == 0)
+ flags.flags &= ~VLAN_FLAG_GVRP;
+ else
+ return on_off("gvrp", *argv);
+ } else if (matches(*argv, "mvrp") == 0) {
+ NEXT_ARG();
+ flags.mask |= VLAN_FLAG_MVRP;
+ if (strcmp(*argv, "on") == 0)
+ flags.flags |= VLAN_FLAG_MVRP;
+ else if (strcmp(*argv, "off") == 0)
+ flags.flags &= ~VLAN_FLAG_MVRP;
+ else
+ return on_off("mvrp", *argv);
+ } else if (matches(*argv, "loose_binding") == 0) {
+ NEXT_ARG();
+ flags.mask |= VLAN_FLAG_LOOSE_BINDING;
+ if (strcmp(*argv, "on") == 0)
+ flags.flags |= VLAN_FLAG_LOOSE_BINDING;
+ else if (strcmp(*argv, "off") == 0)
+ flags.flags &= ~VLAN_FLAG_LOOSE_BINDING;
+ else
+ return on_off("loose_binding", *argv);
+ } else if (matches(*argv, "bridge_binding") == 0) {
+ NEXT_ARG();
+ flags.mask |= VLAN_FLAG_BRIDGE_BINDING;
+ if (strcmp(*argv, "on") == 0)
+ flags.flags |= VLAN_FLAG_BRIDGE_BINDING;
+ else if (strcmp(*argv, "off") == 0)
+ flags.flags &= ~VLAN_FLAG_BRIDGE_BINDING;
+ else
+ return on_off("bridge_binding", *argv);
+ } else if (matches(*argv, "ingress-qos-map") == 0) {
+ NEXT_ARG();
+ if (vlan_parse_qos_map(&argc, &argv, n,
+ IFLA_VLAN_INGRESS_QOS))
+ invarg("invalid ingress-qos-map", *argv);
+ continue;
+ } else if (matches(*argv, "egress-qos-map") == 0) {
+ NEXT_ARG();
+ if (vlan_parse_qos_map(&argc, &argv, n,
+ IFLA_VLAN_EGRESS_QOS))
+ invarg("invalid egress-qos-map", *argv);
+ continue;
+ } else if (matches(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "vlan: unknown command \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ if (flags.mask)
+ addattr_l(n, 1024, IFLA_VLAN_FLAGS, &flags, sizeof(flags));
+
+ return 0;
+}
+
+static void vlan_print_map(FILE *f,
+ const char *name_json,
+ const char *name_fp,
+ struct rtattr *attr)
+{
+ struct ifla_vlan_qos_mapping *m;
+ struct rtattr *i;
+ int rem;
+
+ open_json_array(PRINT_JSON, name_json);
+ print_nl();
+ print_string(PRINT_FP, NULL, " %s { ", name_fp);
+
+ rem = RTA_PAYLOAD(attr);
+ for (i = RTA_DATA(attr); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+ m = RTA_DATA(i);
+
+ if (is_json_context()) {
+ open_json_object(NULL);
+ print_uint(PRINT_JSON, "from", NULL, m->from);
+ print_uint(PRINT_JSON, "to", NULL, m->to);
+ close_json_object();
+ } else {
+ fprintf(f, "%u:%u ", m->from, m->to);
+ }
+ }
+
+ close_json_array(PRINT_JSON, NULL);
+ print_string(PRINT_FP, NULL, "%s ", "}");
+}
+
+static void vlan_print_flags(FILE *fp, __u32 flags)
+{
+ open_json_array(PRINT_ANY, is_json_context() ? "flags" : "<");
+#define _PF(f) if (flags & VLAN_FLAG_##f) { \
+ flags &= ~VLAN_FLAG_##f; \
+ print_string(PRINT_ANY, NULL, flags ? "%s," : "%s", #f); \
+ }
+ _PF(REORDER_HDR);
+ _PF(GVRP);
+ _PF(MVRP);
+ _PF(LOOSE_BINDING);
+ _PF(BRIDGE_BINDING);
+#undef _PF
+ if (flags)
+ print_hex(PRINT_ANY, NULL, "%x", flags);
+ close_json_array(PRINT_ANY, "> ");
+}
+
+static void vlan_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ struct ifla_vlan_flags *flags;
+
+ SPRINT_BUF(b1);
+
+ if (!tb)
+ return;
+
+ if (tb[IFLA_VLAN_PROTOCOL] &&
+ RTA_PAYLOAD(tb[IFLA_VLAN_PROTOCOL]) < sizeof(__u16))
+ return;
+ if (!tb[IFLA_VLAN_ID] ||
+ RTA_PAYLOAD(tb[IFLA_VLAN_ID]) < sizeof(__u16))
+ return;
+
+ if (tb[IFLA_VLAN_PROTOCOL])
+ print_string(PRINT_ANY,
+ "protocol",
+ "protocol %s ",
+ ll_proto_n2a(
+ rta_getattr_u16(tb[IFLA_VLAN_PROTOCOL]),
+ b1, sizeof(b1)));
+ else
+ print_string(PRINT_ANY, "protocol", "protocol %s ", "802.1q");
+
+ print_uint(PRINT_ANY,
+ "id",
+ "id %u ",
+ rta_getattr_u16(tb[IFLA_VLAN_ID]));
+
+ if (tb[IFLA_VLAN_FLAGS]) {
+ if (RTA_PAYLOAD(tb[IFLA_VLAN_FLAGS]) < sizeof(*flags))
+ return;
+ flags = RTA_DATA(tb[IFLA_VLAN_FLAGS]);
+ vlan_print_flags(f, flags->flags);
+ }
+ if (tb[IFLA_VLAN_INGRESS_QOS])
+ vlan_print_map(f,
+ "ingress_qos",
+ "ingress-qos-map",
+ tb[IFLA_VLAN_INGRESS_QOS]);
+ if (tb[IFLA_VLAN_EGRESS_QOS])
+ vlan_print_map(f,
+ "egress_qos",
+ "egress-qos-map",
+ tb[IFLA_VLAN_EGRESS_QOS]);
+}
+
+static void vlan_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ print_explain(f);
+}
+
+struct link_util vlan_link_util = {
+ .id = "vlan",
+ .maxattr = IFLA_VLAN_MAX,
+ .parse_opt = vlan_parse_opt,
+ .print_opt = vlan_print_opt,
+ .print_help = vlan_print_help,
+};
diff --git a/ip/iplink_vrf.c b/ip/iplink_vrf.c
new file mode 100644
index 0000000..9474a2b
--- /dev/null
+++ b/ip/iplink_vrf.c
@@ -0,0 +1,222 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* iplink_vrf.c VRF device support
+ *
+ * Authors: Shrijeet Mukherjee <shm@cumulusnetworks.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <linux/if_link.h>
+#include <errno.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void vrf_explain(FILE *f)
+{
+ fprintf(f, "Usage: ... vrf table TABLEID\n");
+}
+
+static void explain(void)
+{
+ vrf_explain(stderr);
+}
+
+static int vrf_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ while (argc > 0) {
+ if (matches(*argv, "table") == 0) {
+ __u32 table;
+
+ NEXT_ARG();
+
+ if (rtnl_rttable_a2n(&table, *argv))
+ invarg("invalid table ID\n", *argv);
+ addattr32(n, 1024, IFLA_VRF_TABLE, table);
+ } else if (matches(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "vrf: unknown option \"%s\"?\n",
+ *argv);
+ explain();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ return 0;
+}
+
+static void vrf_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ if (!tb)
+ return;
+
+ if (tb[IFLA_VRF_TABLE])
+ print_uint(PRINT_ANY,
+ "table",
+ "table %u ",
+ rta_getattr_u32(tb[IFLA_VRF_TABLE]));
+}
+
+static void vrf_slave_print_opt(struct link_util *lu, FILE *f,
+ struct rtattr *tb[])
+{
+ if (!tb)
+ return;
+
+ if (tb[IFLA_VRF_PORT_TABLE]) {
+ print_uint(PRINT_ANY,
+ "table",
+ "table %u ",
+ rta_getattr_u32(tb[IFLA_VRF_PORT_TABLE]));
+ }
+}
+
+static void vrf_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ vrf_explain(f);
+}
+
+struct link_util vrf_link_util = {
+ .id = "vrf",
+ .maxattr = IFLA_VRF_MAX,
+ .parse_opt = vrf_parse_opt,
+ .print_opt = vrf_print_opt,
+ .print_help = vrf_print_help,
+};
+
+struct link_util vrf_slave_link_util = {
+ .id = "vrf_slave",
+ .maxattr = IFLA_VRF_PORT_MAX,
+ .print_opt = vrf_slave_print_opt,
+};
+
+/* returns table id if name is a VRF device */
+__u32 ipvrf_get_table(const char *name)
+{
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg i;
+ char buf[1024];
+ } req = {
+ .n = {
+ .nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .nlmsg_flags = NLM_F_REQUEST,
+ .nlmsg_type = RTM_GETLINK,
+ },
+ .i = {
+ .ifi_family = preferred_family,
+ },
+ };
+ struct nlmsghdr *answer;
+ struct rtattr *tb[IFLA_MAX+1];
+ struct rtattr *li[IFLA_INFO_MAX+1];
+ struct rtattr *vrf_attr[IFLA_VRF_MAX + 1];
+ struct ifinfomsg *ifi;
+ __u32 tb_id = 0;
+ int len;
+
+ addattr_l(&req.n, sizeof(req), IFLA_IFNAME, name, strlen(name) + 1);
+
+ if (rtnl_talk_suppress_rtnl_errmsg(&rth, &req.n, &answer) < 0) {
+ /* special case "default" vrf to be the main table */
+ if (errno == ENODEV && !strcmp(name, "default"))
+ if (rtnl_rttable_a2n(&tb_id, "main"))
+ fprintf(stderr,
+ "BUG: RTTable \"main\" not found.\n");
+
+ return tb_id;
+ }
+
+ ifi = NLMSG_DATA(answer);
+ len = answer->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi));
+ if (len < 0) {
+ fprintf(stderr, "BUG: Invalid response to link query.\n");
+ goto out;
+ }
+
+ parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+
+ if (!tb[IFLA_LINKINFO])
+ goto out;
+
+ parse_rtattr_nested(li, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+ if (!li[IFLA_INFO_KIND] || !li[IFLA_INFO_DATA])
+ goto out;
+
+ if (strcmp(RTA_DATA(li[IFLA_INFO_KIND]), "vrf"))
+ goto out;
+
+ parse_rtattr_nested(vrf_attr, IFLA_VRF_MAX, li[IFLA_INFO_DATA]);
+ if (vrf_attr[IFLA_VRF_TABLE])
+ tb_id = rta_getattr_u32(vrf_attr[IFLA_VRF_TABLE]);
+
+ if (!tb_id)
+ fprintf(stderr, "BUG: VRF %s is missing table id\n", name);
+
+out:
+ free(answer);
+ return tb_id;
+}
+
+int name_is_vrf(const char *name)
+{
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg i;
+ char buf[1024];
+ } req = {
+ .n = {
+ .nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .nlmsg_flags = NLM_F_REQUEST,
+ .nlmsg_type = RTM_GETLINK,
+ },
+ .i = {
+ .ifi_family = preferred_family,
+ },
+ };
+ struct nlmsghdr *answer;
+ struct rtattr *tb[IFLA_MAX+1];
+ struct rtattr *li[IFLA_INFO_MAX+1];
+ struct ifinfomsg *ifi;
+ int ifindex = 0;
+ int len;
+
+ addattr_l(&req.n, sizeof(req), IFLA_IFNAME, name, strlen(name) + 1);
+
+ if (rtnl_talk_suppress_rtnl_errmsg(&rth, &req.n, &answer) < 0)
+ return 0;
+
+ ifi = NLMSG_DATA(answer);
+ len = answer->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi));
+ if (len < 0) {
+ fprintf(stderr, "BUG: Invalid response to link query.\n");
+ goto out;
+ }
+
+ parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+
+ if (!tb[IFLA_LINKINFO])
+ goto out;
+
+ parse_rtattr_nested(li, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+ if (!li[IFLA_INFO_KIND])
+ goto out;
+
+ if (strcmp(RTA_DATA(li[IFLA_INFO_KIND]), "vrf"))
+ goto out;
+
+ ifindex = ifi->ifi_index;
+out:
+ free(answer);
+ return ifindex;
+}
diff --git a/ip/iplink_vxcan.c b/ip/iplink_vxcan.c
new file mode 100644
index 0000000..e0f9bac
--- /dev/null
+++ b/ip/iplink_vxcan.c
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iplink_vxcan.c vxcan device support (Virtual CAN Tunnel)
+ *
+ * Author: Oliver Hartkopp <socketcan@hartkopp.net>
+ * Based on: link_veth.c from Pavel Emelianov <xemul@openvz.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <net/if.h>
+#include <linux/can/vxcan.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_usage(FILE *f)
+{
+ printf("Usage: ip link <options> type vxcan [peer <options>]\n"
+ "To get <options> type 'ip link add help'\n");
+}
+
+static void usage(void)
+{
+ print_usage(stderr);
+}
+
+static int vxcan_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ char *type = NULL;
+ int err;
+ struct rtattr *data;
+ struct ifinfomsg *ifm, *peer_ifm;
+ unsigned int ifi_flags, ifi_change, ifi_index;
+
+ if (strcmp(argv[0], "peer") != 0) {
+ usage();
+ return -1;
+ }
+
+ ifm = NLMSG_DATA(n);
+ ifi_flags = ifm->ifi_flags;
+ ifi_change = ifm->ifi_change;
+ ifi_index = ifm->ifi_index;
+ ifm->ifi_flags = 0;
+ ifm->ifi_change = 0;
+ ifm->ifi_index = 0;
+
+ data = addattr_nest(n, 1024, VXCAN_INFO_PEER);
+
+ n->nlmsg_len += sizeof(struct ifinfomsg);
+
+ err = iplink_parse(argc - 1, argv + 1, (struct iplink_req *)n, &type);
+ if (err < 0)
+ return err;
+
+ if (type)
+ duparg("type", argv[err]);
+
+ peer_ifm = RTA_DATA(data);
+ peer_ifm->ifi_index = ifm->ifi_index;
+ peer_ifm->ifi_flags = ifm->ifi_flags;
+ peer_ifm->ifi_change = ifm->ifi_change;
+ ifm->ifi_flags = ifi_flags;
+ ifm->ifi_change = ifi_change;
+ ifm->ifi_index = ifi_index;
+
+ addattr_nest_end(n, data);
+ return argc - 1 - err;
+}
+
+static void vxcan_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ print_usage(f);
+}
+
+struct link_util vxcan_link_util = {
+ .id = "vxcan",
+ .parse_opt = vxcan_parse_opt,
+ .print_help = vxcan_print_help,
+};
diff --git a/ip/iplink_vxlan.c b/ip/iplink_vxlan.c
new file mode 100644
index 0000000..7781d60
--- /dev/null
+++ b/ip/iplink_vxlan.c
@@ -0,0 +1,634 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iplink_vxlan.c VXLAN device support
+ *
+ * Authors: Stephen Hemminger <shemminger@vyatta.com
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <net/if.h>
+#include <linux/ip.h>
+#include <linux/if_link.h>
+#include <arpa/inet.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+#define VXLAN_ATTRSET(attrs, type) (((attrs) & (1L << (type))) != 0)
+
+static const struct vxlan_bool_opt {
+ const char *key;
+ int type;
+ bool default_value;
+} vxlan_opts[] = {
+ { "external", IFLA_VXLAN_COLLECT_METADATA, false },
+ { "vnifilter", IFLA_VXLAN_VNIFILTER, false },
+ { "learning", IFLA_VXLAN_LEARNING, true },
+ { "proxy", IFLA_VXLAN_PROXY, false },
+ { "rsc", IFLA_VXLAN_RSC, false },
+ { "l2miss", IFLA_VXLAN_L2MISS, false },
+ { "l3miss", IFLA_VXLAN_L3MISS, false },
+ { "udp_csum", IFLA_VXLAN_UDP_CSUM, true },
+ { "udp_zero_csum6_tx", IFLA_VXLAN_UDP_ZERO_CSUM6_TX, false },
+ { "udp_zero_csum6_rx", IFLA_VXLAN_UDP_ZERO_CSUM6_RX, false },
+ { "remcsum_tx", IFLA_VXLAN_REMCSUM_TX, false },
+ { "remcsum_rx", IFLA_VXLAN_REMCSUM_RX, false },
+ { "localbypass", IFLA_VXLAN_LOCALBYPASS, true },
+};
+
+static void print_explain(FILE *f)
+{
+ fprintf(f,
+ "Usage: ... vxlan id VNI\n"
+ " [ { group | remote } IP_ADDRESS ]\n"
+ " [ local ADDR ]\n"
+ " [ ttl TTL ]\n"
+ " [ tos TOS ]\n"
+ " [ df DF ]\n"
+ " [ flowlabel LABEL ]\n"
+ " [ dev PHYS_DEV ]\n"
+ " [ dstport PORT ]\n"
+ " [ srcport MIN MAX ]\n"
+ " [ [no]learning ]\n"
+ " [ [no]proxy ]\n"
+ " [ [no]rsc ]\n"
+ " [ [no]l2miss ]\n"
+ " [ [no]l3miss ]\n"
+ " [ ageing SECONDS ]\n"
+ " [ maxaddress NUMBER ]\n"
+ " [ [no]udpcsum ]\n"
+ " [ [no]udp6zerocsumtx ]\n"
+ " [ [no]udp6zerocsumrx ]\n"
+ " [ [no]remcsumtx ] [ [no]remcsumrx ]\n"
+ " [ [no]localbypass ]\n"
+ " [ [no]external ] [ gbp ] [ gpe ]\n"
+ " [ [no]vnifilter ]\n"
+ "\n"
+ "Where: VNI := 0-16777215\n"
+ " ADDR := { IP_ADDRESS | any }\n"
+ " TOS := { NUMBER | inherit }\n"
+ " TTL := { 1..255 | auto | inherit }\n"
+ " DF := { unset | set | inherit }\n"
+ " LABEL := 0-1048575\n"
+ );
+}
+
+static void explain(void)
+{
+ print_explain(stderr);
+}
+
+static void check_duparg(__u64 *attrs, int type, const char *key,
+ const char *argv)
+{
+ if (!VXLAN_ATTRSET(*attrs, type)) {
+ *attrs |= (1L << type);
+ return;
+ }
+ duparg2(key, argv);
+}
+
+static int vxlan_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ inet_prefix saddr, daddr;
+ __u32 vni = 0;
+ __u8 learning = 1;
+ __u16 dstport = 0;
+ __u8 metadata = 0;
+ __u8 vnifilter = 0;
+ __u64 attrs = 0;
+ bool set_op = (n->nlmsg_type == RTM_NEWLINK &&
+ !(n->nlmsg_flags & NLM_F_CREATE));
+ bool selected_family = false;
+
+ saddr.family = daddr.family = AF_UNSPEC;
+
+ inet_prefix_reset(&saddr);
+ inet_prefix_reset(&daddr);
+
+ while (argc > 0) {
+ if (!matches(*argv, "id") ||
+ !matches(*argv, "vni")) {
+ /* We will add ID attribute outside of the loop since we
+ * need to consider metadata information as well.
+ */
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_VXLAN_ID, "id", *argv);
+ if (get_u32(&vni, *argv, 0) ||
+ vni >= 1u << 24)
+ invarg("invalid id", *argv);
+ } else if (!matches(*argv, "group")) {
+ if (is_addrtype_inet_not_multi(&daddr)) {
+ fprintf(stderr, "vxlan: both group and remote");
+ fprintf(stderr, " cannot be specified\n");
+ return -1;
+ }
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_VXLAN_GROUP, "group", *argv);
+ get_addr(&daddr, *argv, saddr.family);
+ if (!is_addrtype_inet_multi(&daddr))
+ invarg("invalid group address", *argv);
+ } else if (!matches(*argv, "remote")) {
+ if (is_addrtype_inet_multi(&daddr)) {
+ fprintf(stderr, "vxlan: both group and remote");
+ fprintf(stderr, " cannot be specified\n");
+ return -1;
+ }
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_VXLAN_GROUP, "remote", *argv);
+ get_addr(&daddr, *argv, saddr.family);
+ if (!is_addrtype_inet_not_multi(&daddr))
+ invarg("invalid remote address", *argv);
+ } else if (!matches(*argv, "local")) {
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_VXLAN_LOCAL, "local", *argv);
+ get_addr(&saddr, *argv, daddr.family);
+ if (!is_addrtype_inet_not_multi(&saddr))
+ invarg("invalid local address", *argv);
+ } else if (!matches(*argv, "dev")) {
+ unsigned int link;
+
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_VXLAN_LINK, "dev", *argv);
+ link = ll_name_to_index(*argv);
+ if (!link)
+ exit(nodev(*argv));
+ addattr32(n, 1024, IFLA_VXLAN_LINK, link);
+ } else if (!matches(*argv, "ttl") ||
+ !matches(*argv, "hoplimit")) {
+ unsigned int uval;
+ __u8 ttl = 0;
+
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_VXLAN_TTL, "ttl", *argv);
+ if (strcmp(*argv, "inherit") == 0) {
+ addattr(n, 1024, IFLA_VXLAN_TTL_INHERIT);
+ } else if (strcmp(*argv, "auto") == 0) {
+ addattr8(n, 1024, IFLA_VXLAN_TTL, ttl);
+ } else {
+ if (get_unsigned(&uval, *argv, 0))
+ invarg("invalid TTL", *argv);
+ if (uval > 255)
+ invarg("TTL must be <= 255", *argv);
+ ttl = uval;
+ addattr8(n, 1024, IFLA_VXLAN_TTL, ttl);
+ }
+ } else if (!matches(*argv, "tos") ||
+ !matches(*argv, "dsfield")) {
+ __u32 uval;
+ __u8 tos;
+
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_VXLAN_TOS, "tos", *argv);
+ if (strcmp(*argv, "inherit") != 0) {
+ if (rtnl_dsfield_a2n(&uval, *argv))
+ invarg("bad TOS value", *argv);
+ tos = uval;
+ } else
+ tos = 1;
+ addattr8(n, 1024, IFLA_VXLAN_TOS, tos);
+ } else if (!matches(*argv, "df")) {
+ enum ifla_vxlan_df df;
+
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_VXLAN_DF, "df", *argv);
+ if (strcmp(*argv, "unset") == 0)
+ df = VXLAN_DF_UNSET;
+ else if (strcmp(*argv, "set") == 0)
+ df = VXLAN_DF_SET;
+ else if (strcmp(*argv, "inherit") == 0)
+ df = VXLAN_DF_INHERIT;
+ else
+ invarg("DF must be 'unset', 'set' or 'inherit'",
+ *argv);
+
+ addattr8(n, 1024, IFLA_VXLAN_DF, df);
+ } else if (!matches(*argv, "label") ||
+ !matches(*argv, "flowlabel")) {
+ __u32 uval;
+
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_VXLAN_LABEL, "flowlabel",
+ *argv);
+ if (get_u32(&uval, *argv, 0) ||
+ (uval & ~LABEL_MAX_MASK))
+ invarg("invalid flowlabel", *argv);
+ addattr32(n, 1024, IFLA_VXLAN_LABEL, htonl(uval));
+ } else if (!matches(*argv, "ageing")) {
+ __u32 age;
+
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_VXLAN_AGEING, "ageing",
+ *argv);
+ if (strcmp(*argv, "none") == 0)
+ age = 0;
+ else if (get_u32(&age, *argv, 0))
+ invarg("ageing timer", *argv);
+ addattr32(n, 1024, IFLA_VXLAN_AGEING, age);
+ } else if (!matches(*argv, "maxaddress")) {
+ __u32 maxaddr;
+
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_VXLAN_LIMIT,
+ "maxaddress", *argv);
+ if (strcmp(*argv, "unlimited") == 0)
+ maxaddr = 0;
+ else if (get_u32(&maxaddr, *argv, 0))
+ invarg("max addresses", *argv);
+ addattr32(n, 1024, IFLA_VXLAN_LIMIT, maxaddr);
+ } else if (!matches(*argv, "port") ||
+ !matches(*argv, "srcport")) {
+ struct ifla_vxlan_port_range range = { 0, 0 };
+
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_VXLAN_PORT_RANGE, "srcport",
+ *argv);
+ if (get_be16(&range.low, *argv, 0))
+ invarg("min port", *argv);
+ NEXT_ARG();
+ if (get_be16(&range.high, *argv, 0))
+ invarg("max port", *argv);
+ if (range.low || range.high) {
+ addattr_l(n, 1024, IFLA_VXLAN_PORT_RANGE,
+ &range, sizeof(range));
+ }
+ } else if (!matches(*argv, "dstport")) {
+ NEXT_ARG();
+ check_duparg(&attrs, IFLA_VXLAN_PORT, "dstport", *argv);
+ if (get_u16(&dstport, *argv, 0))
+ invarg("dst port", *argv);
+ } else if (!matches(*argv, "nolearning")) {
+ check_duparg(&attrs, IFLA_VXLAN_LEARNING, *argv, *argv);
+ learning = 0;
+ } else if (!matches(*argv, "learning")) {
+ check_duparg(&attrs, IFLA_VXLAN_LEARNING, *argv, *argv);
+ learning = 1;
+ } else if (!matches(*argv, "noproxy")) {
+ check_duparg(&attrs, IFLA_VXLAN_PROXY, *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_PROXY, 0);
+ } else if (!matches(*argv, "proxy")) {
+ check_duparg(&attrs, IFLA_VXLAN_PROXY, *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_PROXY, 1);
+ } else if (!matches(*argv, "norsc")) {
+ check_duparg(&attrs, IFLA_VXLAN_RSC, *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_RSC, 0);
+ } else if (!matches(*argv, "rsc")) {
+ check_duparg(&attrs, IFLA_VXLAN_RSC, *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_RSC, 1);
+ } else if (!matches(*argv, "nol2miss")) {
+ check_duparg(&attrs, IFLA_VXLAN_L2MISS, *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_L2MISS, 0);
+ } else if (!matches(*argv, "l2miss")) {
+ check_duparg(&attrs, IFLA_VXLAN_L2MISS, *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_L2MISS, 1);
+ } else if (!matches(*argv, "nol3miss")) {
+ check_duparg(&attrs, IFLA_VXLAN_L3MISS, *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_L3MISS, 0);
+ } else if (!matches(*argv, "l3miss")) {
+ check_duparg(&attrs, IFLA_VXLAN_L3MISS, *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_L3MISS, 1);
+ } else if (!matches(*argv, "udpcsum")) {
+ check_duparg(&attrs, IFLA_VXLAN_UDP_CSUM, *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_UDP_CSUM, 1);
+ } else if (!matches(*argv, "noudpcsum")) {
+ check_duparg(&attrs, IFLA_VXLAN_UDP_CSUM, *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_UDP_CSUM, 0);
+ } else if (!matches(*argv, "udp6zerocsumtx")) {
+ check_duparg(&attrs, IFLA_VXLAN_UDP_ZERO_CSUM6_TX,
+ *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_UDP_ZERO_CSUM6_TX, 1);
+ } else if (!matches(*argv, "noudp6zerocsumtx")) {
+ check_duparg(&attrs, IFLA_VXLAN_UDP_ZERO_CSUM6_TX,
+ *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_UDP_ZERO_CSUM6_TX, 0);
+ } else if (!matches(*argv, "udp6zerocsumrx")) {
+ check_duparg(&attrs, IFLA_VXLAN_UDP_ZERO_CSUM6_RX,
+ *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_UDP_ZERO_CSUM6_RX, 1);
+ } else if (!matches(*argv, "noudp6zerocsumrx")) {
+ check_duparg(&attrs, IFLA_VXLAN_UDP_ZERO_CSUM6_RX,
+ *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_UDP_ZERO_CSUM6_RX, 0);
+ } else if (!matches(*argv, "remcsumtx")) {
+ check_duparg(&attrs, IFLA_VXLAN_REMCSUM_TX,
+ *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_REMCSUM_TX, 1);
+ } else if (!matches(*argv, "noremcsumtx")) {
+ check_duparg(&attrs, IFLA_VXLAN_REMCSUM_TX,
+ *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_REMCSUM_TX, 0);
+ } else if (!matches(*argv, "remcsumrx")) {
+ check_duparg(&attrs, IFLA_VXLAN_REMCSUM_RX,
+ *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_REMCSUM_RX, 1);
+ } else if (!matches(*argv, "noremcsumrx")) {
+ check_duparg(&attrs, IFLA_VXLAN_REMCSUM_RX,
+ *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_REMCSUM_RX, 0);
+ } else if (strcmp(*argv, "localbypass") == 0) {
+ check_duparg(&attrs, IFLA_VXLAN_LOCALBYPASS,
+ *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_LOCALBYPASS, 1);
+ } else if (strcmp(*argv, "nolocalbypass") == 0) {
+ check_duparg(&attrs, IFLA_VXLAN_LOCALBYPASS,
+ *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_LOCALBYPASS, 0);
+ } else if (!matches(*argv, "external")) {
+ check_duparg(&attrs, IFLA_VXLAN_COLLECT_METADATA,
+ *argv, *argv);
+ metadata = 1;
+ learning = 0;
+ /* we will add LEARNING attribute outside of the loop */
+ addattr8(n, 1024, IFLA_VXLAN_COLLECT_METADATA,
+ metadata);
+ } else if (!matches(*argv, "noexternal")) {
+ check_duparg(&attrs, IFLA_VXLAN_COLLECT_METADATA,
+ *argv, *argv);
+ metadata = 0;
+ addattr8(n, 1024, IFLA_VXLAN_COLLECT_METADATA,
+ metadata);
+ } else if (!matches(*argv, "gbp")) {
+ check_duparg(&attrs, IFLA_VXLAN_GBP, *argv, *argv);
+ addattr_l(n, 1024, IFLA_VXLAN_GBP, NULL, 0);
+ } else if (!matches(*argv, "gpe")) {
+ check_duparg(&attrs, IFLA_VXLAN_GPE, *argv, *argv);
+ addattr_l(n, 1024, IFLA_VXLAN_GPE, NULL, 0);
+ } else if (!strcmp(*argv, "vnifilter")) {
+ check_duparg(&attrs, IFLA_VXLAN_VNIFILTER,
+ *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_VNIFILTER, 1);
+ vnifilter = 1;
+ } else if (!strcmp(*argv, "novnifilter")) {
+ check_duparg(&attrs, IFLA_VXLAN_VNIFILTER,
+ *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_VNIFILTER, 0);
+ } else if (matches(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "vxlan: unknown command \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ if (!metadata && vnifilter) {
+ fprintf(stderr, "vxlan: vnifilter is valid only when 'external' is set\n");
+ return -1;
+ }
+
+ if (metadata && VXLAN_ATTRSET(attrs, IFLA_VXLAN_ID)) {
+ fprintf(stderr, "vxlan: both 'external' and vni cannot be specified\n");
+ return -1;
+ }
+
+ if (!metadata && !vnifilter && !VXLAN_ATTRSET(attrs, IFLA_VXLAN_ID) && !set_op) {
+ fprintf(stderr, "vxlan: missing virtual network identifier\n");
+ return -1;
+ }
+
+ if (is_addrtype_inet_multi(&daddr) &&
+ !VXLAN_ATTRSET(attrs, IFLA_VXLAN_LINK)) {
+ fprintf(stderr, "vxlan: 'group' requires 'dev' to be specified\n");
+ return -1;
+ }
+
+ if (!VXLAN_ATTRSET(attrs, IFLA_VXLAN_PORT) &&
+ VXLAN_ATTRSET(attrs, IFLA_VXLAN_GPE)) {
+ dstport = 4790;
+ } else if (!VXLAN_ATTRSET(attrs, IFLA_VXLAN_PORT) && !set_op) {
+ fprintf(stderr, "vxlan: destination port not specified\n"
+ "Will use Linux kernel default (non-standard value)\n");
+ fprintf(stderr,
+ "Use 'dstport 4789' to get the IANA assigned value\n"
+ "Use 'dstport 0' to get default and quiet this message\n");
+ }
+
+ if (VXLAN_ATTRSET(attrs, IFLA_VXLAN_ID))
+ addattr32(n, 1024, IFLA_VXLAN_ID, vni);
+
+ if (is_addrtype_inet(&saddr)) {
+ int type = (saddr.family == AF_INET) ? IFLA_VXLAN_LOCAL
+ : IFLA_VXLAN_LOCAL6;
+ addattr_l(n, 1024, type, saddr.data, saddr.bytelen);
+ selected_family = true;
+ }
+
+ if (is_addrtype_inet(&daddr)) {
+ int type = (daddr.family == AF_INET) ? IFLA_VXLAN_GROUP
+ : IFLA_VXLAN_GROUP6;
+ addattr_l(n, 1024, type, daddr.data, daddr.bytelen);
+ selected_family = true;
+ }
+
+ if (!selected_family) {
+ if (preferred_family == AF_INET) {
+ get_addr(&daddr, "default", AF_INET);
+ addattr_l(n, 1024, IFLA_VXLAN_GROUP,
+ daddr.data, daddr.bytelen);
+ } else if (preferred_family == AF_INET6) {
+ get_addr(&daddr, "default", AF_INET6);
+ addattr_l(n, 1024, IFLA_VXLAN_GROUP6,
+ daddr.data, daddr.bytelen);
+ }
+ }
+
+ if (!set_op || VXLAN_ATTRSET(attrs, IFLA_VXLAN_LEARNING))
+ addattr8(n, 1024, IFLA_VXLAN_LEARNING, learning);
+
+ if (dstport)
+ addattr16(n, 1024, IFLA_VXLAN_PORT, htons(dstport));
+
+ return 0;
+}
+
+static void vxlan_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ unsigned int i;
+ __u8 ttl = 0;
+ __u8 tos = 0;
+ __u32 maxaddr;
+
+ if (!tb)
+ return;
+
+ if (tb[IFLA_VXLAN_ID] &&
+ RTA_PAYLOAD(tb[IFLA_VXLAN_ID]) >= sizeof(__u32)) {
+ print_uint(PRINT_ANY, "id", "id %u ", rta_getattr_u32(tb[IFLA_VXLAN_ID]));
+ }
+
+ if (tb[IFLA_VXLAN_GROUP]) {
+ __be32 addr = rta_getattr_u32(tb[IFLA_VXLAN_GROUP]);
+
+ if (addr) {
+ if (IN_MULTICAST(ntohl(addr)))
+ print_string(PRINT_ANY,
+ "group",
+ "group %s ",
+ format_host(AF_INET, 4, &addr));
+ else
+ print_string(PRINT_ANY,
+ "remote",
+ "remote %s ",
+ format_host(AF_INET, 4, &addr));
+ }
+ } else if (tb[IFLA_VXLAN_GROUP6]) {
+ struct in6_addr addr;
+
+ memcpy(&addr, RTA_DATA(tb[IFLA_VXLAN_GROUP6]), sizeof(struct in6_addr));
+ if (!IN6_IS_ADDR_UNSPECIFIED(&addr)) {
+ if (IN6_IS_ADDR_MULTICAST(&addr))
+ print_string(PRINT_ANY,
+ "group6",
+ "group %s ",
+ format_host(AF_INET6,
+ sizeof(struct in6_addr),
+ &addr));
+ else
+ print_string(PRINT_ANY,
+ "remote6",
+ "remote %s ",
+ format_host(AF_INET6,
+ sizeof(struct in6_addr),
+ &addr));
+ }
+ }
+
+ if (tb[IFLA_VXLAN_LOCAL]) {
+ __be32 addr = rta_getattr_u32(tb[IFLA_VXLAN_LOCAL]);
+
+ if (addr)
+ print_string(PRINT_ANY,
+ "local",
+ "local %s ",
+ format_host(AF_INET, 4, &addr));
+ } else if (tb[IFLA_VXLAN_LOCAL6]) {
+ struct in6_addr addr;
+
+ memcpy(&addr, RTA_DATA(tb[IFLA_VXLAN_LOCAL6]), sizeof(struct in6_addr));
+ if (!IN6_IS_ADDR_UNSPECIFIED(&addr))
+ print_string(PRINT_ANY,
+ "local6",
+ "local %s ",
+ format_host(AF_INET6,
+ sizeof(struct in6_addr),
+ &addr));
+ }
+
+ if (tb[IFLA_VXLAN_LINK]) {
+ unsigned int link = rta_getattr_u32(tb[IFLA_VXLAN_LINK]);
+
+ if (link) {
+ print_string(PRINT_ANY, "link", "dev %s ",
+ ll_index_to_name(link));
+ }
+ }
+
+ if (tb[IFLA_VXLAN_PORT_RANGE]) {
+ const struct ifla_vxlan_port_range *r
+ = RTA_DATA(tb[IFLA_VXLAN_PORT_RANGE]);
+ if (is_json_context()) {
+ open_json_object("port_range");
+ print_uint(PRINT_JSON, "low", NULL, ntohs(r->low));
+ print_uint(PRINT_JSON, "high", NULL, ntohs(r->high));
+ close_json_object();
+ } else {
+ fprintf(f, "srcport %u %u ",
+ ntohs(r->low), ntohs(r->high));
+ }
+ }
+
+ if (tb[IFLA_VXLAN_PORT])
+ print_uint(PRINT_ANY,
+ "port",
+ "dstport %u ",
+ rta_getattr_be16(tb[IFLA_VXLAN_PORT]));
+
+ if (tb[IFLA_VXLAN_TOS])
+ tos = rta_getattr_u8(tb[IFLA_VXLAN_TOS]);
+ if (tos) {
+ if (is_json_context() || tos != 1)
+ print_0xhex(PRINT_ANY, "tos", "tos %#llx ", tos);
+ else
+ print_string(PRINT_FP, NULL, "tos %s ", "inherit");
+ }
+
+ if (tb[IFLA_VXLAN_TTL_INHERIT] &&
+ rta_getattr_u8(tb[IFLA_VXLAN_TTL_INHERIT])) {
+ print_string(PRINT_FP, NULL, "ttl %s ", "inherit");
+ } else if (tb[IFLA_VXLAN_TTL]) {
+ ttl = rta_getattr_u8(tb[IFLA_VXLAN_TTL]);
+ if (is_json_context() || ttl)
+ print_uint(PRINT_ANY, "ttl", "ttl %u ", ttl);
+ else
+ print_string(PRINT_FP, NULL, "ttl %s ", "auto");
+ }
+
+ if (tb[IFLA_VXLAN_DF]) {
+ enum ifla_vxlan_df df = rta_getattr_u8(tb[IFLA_VXLAN_DF]);
+
+ if (df == VXLAN_DF_UNSET)
+ print_string(PRINT_JSON, "df", "df %s ", "unset");
+ else if (df == VXLAN_DF_SET)
+ print_string(PRINT_ANY, "df", "df %s ", "set");
+ else if (df == VXLAN_DF_INHERIT)
+ print_string(PRINT_ANY, "df", "df %s ", "inherit");
+ }
+
+ if (tb[IFLA_VXLAN_LABEL]) {
+ __u32 label = rta_getattr_u32(tb[IFLA_VXLAN_LABEL]);
+
+ if (label)
+ print_0xhex(PRINT_ANY, "label",
+ "flowlabel %#llx ", ntohl(label));
+ }
+
+ if (tb[IFLA_VXLAN_AGEING]) {
+ __u32 age = rta_getattr_u32(tb[IFLA_VXLAN_AGEING]);
+
+ if (age == 0)
+ print_uint(PRINT_ANY, "ageing", "ageing none ", 0);
+ else
+ print_uint(PRINT_ANY, "ageing", "ageing %u ", age);
+ }
+
+ if (tb[IFLA_VXLAN_LIMIT] &&
+ ((maxaddr = rta_getattr_u32(tb[IFLA_VXLAN_LIMIT])) != 0))
+ print_uint(PRINT_ANY, "limit", "maxaddr %u ", maxaddr);
+
+ if (tb[IFLA_VXLAN_GBP])
+ print_null(PRINT_ANY, "gbp", "gbp ", NULL);
+ if (tb[IFLA_VXLAN_GPE])
+ print_null(PRINT_ANY, "gpe", "gpe ", NULL);
+
+ for (i = 0; i < ARRAY_SIZE(vxlan_opts); i++) {
+ const struct vxlan_bool_opt *opt = &vxlan_opts[i];
+ __u8 val;
+
+ if (!tb[opt->type])
+ continue;
+ val = rta_getattr_u8(tb[opt->type]);
+
+ print_bool_opt(PRINT_ANY, opt->key, val,
+ val != opt->default_value || show_details > 1);
+ }
+}
+
+static void vxlan_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ print_explain(f);
+}
+
+struct link_util vxlan_link_util = {
+ .id = "vxlan",
+ .maxattr = IFLA_VXLAN_MAX,
+ .parse_opt = vxlan_parse_opt,
+ .print_opt = vxlan_print_opt,
+ .print_help = vxlan_print_help,
+};
diff --git a/ip/iplink_wwan.c b/ip/iplink_wwan.c
new file mode 100644
index 0000000..3510477
--- /dev/null
+++ b/ip/iplink_wwan.c
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <stdio.h>
+#include <linux/netlink.h>
+#include <linux/wwan.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_explain(FILE *f)
+{
+ fprintf(f,
+ "Usage: ... wwan linkid LINKID\n"
+ "\n"
+ "Where: LINKID := 0-4294967295\n"
+ );
+}
+
+static void explain(void)
+{
+ print_explain(stderr);
+}
+
+static int wwan_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ while (argc > 0) {
+ if (matches(*argv, "linkid") == 0) {
+ __u32 linkid;
+
+ NEXT_ARG();
+ if (get_u32(&linkid, *argv, 0))
+ invarg("linkid", *argv);
+ addattr32(n, 1024, IFLA_WWAN_LINK_ID, linkid);
+ } else if (matches(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "wwan: unknown command \"%s\"?\n",
+ *argv);
+ explain();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ return 0;
+}
+
+static void wwan_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ if (!tb)
+ return;
+
+ if (tb[IFLA_WWAN_LINK_ID])
+ print_uint(PRINT_ANY, "linkid", "linkid %u ",
+ rta_getattr_u32(tb[IFLA_WWAN_LINK_ID]));
+}
+
+static void wwan_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ print_explain(f);
+}
+
+struct link_util wwan_link_util = {
+ .id = "wwan",
+ .maxattr = IFLA_WWAN_MAX,
+ .parse_opt = wwan_parse_opt,
+ .print_opt = wwan_print_opt,
+ .print_help = wwan_print_help,
+};
diff --git a/ip/iplink_xdp.c b/ip/iplink_xdp.c
new file mode 100644
index 0000000..5928dff
--- /dev/null
+++ b/ip/iplink_xdp.c
@@ -0,0 +1,195 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iplink_xdp.c XDP program loader
+ *
+ * Authors: Daniel Borkmann <daniel@iogearbox.net>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <linux/bpf.h>
+
+#include "bpf_util.h"
+#include "utils.h"
+#include "ip_common.h"
+
+extern int force;
+
+struct xdp_req {
+ struct iplink_req *req;
+ __u32 flags;
+};
+
+static void xdp_ebpf_cb(void *raw, int fd, const char *annotation)
+{
+ struct xdp_req *xdp = raw;
+ struct iplink_req *req = xdp->req;
+ struct rtattr *xdp_attr;
+
+ xdp_attr = addattr_nest(&req->n, sizeof(*req), IFLA_XDP);
+ addattr32(&req->n, sizeof(*req), IFLA_XDP_FD, fd);
+ if (xdp->flags)
+ addattr32(&req->n, sizeof(*req), IFLA_XDP_FLAGS, xdp->flags);
+ addattr_nest_end(&req->n, xdp_attr);
+}
+
+static const struct bpf_cfg_ops bpf_cb_ops = {
+ .ebpf_cb = xdp_ebpf_cb,
+};
+
+static int xdp_delete(struct xdp_req *xdp)
+{
+ xdp_ebpf_cb(xdp, -1, NULL);
+ return 0;
+}
+
+int xdp_parse(int *argc, char ***argv, struct iplink_req *req,
+ const char *ifname, bool generic, bool drv, bool offload)
+{
+ struct bpf_cfg_in cfg = {
+ .type = BPF_PROG_TYPE_XDP,
+ .argc = *argc,
+ .argv = *argv,
+ };
+ struct xdp_req xdp = {
+ .req = req,
+ };
+
+ if (offload) {
+ int ifindex = ll_name_to_index(ifname);
+
+ if (!ifindex)
+ incomplete_command();
+ cfg.ifindex = ifindex;
+ }
+
+ if (!force)
+ xdp.flags |= XDP_FLAGS_UPDATE_IF_NOEXIST;
+ if (generic)
+ xdp.flags |= XDP_FLAGS_SKB_MODE;
+ if (drv)
+ xdp.flags |= XDP_FLAGS_DRV_MODE;
+ if (offload)
+ xdp.flags |= XDP_FLAGS_HW_MODE;
+
+ if (*argc == 1) {
+ if (strcmp(**argv, "none") == 0 ||
+ strcmp(**argv, "off") == 0)
+ return xdp_delete(&xdp);
+ }
+
+ if (bpf_parse_and_load_common(&cfg, &bpf_cb_ops, &xdp))
+ return -1;
+
+ *argc = cfg.argc;
+ *argv = cfg.argv;
+ return 0;
+}
+
+static void xdp_dump_json_one(struct rtattr *tb[IFLA_XDP_MAX + 1], __u32 attr,
+ __u8 mode)
+{
+ if (!tb[attr])
+ return;
+
+ open_json_object(NULL);
+ print_uint(PRINT_JSON, "mode", NULL, mode);
+ bpf_dump_prog_info(NULL, rta_getattr_u32(tb[attr]));
+ close_json_object();
+}
+
+static void xdp_dump_json(struct rtattr *tb[IFLA_XDP_MAX + 1])
+{
+ __u32 prog_id = 0;
+ __u8 mode;
+
+ mode = rta_getattr_u8(tb[IFLA_XDP_ATTACHED]);
+ if (tb[IFLA_XDP_PROG_ID])
+ prog_id = rta_getattr_u32(tb[IFLA_XDP_PROG_ID]);
+
+ open_json_object("xdp");
+ print_uint(PRINT_JSON, "mode", NULL, mode);
+ if (prog_id)
+ bpf_dump_prog_info(NULL, prog_id);
+
+ open_json_array(PRINT_JSON, "attached");
+ if (tb[IFLA_XDP_SKB_PROG_ID] ||
+ tb[IFLA_XDP_DRV_PROG_ID] ||
+ tb[IFLA_XDP_HW_PROG_ID]) {
+ xdp_dump_json_one(tb, IFLA_XDP_SKB_PROG_ID, XDP_ATTACHED_SKB);
+ xdp_dump_json_one(tb, IFLA_XDP_DRV_PROG_ID, XDP_ATTACHED_DRV);
+ xdp_dump_json_one(tb, IFLA_XDP_HW_PROG_ID, XDP_ATTACHED_HW);
+ } else if (tb[IFLA_XDP_PROG_ID]) {
+ /* Older kernel - use IFLA_XDP_PROG_ID */
+ xdp_dump_json_one(tb, IFLA_XDP_PROG_ID, mode);
+ }
+ close_json_array(PRINT_JSON, NULL);
+
+ close_json_object();
+}
+
+static void xdp_dump_prog_one(FILE *fp, struct rtattr *tb[IFLA_XDP_MAX + 1],
+ __u32 attr, bool link, bool details,
+ const char *pfx)
+{
+ __u32 prog_id;
+
+ if (!tb[attr])
+ return;
+
+ prog_id = rta_getattr_u32(tb[attr]);
+ if (!details) {
+ if (prog_id && !link && attr == IFLA_XDP_PROG_ID)
+ fprintf(fp, "/id:%u", prog_id);
+ return;
+ }
+
+ if (prog_id) {
+ fprintf(fp, "%s prog/xdp%s ", _SL_, pfx);
+ bpf_dump_prog_info(fp, prog_id);
+ }
+}
+
+void xdp_dump(FILE *fp, struct rtattr *xdp, bool link, bool details)
+{
+ struct rtattr *tb[IFLA_XDP_MAX + 1];
+ __u8 mode;
+
+ parse_rtattr_nested(tb, IFLA_XDP_MAX, xdp);
+
+ if (!tb[IFLA_XDP_ATTACHED])
+ return;
+
+ mode = rta_getattr_u8(tb[IFLA_XDP_ATTACHED]);
+ if (mode == XDP_ATTACHED_NONE)
+ return;
+ else if (is_json_context())
+ return details ? (void)0 : xdp_dump_json(tb);
+ else if (details && link)
+ /* don't print mode */;
+ else if (mode == XDP_ATTACHED_DRV)
+ fprintf(fp, "xdp");
+ else if (mode == XDP_ATTACHED_SKB)
+ fprintf(fp, "xdpgeneric");
+ else if (mode == XDP_ATTACHED_HW)
+ fprintf(fp, "xdpoffload");
+ else if (mode == XDP_ATTACHED_MULTI)
+ fprintf(fp, "xdpmulti");
+ else
+ fprintf(fp, "xdp[%u]", mode);
+
+ xdp_dump_prog_one(fp, tb, IFLA_XDP_PROG_ID, link, details, "");
+
+ if (mode == XDP_ATTACHED_MULTI) {
+ xdp_dump_prog_one(fp, tb, IFLA_XDP_SKB_PROG_ID, link, details,
+ "generic");
+ xdp_dump_prog_one(fp, tb, IFLA_XDP_DRV_PROG_ID, link, details,
+ "drv");
+ xdp_dump_prog_one(fp, tb, IFLA_XDP_HW_PROG_ID, link, details,
+ "offload");
+ }
+
+ if (!details || !link)
+ fprintf(fp, " ");
+}
diff --git a/ip/iplink_xstats.c b/ip/iplink_xstats.c
new file mode 100644
index 0000000..8d36798
--- /dev/null
+++ b/ip/iplink_xstats.c
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iplink_stats.c Extended statistics commands
+ *
+ * Authors: Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/if_link.h>
+#include <netinet/ether.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_explain(FILE *f)
+{
+ fprintf(f, "Usage: ... xstats type TYPE [ ARGS ]\n");
+}
+
+int iplink_ifla_xstats(int argc, char **argv)
+{
+ struct link_util *lu = NULL;
+ __u32 filt_mask;
+
+ if (!argc) {
+ fprintf(stderr, "xstats: missing argument\n");
+ return -1;
+ }
+
+ if (matches(*argv, "type") == 0) {
+ NEXT_ARG();
+ lu = get_link_kind(*argv);
+ if (!lu)
+ invarg("invalid type", *argv);
+ } else if (matches(*argv, "help") == 0) {
+ print_explain(stdout);
+ return 0;
+ } else {
+ invarg("unknown argument", *argv);
+ }
+
+ if (!lu) {
+ print_explain(stderr);
+ return -1;
+ }
+
+ if (!lu->print_ifla_xstats) {
+ fprintf(stderr, "xstats: link type %s doesn't support xstats\n",
+ lu->id);
+ return -1;
+ }
+
+ if (lu->parse_ifla_xstats &&
+ lu->parse_ifla_xstats(lu, argc-1, argv+1))
+ return -1;
+
+ if (strstr(lu->id, "_slave"))
+ filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS_SLAVE);
+ else
+ filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS);
+
+ if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask,
+ NULL, NULL) < 0) {
+ perror("Cannot send dump request");
+ return -1;
+ }
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&rth, lu->print_ifla_xstats, stdout) < 0) {
+ delete_json_obj();
+ fprintf(stderr, "Dump terminated\n");
+ return -1;
+ }
+ delete_json_obj();
+
+ return 0;
+}
diff --git a/ip/ipmacsec.c b/ip/ipmacsec.c
new file mode 100644
index 0000000..fc4c863
--- /dev/null
+++ b/ip/ipmacsec.c
@@ -0,0 +1,1544 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * ipmacsec.c "ip macsec".
+ *
+ * Authors: Sabrina Dubroca <sd@queasysnail.net>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <linux/genetlink.h>
+#include <linux/if_ether.h>
+#include <linux/if_macsec.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "ll_map.h"
+#include "libgenl.h"
+
+static const char * const validate_str[] = {
+ [MACSEC_VALIDATE_DISABLED] = "disabled",
+ [MACSEC_VALIDATE_CHECK] = "check",
+ [MACSEC_VALIDATE_STRICT] = "strict",
+};
+
+static const char * const offload_str[] = {
+ [MACSEC_OFFLOAD_OFF] = "off",
+ [MACSEC_OFFLOAD_PHY] = "phy",
+ [MACSEC_OFFLOAD_MAC] = "mac",
+};
+
+struct sci {
+ __u64 sci;
+ __u16 port;
+ char abuf[6];
+};
+
+struct sa_desc {
+ __u8 an;
+ union {
+ __u32 pn32;
+ __u64 pn64;
+ } pn;
+ __u8 key_id[MACSEC_KEYID_LEN];
+ __u32 key_len;
+ __u8 key[MACSEC_MAX_KEY_LEN];
+ __u8 active;
+ __u8 salt[MACSEC_SALT_LEN];
+ __u32 ssci;
+ bool xpn;
+ bool salt_set;
+ bool ssci_set;
+};
+
+struct cipher_args {
+ __u64 id;
+ __u8 icv_len;
+};
+
+struct txsc_desc {
+ int ifindex;
+ __u64 sci;
+ __be16 port;
+ struct cipher_args cipher;
+ __u32 window;
+ enum macsec_validation_type validate;
+ __u8 encoding_sa;
+};
+
+struct rxsc_desc {
+ int ifindex;
+ __u64 sci;
+ __u8 active;
+};
+
+#define MACSEC_BUFLEN 1024
+
+
+/* netlink socket */
+static struct rtnl_handle genl_rth;
+static int genl_family = -1;
+
+#define MACSEC_GENL_REQ(_req, _bufsiz, _cmd, _flags) \
+ GENL_REQUEST(_req, _bufsiz, genl_family, 0, MACSEC_GENL_VERSION, \
+ _cmd, _flags)
+
+
+static void ipmacsec_usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip macsec add DEV tx sa { 0..3 } [ OPTS ] key ID KEY\n"
+ " ip macsec set DEV tx sa { 0..3 } [ OPTS ]\n"
+ " ip macsec del DEV tx sa { 0..3 }\n"
+ " ip macsec add DEV rx SCI [ on | off ]\n"
+ " ip macsec set DEV rx SCI [ on | off ]\n"
+ " ip macsec del DEV rx SCI\n"
+ " ip macsec add DEV rx SCI sa { 0..3 } [ OPTS ] key ID KEY\n"
+ " ip macsec set DEV rx SCI sa { 0..3 } [ OPTS ]\n"
+ " ip macsec del DEV rx SCI sa { 0..3 }\n"
+ " ip macsec show\n"
+ " ip macsec show DEV\n"
+ " ip macsec offload DEV [ off | phy | mac ]\n"
+ "where OPTS := [ pn <u32> | xpn <u64> ] [ salt SALT ] [ ssci <u32> ] [ on | off ]\n"
+ " ID := 128-bit hex string\n"
+ " KEY := 128-bit or 256-bit hex string\n"
+ " SCI := { sci <u64> | port { 1..2^16-1 } address <lladdr> }\n"
+ " SALT := 96-bit hex string\n");
+
+ exit(-1);
+}
+
+static bool ciphersuite_is_xpn(__u64 cid)
+{
+ return (cid == MACSEC_CIPHER_ID_GCM_AES_XPN_128 || cid == MACSEC_CIPHER_ID_GCM_AES_XPN_256);
+}
+
+static int get_an(__u8 *val, const char *arg)
+{
+ int ret = get_u8(val, arg, 0);
+
+ if (ret)
+ return ret;
+
+ if (*val > 3)
+ return -1;
+
+ return 0;
+}
+
+static int get_sci(__u64 *sci, const char *arg)
+{
+ return get_be64(sci, arg, 16);
+}
+
+static int get_ssci(__u32 *ssci, const char *arg)
+{
+ return get_be32(ssci, arg, 16);
+}
+
+static int get_port(__be16 *port, const char *arg)
+{
+ return get_be16(port, arg, 0);
+}
+
+#define _STR(a) #a
+#define STR(a) _STR(a)
+
+static void get_icvlen(__u8 *icvlen, char *arg)
+{
+ int ret = get_u8(icvlen, arg, 10);
+
+ if (ret)
+ invarg("expected ICV length", arg);
+
+ if (*icvlen < MACSEC_MIN_ICV_LEN || *icvlen > MACSEC_STD_ICV_LEN)
+ invarg("ICV length must be in the range {"
+ STR(MACSEC_MIN_ICV_LEN) ".." STR(MACSEC_STD_ICV_LEN)
+ "}", arg);
+}
+
+static bool get_sa(int *argcp, char ***argvp, __u8 *an)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+ int ret;
+
+ if (argc <= 0 || strcmp(*argv, "sa") != 0)
+ return false;
+
+ NEXT_ARG();
+ ret = get_an(an, *argv);
+ if (ret)
+ invarg("expected an { 0..3 }", *argv);
+ argc--; argv++;
+
+ *argvp = argv;
+ *argcp = argc;
+ return true;
+}
+
+static int parse_sa_args(int *argcp, char ***argvp, struct sa_desc *sa)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+ int ret;
+ bool active_set = false;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "pn") == 0) {
+ if (sa->pn.pn64 != 0)
+ duparg2("pn", "pn");
+ NEXT_ARG();
+ ret = get_u32(&sa->pn.pn32, *argv, 0);
+ if (ret)
+ invarg("expected pn", *argv);
+ if (sa->pn.pn32 == 0)
+ invarg("expected pn != 0", *argv);
+ } else if (strcmp(*argv, "xpn") == 0) {
+ if (sa->pn.pn64 != 0)
+ duparg2("xpn", "xpn");
+ NEXT_ARG();
+ ret = get_u64(&sa->pn.pn64, *argv, 0);
+ if (ret)
+ invarg("expected pn", *argv);
+ if (sa->pn.pn64 == 0)
+ invarg("expected pn != 0", *argv);
+ sa->xpn = true;
+ } else if (strcmp(*argv, "salt") == 0) {
+ unsigned int len;
+
+ if (sa->salt_set)
+ duparg2("salt", "salt");
+ NEXT_ARG();
+ if (!hexstring_a2n(*argv, sa->salt, MACSEC_SALT_LEN,
+ &len))
+ invarg("expected salt", *argv);
+ sa->salt_set = true;
+ } else if (strcmp(*argv, "ssci") == 0) {
+ if (sa->ssci_set)
+ duparg2("ssci", "ssci");
+ NEXT_ARG();
+ ret = get_ssci(&sa->ssci, *argv);
+ if (ret)
+ invarg("expected ssci", *argv);
+ sa->ssci_set = true;
+ } else if (strcmp(*argv, "key") == 0) {
+ unsigned int len;
+
+ NEXT_ARG();
+ if (!hexstring_a2n(*argv, sa->key_id, MACSEC_KEYID_LEN,
+ &len))
+ invarg("expected key id", *argv);
+ NEXT_ARG();
+ if (!hexstring_a2n(*argv, sa->key, MACSEC_MAX_KEY_LEN,
+ &sa->key_len))
+ invarg("expected key", *argv);
+ } else if (strcmp(*argv, "on") == 0) {
+ if (active_set)
+ duparg2("on/off", "on");
+ sa->active = true;
+ active_set = true;
+ } else if (strcmp(*argv, "off") == 0) {
+ if (active_set)
+ duparg2("on/off", "off");
+ sa->active = false;
+ active_set = true;
+ } else {
+ fprintf(stderr, "macsec: unknown command \"%s\"?\n",
+ *argv);
+ ipmacsec_usage();
+ }
+
+ argv++; argc--;
+ }
+
+ *argvp = argv;
+ *argcp = argc;
+ return 0;
+}
+
+static __u64 make_sci(char *addr, __be16 port)
+{
+ __u64 sci;
+
+ memcpy(&sci, addr, ETH_ALEN);
+ memcpy(((char *)&sci) + ETH_ALEN, &port, sizeof(port));
+
+ return sci;
+}
+
+static bool sci_complete(bool sci, bool port, bool addr, bool port_only)
+{
+ return sci || (port && (addr || port_only));
+}
+
+static int get_sci_portaddr(struct sci *sci, int *argcp, char ***argvp,
+ bool port_only, bool optional)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+ int ret;
+ bool p = false, a = false, s = false;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "sci") == 0) {
+ if (p)
+ invarg("expected address", *argv);
+ if (a)
+ invarg("expected port", *argv);
+ NEXT_ARG();
+ ret = get_sci(&sci->sci, *argv);
+ if (ret)
+ invarg("expected sci", *argv);
+ s = true;
+ } else if (strcmp(*argv, "port") == 0) {
+ NEXT_ARG();
+ ret = get_port(&sci->port, *argv);
+ if (ret)
+ invarg("expected port", *argv);
+ if (sci->port == 0)
+ invarg("expected port != 0", *argv);
+ p = true;
+ } else if (strcmp(*argv, "address") == 0) {
+ NEXT_ARG();
+ ret = ll_addr_a2n(sci->abuf, sizeof(sci->abuf), *argv);
+ if (ret < 0)
+ invarg("expected lladdr", *argv);
+ a = true;
+ } else if (optional) {
+ break;
+ } else {
+ invarg("expected sci, port, or address", *argv);
+ }
+
+ argv++; argc--;
+
+ if (sci_complete(s, p, a, port_only))
+ break;
+ }
+
+ if (!optional && !sci_complete(s, p, a, port_only))
+ return -1;
+
+ if (p && a)
+ sci->sci = make_sci(sci->abuf, sci->port);
+
+ *argvp = argv;
+ *argcp = argc;
+
+ return p || a || s;
+}
+
+static bool parse_rxsci(int *argcp, char ***argvp, struct rxsc_desc *rxsc,
+ struct sa_desc *rxsa)
+{
+ struct sci sci = { 0 };
+
+ if (*argcp == 0 ||
+ get_sci_portaddr(&sci, argcp, argvp, false, false) < 0) {
+ fprintf(stderr, "expected sci\n");
+ ipmacsec_usage();
+ }
+
+ rxsc->sci = sci.sci;
+
+ return get_sa(argcp, argvp, &rxsa->an);
+}
+
+static int parse_rxsci_args(int *argcp, char ***argvp, struct rxsc_desc *rxsc)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+ bool active_set = false;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "on") == 0) {
+ if (active_set)
+ duparg2("on/off", "on");
+ rxsc->active = true;
+ active_set = true;
+ } else if (strcmp(*argv, "off") == 0) {
+ if (active_set)
+ duparg2("on/off", "off");
+ rxsc->active = false;
+ active_set = true;
+ } else {
+ fprintf(stderr, "macsec: unknown command \"%s\"?\n",
+ *argv);
+ ipmacsec_usage();
+ }
+
+ argv++; argc--;
+ }
+
+ *argvp = argv;
+ *argcp = argc;
+ return 0;
+}
+
+enum cmd {
+ CMD_ADD,
+ CMD_DEL,
+ CMD_UPD,
+ CMD_OFFLOAD,
+ __CMD_MAX
+};
+
+static const enum macsec_nl_commands macsec_commands[__CMD_MAX][2][2] = {
+ [CMD_ADD] = {
+ [0] = {-1, MACSEC_CMD_ADD_RXSC},
+ [1] = {MACSEC_CMD_ADD_TXSA, MACSEC_CMD_ADD_RXSA},
+ },
+ [CMD_UPD] = {
+ [0] = {-1, MACSEC_CMD_UPD_RXSC},
+ [1] = {MACSEC_CMD_UPD_TXSA, MACSEC_CMD_UPD_RXSA},
+ },
+ [CMD_DEL] = {
+ [0] = {-1, MACSEC_CMD_DEL_RXSC},
+ [1] = {MACSEC_CMD_DEL_TXSA, MACSEC_CMD_DEL_RXSA},
+ },
+ [CMD_OFFLOAD] = {
+ [0] = {-1, MACSEC_CMD_UPD_OFFLOAD },
+ },
+};
+
+static int do_modify_nl(enum cmd c, enum macsec_nl_commands cmd, int ifindex,
+ struct rxsc_desc *rxsc, struct sa_desc *sa)
+{
+ struct rtattr *attr_sa;
+
+ MACSEC_GENL_REQ(req, MACSEC_BUFLEN, cmd, NLM_F_REQUEST);
+
+ addattr32(&req.n, MACSEC_BUFLEN, MACSEC_ATTR_IFINDEX, ifindex);
+ if (rxsc) {
+ struct rtattr *attr_rxsc;
+
+ attr_rxsc = addattr_nest(&req.n, MACSEC_BUFLEN,
+ MACSEC_ATTR_RXSC_CONFIG);
+ addattr64(&req.n, MACSEC_BUFLEN,
+ MACSEC_RXSC_ATTR_SCI, rxsc->sci);
+ if (c != CMD_DEL && rxsc->active != 0xff)
+ addattr8(&req.n, MACSEC_BUFLEN,
+ MACSEC_RXSC_ATTR_ACTIVE, rxsc->active);
+
+ addattr_nest_end(&req.n, attr_rxsc);
+ }
+
+ if (sa->an == 0xff)
+ goto talk;
+
+ attr_sa = addattr_nest(&req.n, MACSEC_BUFLEN, MACSEC_ATTR_SA_CONFIG);
+
+ addattr8(&req.n, MACSEC_BUFLEN, MACSEC_SA_ATTR_AN, sa->an);
+
+ if (c != CMD_DEL) {
+ if (sa->xpn) {
+ if (sa->pn.pn64)
+ addattr64(&req.n, MACSEC_BUFLEN, MACSEC_SA_ATTR_PN,
+ sa->pn.pn64);
+ if (sa->salt_set)
+ addattr_l(&req.n, MACSEC_BUFLEN, MACSEC_SA_ATTR_SALT,
+ sa->salt, MACSEC_SALT_LEN);
+ if (sa->ssci_set)
+ addattr32(&req.n, MACSEC_BUFLEN, MACSEC_SA_ATTR_SSCI,
+ sa->ssci);
+ } else {
+ if (sa->pn.pn32)
+ addattr32(&req.n, MACSEC_BUFLEN, MACSEC_SA_ATTR_PN,
+ sa->pn.pn32);
+ }
+
+ if (sa->key_len) {
+ addattr_l(&req.n, MACSEC_BUFLEN, MACSEC_SA_ATTR_KEYID,
+ sa->key_id, MACSEC_KEYID_LEN);
+ addattr_l(&req.n, MACSEC_BUFLEN, MACSEC_SA_ATTR_KEY,
+ sa->key, sa->key_len);
+ }
+
+ if (sa->active != 0xff) {
+ addattr8(&req.n, MACSEC_BUFLEN,
+ MACSEC_SA_ATTR_ACTIVE, sa->active);
+ }
+ }
+
+ addattr_nest_end(&req.n, attr_sa);
+
+talk:
+ if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
+ return -2;
+
+ return 0;
+}
+
+static bool check_sa_args(enum cmd c, struct sa_desc *sa)
+{
+ if (c == CMD_ADD) {
+ if (!sa->key_len) {
+ fprintf(stderr, "cannot create SA without key\n");
+ return -1;
+ }
+
+ if (sa->pn.pn64 == 0) {
+ fprintf(stderr, "must specify a packet number != 0\n");
+ return -1;
+ }
+ } else if (c == CMD_UPD) {
+ if (sa->key_len) {
+ fprintf(stderr, "cannot change key on SA\n");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int do_modify_txsa(enum cmd c, int argc, char **argv, int ifindex)
+{
+ struct sa_desc txsa = {0};
+ enum macsec_nl_commands cmd;
+
+ txsa.an = 0xff;
+ txsa.active = 0xff;
+
+ if (argc == 0 || !get_sa(&argc, &argv, &txsa.an))
+ ipmacsec_usage();
+
+ if (c == CMD_DEL)
+ goto modify;
+
+ if (parse_sa_args(&argc, &argv, &txsa))
+ return -1;
+
+ if (check_sa_args(c, &txsa))
+ return -1;
+
+modify:
+ cmd = macsec_commands[c][1][0];
+ return do_modify_nl(c, cmd, ifindex, NULL, &txsa);
+}
+
+static int do_modify_rxsci(enum cmd c, int argc, char **argv, int ifindex)
+{
+ struct rxsc_desc rxsc = {0};
+ struct sa_desc rxsa = {0};
+ bool sa_set;
+ enum macsec_nl_commands cmd;
+
+ rxsc.ifindex = ifindex;
+ rxsc.active = 0xff;
+ rxsa.an = 0xff;
+ rxsa.active = 0xff;
+
+ sa_set = parse_rxsci(&argc, &argv, &rxsc, &rxsa);
+
+ if (c == CMD_DEL)
+ goto modify;
+
+ if (sa_set && (parse_sa_args(&argc, &argv, &rxsa) ||
+ check_sa_args(c, &rxsa)))
+ return -1;
+ if (!sa_set && parse_rxsci_args(&argc, &argv, &rxsc))
+ return -1;
+
+modify:
+ cmd = macsec_commands[c][sa_set][1];
+ return do_modify_nl(c, cmd, rxsc.ifindex, &rxsc, &rxsa);
+}
+
+static int do_modify(enum cmd c, int argc, char **argv)
+{
+ int ifindex;
+
+ if (argc == 0)
+ ipmacsec_usage();
+
+ ifindex = ll_name_to_index(*argv);
+ if (!ifindex) {
+ fprintf(stderr, "Device \"%s\" does not exist.\n", *argv);
+ return -1;
+ }
+ argc--; argv++;
+
+ if (argc == 0)
+ ipmacsec_usage();
+
+ if (strcmp(*argv, "tx") == 0)
+ return do_modify_txsa(c, argc-1, argv+1, ifindex);
+ if (strcmp(*argv, "rx") == 0)
+ return do_modify_rxsci(c, argc-1, argv+1, ifindex);
+
+ ipmacsec_usage();
+ return -1;
+}
+
+static int do_offload(enum cmd c, int argc, char **argv)
+{
+ enum macsec_offload offload;
+ struct rtattr *attr;
+ int ifindex, ret;
+
+ if (argc == 0)
+ ipmacsec_usage();
+
+ ifindex = ll_name_to_index(*argv);
+ if (!ifindex) {
+ fprintf(stderr, "Device \"%s\" does not exist.\n", *argv);
+ return -1;
+ }
+ argc--; argv++;
+
+ if (argc == 0)
+ ipmacsec_usage();
+
+ offload = parse_one_of("offload", *argv, offload_str, ARRAY_SIZE(offload_str), &ret);
+ if (ret)
+ ipmacsec_usage();
+
+ MACSEC_GENL_REQ(req, MACSEC_BUFLEN, macsec_commands[c][0][1], NLM_F_REQUEST);
+
+ addattr32(&req.n, MACSEC_BUFLEN, MACSEC_ATTR_IFINDEX, ifindex);
+
+ attr = addattr_nest(&req.n, MACSEC_BUFLEN, MACSEC_ATTR_OFFLOAD);
+ addattr8(&req.n, MACSEC_BUFLEN, MACSEC_OFFLOAD_ATTR_TYPE, offload);
+ addattr_nest_end(&req.n, attr);
+
+ if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
+ return -2;
+
+ return 0;
+}
+
+/* dump/show */
+static struct {
+ int ifindex;
+ __u64 sci;
+} filter;
+
+static int validate_dump(struct rtattr **attrs)
+{
+ return attrs[MACSEC_ATTR_IFINDEX] && attrs[MACSEC_ATTR_SECY] &&
+ attrs[MACSEC_ATTR_TXSA_LIST] && attrs[MACSEC_ATTR_RXSC_LIST] &&
+ attrs[MACSEC_ATTR_TXSC_STATS] && attrs[MACSEC_ATTR_SECY_STATS];
+
+}
+
+static int validate_secy_dump(struct rtattr **attrs)
+{
+ return attrs[MACSEC_SECY_ATTR_SCI] &&
+ attrs[MACSEC_SECY_ATTR_ENCODING_SA] &&
+ attrs[MACSEC_SECY_ATTR_CIPHER_SUITE] &&
+ attrs[MACSEC_SECY_ATTR_ICV_LEN] &&
+ attrs[MACSEC_SECY_ATTR_PROTECT] &&
+ attrs[MACSEC_SECY_ATTR_REPLAY] &&
+ attrs[MACSEC_SECY_ATTR_OPER] &&
+ attrs[MACSEC_SECY_ATTR_VALIDATE] &&
+ attrs[MACSEC_SECY_ATTR_ENCRYPT] &&
+ attrs[MACSEC_SECY_ATTR_INC_SCI] &&
+ attrs[MACSEC_SECY_ATTR_ES] &&
+ attrs[MACSEC_SECY_ATTR_SCB];
+}
+
+static void print_flag(struct rtattr *attrs[], const char *desc,
+ int field)
+{
+ __u8 flag;
+
+ if (!attrs[field])
+ return;
+
+ flag = rta_getattr_u8(attrs[field]);
+ if (is_json_context())
+ print_bool(PRINT_JSON, desc, NULL, flag);
+ else {
+ print_string(PRINT_FP, NULL, "%s ", desc);
+ print_string(PRINT_FP, NULL, "%s ",
+ flag ? "on" : "off");
+ }
+}
+
+static void print_key(struct rtattr *key)
+{
+ SPRINT_BUF(keyid);
+
+ print_string(PRINT_ANY, "key", " key %s\n",
+ hexstring_n2a(RTA_DATA(key), RTA_PAYLOAD(key),
+ keyid, sizeof(keyid)));
+}
+
+#define CIPHER_NAME_GCM_AES_128 "GCM-AES-128"
+#define CIPHER_NAME_GCM_AES_256 "GCM-AES-256"
+#define CIPHER_NAME_GCM_AES_XPN_128 "GCM-AES-XPN-128"
+#define CIPHER_NAME_GCM_AES_XPN_256 "GCM-AES-XPN-256"
+
+#define DEFAULT_CIPHER_NAME CIPHER_NAME_GCM_AES_128
+
+static const char *cs_id_to_name(__u64 cid)
+{
+ switch (cid) {
+ case MACSEC_DEFAULT_CIPHER_ID:
+ return DEFAULT_CIPHER_NAME;
+ case MACSEC_CIPHER_ID_GCM_AES_128:
+ /* MACSEC_DEFAULT_CIPHER_ALT: */
+ return CIPHER_NAME_GCM_AES_128;
+ case MACSEC_CIPHER_ID_GCM_AES_256:
+ return CIPHER_NAME_GCM_AES_256;
+ case MACSEC_CIPHER_ID_GCM_AES_XPN_128:
+ return CIPHER_NAME_GCM_AES_XPN_128;
+ case MACSEC_CIPHER_ID_GCM_AES_XPN_256:
+ return CIPHER_NAME_GCM_AES_XPN_256;
+ default:
+ return "(unknown)";
+ }
+}
+
+static const char *validate_to_str(__u8 validate)
+{
+ if (validate >= ARRAY_SIZE(validate_str))
+ return "(unknown)";
+
+ return validate_str[validate];
+}
+
+static const char *offload_to_str(__u8 offload)
+{
+ if (offload >= ARRAY_SIZE(offload_str))
+ return "(unknown)";
+
+ return offload_str[offload];
+}
+
+static void print_attrs(struct rtattr *attrs[])
+{
+ print_flag(attrs, "protect", MACSEC_SECY_ATTR_PROTECT);
+
+ if (attrs[MACSEC_SECY_ATTR_VALIDATE]) {
+ __u8 val = rta_getattr_u8(attrs[MACSEC_SECY_ATTR_VALIDATE]);
+
+ print_string(PRINT_ANY, "validate",
+ "validate %s ", validate_to_str(val));
+ }
+
+ print_flag(attrs, "sc", MACSEC_RXSC_ATTR_ACTIVE);
+ print_flag(attrs, "sa", MACSEC_SA_ATTR_ACTIVE);
+ print_flag(attrs, "encrypt", MACSEC_SECY_ATTR_ENCRYPT);
+ print_flag(attrs, "send_sci", MACSEC_SECY_ATTR_INC_SCI);
+ print_flag(attrs, "end_station", MACSEC_SECY_ATTR_ES);
+ print_flag(attrs, "scb", MACSEC_SECY_ATTR_SCB);
+ print_flag(attrs, "replay", MACSEC_SECY_ATTR_REPLAY);
+
+ if (attrs[MACSEC_SECY_ATTR_WINDOW]) {
+ __u32 win = rta_getattr_u32(attrs[MACSEC_SECY_ATTR_WINDOW]);
+
+ print_uint(PRINT_ANY, "window", "window %u ", win);
+ }
+
+ if (attrs[MACSEC_SECY_ATTR_CIPHER_SUITE]) {
+ __u64 cid = rta_getattr_u64(attrs[MACSEC_SECY_ATTR_CIPHER_SUITE]);
+
+ print_nl();
+ print_string(PRINT_ANY, "cipher_suite",
+ " cipher suite: %s,", cs_id_to_name(cid));
+ }
+
+ if (attrs[MACSEC_SECY_ATTR_ICV_LEN]) {
+ __u8 icv_len = rta_getattr_u8(attrs[MACSEC_SECY_ATTR_ICV_LEN]);
+
+ print_uint(PRINT_ANY, "icv_length",
+ " using ICV length %u\n", icv_len);
+ }
+}
+
+static __u64 getattr_u64(const struct rtattr *stat)
+{
+ size_t len = RTA_PAYLOAD(stat);
+
+ switch (len) {
+ case sizeof(__u64):
+ return rta_getattr_u64(stat);
+ case sizeof(__u32):
+ return rta_getattr_u32(stat);
+ case sizeof(__u16):
+ return rta_getattr_u16(stat);
+ case sizeof(__u8):
+ return rta_getattr_u8(stat);
+ default:
+ fprintf(stderr, "invalid attribute length %zu\n",
+ len);
+ exit(-1);
+ }
+}
+
+static void print_fp_stats(const char *prefix,
+ const char *names[], unsigned int num,
+ struct rtattr *stats[])
+{
+ unsigned int i;
+ int pad;
+
+ printf("%sstats:", prefix);
+
+ for (i = 1; i < num; i++) {
+ if (!names[i])
+ continue;
+ printf(" %s", names[i]);
+ }
+
+ printf("\n%s ", prefix);
+
+ for (i = 1; i < num; i++) {
+ if (!names[i])
+ continue;
+
+ pad = strlen(names[i]) + 1;
+ if (stats[i])
+ printf("%*llu", pad, getattr_u64(stats[i]));
+ else
+ printf("%*c", pad, '-');
+ }
+ printf("\n");
+}
+
+static void print_json_stats(const char *names[], unsigned int num,
+ struct rtattr *stats[])
+{
+ unsigned int i;
+
+ for (i = 1; i < num; i++) {
+ if (!names[i] || !stats[i])
+ continue;
+
+ print_u64(PRINT_JSON, names[i],
+ NULL, getattr_u64(stats[i]));
+ }
+}
+
+static void print_stats(const char *prefix,
+ const char *names[], unsigned int num,
+ struct rtattr *stats[])
+{
+
+ if (is_json_context())
+ print_json_stats(names, num, stats);
+ else
+ print_fp_stats(prefix, names, num, stats);
+}
+
+static const char *txsc_stats_names[NUM_MACSEC_TXSC_STATS_ATTR] = {
+ [MACSEC_TXSC_STATS_ATTR_OUT_PKTS_PROTECTED] = "OutPktsProtected",
+ [MACSEC_TXSC_STATS_ATTR_OUT_PKTS_ENCRYPTED] = "OutPktsEncrypted",
+ [MACSEC_TXSC_STATS_ATTR_OUT_OCTETS_PROTECTED] = "OutOctetsProtected",
+ [MACSEC_TXSC_STATS_ATTR_OUT_OCTETS_ENCRYPTED] = "OutOctetsEncrypted",
+};
+
+static void print_txsc_stats(const char *prefix, struct rtattr *attr)
+{
+ struct rtattr *stats[MACSEC_TXSC_STATS_ATTR_MAX + 1];
+
+ if (!attr || show_stats == 0)
+ return;
+
+ parse_rtattr_nested(stats, MACSEC_TXSC_STATS_ATTR_MAX, attr);
+
+ print_stats(prefix, txsc_stats_names, NUM_MACSEC_TXSC_STATS_ATTR,
+ stats);
+}
+
+static const char *secy_stats_names[NUM_MACSEC_SECY_STATS_ATTR] = {
+ [MACSEC_SECY_STATS_ATTR_OUT_PKTS_UNTAGGED] = "OutPktsUntagged",
+ [MACSEC_SECY_STATS_ATTR_IN_PKTS_UNTAGGED] = "InPktsUntagged",
+ [MACSEC_SECY_STATS_ATTR_OUT_PKTS_TOO_LONG] = "OutPktsTooLong",
+ [MACSEC_SECY_STATS_ATTR_IN_PKTS_NO_TAG] = "InPktsNoTag",
+ [MACSEC_SECY_STATS_ATTR_IN_PKTS_BAD_TAG] = "InPktsBadTag",
+ [MACSEC_SECY_STATS_ATTR_IN_PKTS_UNKNOWN_SCI] = "InPktsUnknownSCI",
+ [MACSEC_SECY_STATS_ATTR_IN_PKTS_NO_SCI] = "InPktsNoSCI",
+ [MACSEC_SECY_STATS_ATTR_IN_PKTS_OVERRUN] = "InPktsOverrun",
+};
+
+static void print_secy_stats(const char *prefix, struct rtattr *attr)
+{
+ struct rtattr *stats[MACSEC_SECY_STATS_ATTR_MAX + 1];
+
+ if (!attr || show_stats == 0)
+ return;
+
+ parse_rtattr_nested(stats, MACSEC_SECY_STATS_ATTR_MAX, attr);
+
+ print_stats(prefix, secy_stats_names,
+ NUM_MACSEC_SECY_STATS_ATTR, stats);
+}
+
+static const char *rxsa_stats_names[NUM_MACSEC_SA_STATS_ATTR] = {
+ [MACSEC_SA_STATS_ATTR_IN_PKTS_OK] = "InPktsOK",
+ [MACSEC_SA_STATS_ATTR_IN_PKTS_INVALID] = "InPktsInvalid",
+ [MACSEC_SA_STATS_ATTR_IN_PKTS_NOT_VALID] = "InPktsNotValid",
+ [MACSEC_SA_STATS_ATTR_IN_PKTS_NOT_USING_SA] = "InPktsNotUsingSA",
+ [MACSEC_SA_STATS_ATTR_IN_PKTS_UNUSED_SA] = "InPktsUnusedSA",
+};
+
+static void print_rxsa_stats(const char *prefix, struct rtattr *attr)
+{
+ struct rtattr *stats[MACSEC_SA_STATS_ATTR_MAX + 1];
+
+ if (!attr || show_stats == 0)
+ return;
+
+ parse_rtattr_nested(stats, MACSEC_SA_STATS_ATTR_MAX, attr);
+
+ print_stats(prefix, rxsa_stats_names, NUM_MACSEC_SA_STATS_ATTR, stats);
+}
+
+static const char *txsa_stats_names[NUM_MACSEC_SA_STATS_ATTR] = {
+ [MACSEC_SA_STATS_ATTR_OUT_PKTS_PROTECTED] = "OutPktsProtected",
+ [MACSEC_SA_STATS_ATTR_OUT_PKTS_ENCRYPTED] = "OutPktsEncrypted",
+};
+
+static void print_txsa_stats(const char *prefix, struct rtattr *attr)
+{
+ struct rtattr *stats[MACSEC_SA_STATS_ATTR_MAX + 1];
+
+ if (!attr || show_stats == 0)
+ return;
+
+ parse_rtattr_nested(stats, MACSEC_SA_STATS_ATTR_MAX, attr);
+
+ print_stats(prefix, txsa_stats_names, NUM_MACSEC_SA_STATS_ATTR, stats);
+}
+
+static void print_tx_sc(const char *prefix, __u64 sci, __u8 encoding_sa,
+ bool is_xpn, struct rtattr *txsc_stats,
+ struct rtattr *secy_stats, struct rtattr *sa)
+{
+ struct rtattr *sa_attr[MACSEC_SA_ATTR_MAX + 1];
+ struct rtattr *a;
+ int rem;
+
+ print_string(PRINT_FP, NULL, "%s", prefix);
+ print_0xhex(PRINT_ANY, "sci",
+ "TXSC: %016llx", ntohll(sci));
+ print_uint(PRINT_ANY, "encoding_sa",
+ " on SA %d\n", encoding_sa);
+
+ print_secy_stats(prefix, secy_stats);
+ print_txsc_stats(prefix, txsc_stats);
+
+ open_json_array(PRINT_JSON, "sa_list");
+ rem = RTA_PAYLOAD(sa);
+ for (a = RTA_DATA(sa); RTA_OK(a, rem); a = RTA_NEXT(a, rem)) {
+ bool state;
+
+ open_json_object(NULL);
+ parse_rtattr_nested(sa_attr, MACSEC_SA_ATTR_MAX, a);
+ state = rta_getattr_u8(sa_attr[MACSEC_SA_ATTR_ACTIVE]);
+
+ print_string(PRINT_FP, NULL, "%s", prefix);
+ print_string(PRINT_FP, NULL, "%s", prefix);
+ print_uint(PRINT_ANY, "an", "%d:",
+ rta_getattr_u8(sa_attr[MACSEC_SA_ATTR_AN]));
+ if (is_xpn) {
+ print_lluint(PRINT_ANY, "pn", " PN %llu,",
+ rta_getattr_u64(sa_attr[MACSEC_SA_ATTR_PN]));
+ print_0xhex(PRINT_ANY, "ssci",
+ "SSCI %08x",
+ ntohl(rta_getattr_u32(sa_attr[MACSEC_SA_ATTR_SSCI])));
+ } else {
+ print_uint(PRINT_ANY, "pn", " PN %u,",
+ rta_getattr_u32(sa_attr[MACSEC_SA_ATTR_PN]));
+ }
+
+ print_bool(PRINT_JSON, "active", NULL, state);
+ print_string(PRINT_FP, NULL,
+ " state %s,", state ? "on" : "off");
+ print_key(sa_attr[MACSEC_SA_ATTR_KEYID]);
+
+ print_txsa_stats(prefix, sa_attr[MACSEC_SA_ATTR_STATS]);
+ close_json_object();
+ }
+ close_json_array(PRINT_JSON, NULL);
+}
+
+static const char *rxsc_stats_names[NUM_MACSEC_RXSC_STATS_ATTR] = {
+ [MACSEC_RXSC_STATS_ATTR_IN_OCTETS_VALIDATED] = "InOctetsValidated",
+ [MACSEC_RXSC_STATS_ATTR_IN_OCTETS_DECRYPTED] = "InOctetsDecrypted",
+ [MACSEC_RXSC_STATS_ATTR_IN_PKTS_UNCHECKED] = "InPktsUnchecked",
+ [MACSEC_RXSC_STATS_ATTR_IN_PKTS_DELAYED] = "InPktsDelayed",
+ [MACSEC_RXSC_STATS_ATTR_IN_PKTS_OK] = "InPktsOK",
+ [MACSEC_RXSC_STATS_ATTR_IN_PKTS_INVALID] = "InPktsInvalid",
+ [MACSEC_RXSC_STATS_ATTR_IN_PKTS_LATE] = "InPktsLate",
+ [MACSEC_RXSC_STATS_ATTR_IN_PKTS_NOT_VALID] = "InPktsNotValid",
+ [MACSEC_RXSC_STATS_ATTR_IN_PKTS_NOT_USING_SA] = "InPktsNotUsingSA",
+ [MACSEC_RXSC_STATS_ATTR_IN_PKTS_UNUSED_SA] = "InPktsUnusedSA",
+};
+
+static void print_rxsc_stats(const char *prefix, struct rtattr *attr)
+{
+ struct rtattr *stats[MACSEC_RXSC_STATS_ATTR_MAX + 1];
+
+ if (!attr || show_stats == 0)
+ return;
+
+ parse_rtattr_nested(stats, MACSEC_RXSC_STATS_ATTR_MAX, attr);
+
+ print_stats(prefix, rxsc_stats_names,
+ NUM_MACSEC_RXSC_STATS_ATTR, stats);
+}
+
+static void print_rx_sc(const char *prefix, __be64 sci, __u8 active,
+ bool is_xpn, struct rtattr *rxsc_stats,
+ struct rtattr *sa)
+{
+ struct rtattr *sa_attr[MACSEC_SA_ATTR_MAX + 1];
+ struct rtattr *a;
+ int rem;
+
+ print_string(PRINT_FP, NULL, "%s", prefix);
+ print_0xhex(PRINT_ANY, "sci",
+ "RXSC: %016llx,", ntohll(sci));
+ print_bool(PRINT_JSON, "active", NULL, active);
+ print_string(PRINT_FP, NULL,
+ " state %s\n", active ? "on" : "off");
+ print_rxsc_stats(prefix, rxsc_stats);
+
+ open_json_array(PRINT_JSON, "sa_list");
+ rem = RTA_PAYLOAD(sa);
+ for (a = RTA_DATA(sa); RTA_OK(a, rem); a = RTA_NEXT(a, rem)) {
+ bool state;
+
+ open_json_object(NULL);
+ parse_rtattr_nested(sa_attr, MACSEC_SA_ATTR_MAX, a);
+ state = rta_getattr_u8(sa_attr[MACSEC_SA_ATTR_ACTIVE]);
+
+ print_string(PRINT_FP, NULL, "%s", prefix);
+ print_string(PRINT_FP, NULL, "%s", prefix);
+ print_uint(PRINT_ANY, "an", "%u:",
+ rta_getattr_u8(sa_attr[MACSEC_SA_ATTR_AN]));
+ if (is_xpn) {
+ print_lluint(PRINT_ANY, "pn", " PN %llu,",
+ rta_getattr_u64(sa_attr[MACSEC_SA_ATTR_PN]));
+ print_0xhex(PRINT_ANY, "ssci",
+ "SSCI %08x",
+ ntohl(rta_getattr_u32(sa_attr[MACSEC_SA_ATTR_SSCI])));
+ } else {
+ print_uint(PRINT_ANY, "pn", " PN %u,",
+ rta_getattr_u32(sa_attr[MACSEC_SA_ATTR_PN]));
+ }
+
+ print_bool(PRINT_JSON, "active", NULL, state);
+ print_string(PRINT_FP, NULL, " state %s,",
+ state ? "on" : "off");
+
+ print_key(sa_attr[MACSEC_SA_ATTR_KEYID]);
+
+ print_rxsa_stats(prefix, sa_attr[MACSEC_SA_ATTR_STATS]);
+ close_json_object();
+ }
+ close_json_array(PRINT_JSON, NULL);
+}
+
+static void print_rxsc_list(struct rtattr *sc, bool is_xpn)
+{
+ int rem = RTA_PAYLOAD(sc);
+ struct rtattr *c;
+
+ open_json_array(PRINT_JSON, "rx_sc");
+ for (c = RTA_DATA(sc); RTA_OK(c, rem); c = RTA_NEXT(c, rem)) {
+ struct rtattr *sc_attr[MACSEC_RXSC_ATTR_MAX + 1];
+
+ open_json_object(NULL);
+
+ parse_rtattr_nested(sc_attr, MACSEC_RXSC_ATTR_MAX, c);
+ print_rx_sc(" ",
+ rta_getattr_u64(sc_attr[MACSEC_RXSC_ATTR_SCI]),
+ rta_getattr_u32(sc_attr[MACSEC_RXSC_ATTR_ACTIVE]),
+ is_xpn,
+ sc_attr[MACSEC_RXSC_ATTR_STATS],
+ sc_attr[MACSEC_RXSC_ATTR_SA_LIST]);
+ close_json_object();
+ }
+ close_json_array(PRINT_JSON, NULL);
+}
+
+static int process(struct nlmsghdr *n, void *arg)
+{
+ struct genlmsghdr *ghdr;
+ struct rtattr *attrs[MACSEC_ATTR_MAX + 1];
+ struct rtattr *attrs_secy[MACSEC_SECY_ATTR_MAX + 1];
+ int len = n->nlmsg_len;
+ int ifindex;
+ __u64 sci;
+ __u8 encoding_sa;
+ __u64 cid;
+ bool is_xpn = false;
+
+ if (n->nlmsg_type != genl_family)
+ return -1;
+
+ len -= NLMSG_LENGTH(GENL_HDRLEN);
+ if (len < 0)
+ return -1;
+
+ ghdr = NLMSG_DATA(n);
+ if (ghdr->cmd != MACSEC_CMD_GET_TXSC)
+ return 0;
+
+ parse_rtattr(attrs, MACSEC_ATTR_MAX, (void *) ghdr + GENL_HDRLEN, len);
+ if (!validate_dump(attrs)) {
+ fprintf(stderr, "incomplete dump message\n");
+ return -1;
+ }
+
+ ifindex = rta_getattr_u32(attrs[MACSEC_ATTR_IFINDEX]);
+ parse_rtattr_nested(attrs_secy, MACSEC_SECY_ATTR_MAX,
+ attrs[MACSEC_ATTR_SECY]);
+
+ if (!validate_secy_dump(attrs_secy)) {
+ fprintf(stderr, "incomplete dump message\n");
+ return -1;
+ }
+
+ sci = rta_getattr_u64(attrs_secy[MACSEC_SECY_ATTR_SCI]);
+ encoding_sa = rta_getattr_u8(attrs_secy[MACSEC_SECY_ATTR_ENCODING_SA]);
+
+ if (filter.ifindex && ifindex != filter.ifindex)
+ return 0;
+
+ if (filter.sci && sci != filter.sci)
+ return 0;
+
+ open_json_object(NULL);
+ print_uint(PRINT_ANY, "ifindex", "%u: ", ifindex);
+ print_color_string(PRINT_ANY, COLOR_IFNAME, "ifname",
+ "%s: ", ll_index_to_name(ifindex));
+
+ print_attrs(attrs_secy);
+
+ cid = rta_getattr_u64(attrs_secy[MACSEC_SECY_ATTR_CIPHER_SUITE]);
+ is_xpn = ciphersuite_is_xpn(cid);
+ print_tx_sc(" ", sci, encoding_sa, is_xpn,
+ attrs[MACSEC_ATTR_TXSC_STATS],
+ attrs[MACSEC_ATTR_SECY_STATS],
+ attrs[MACSEC_ATTR_TXSA_LIST]);
+
+ if (attrs[MACSEC_ATTR_RXSC_LIST])
+ print_rxsc_list(attrs[MACSEC_ATTR_RXSC_LIST], is_xpn);
+
+ if (attrs[MACSEC_ATTR_OFFLOAD]) {
+ struct rtattr *attrs_offload[MACSEC_OFFLOAD_ATTR_MAX + 1];
+ __u8 offload;
+
+ parse_rtattr_nested(attrs_offload, MACSEC_OFFLOAD_ATTR_MAX,
+ attrs[MACSEC_ATTR_OFFLOAD]);
+
+ offload = rta_getattr_u8(attrs_offload[MACSEC_OFFLOAD_ATTR_TYPE]);
+ print_string(PRINT_ANY, "offload",
+ " offload: %s ", offload_to_str(offload));
+ print_nl();
+ }
+
+ close_json_object();
+
+ return 0;
+}
+
+static int do_dump(int ifindex)
+{
+ MACSEC_GENL_REQ(req, MACSEC_BUFLEN, MACSEC_CMD_GET_TXSC,
+ NLM_F_REQUEST | NLM_F_DUMP);
+
+ memset(&filter, 0, sizeof(filter));
+ filter.ifindex = ifindex;
+
+ req.n.nlmsg_seq = genl_rth.dump = ++genl_rth.seq;
+ if (rtnl_send(&genl_rth, &req, req.n.nlmsg_len) < 0) {
+ perror("Failed to send dump request");
+ exit(1);
+ }
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&genl_rth, process, stdout) < 0) {
+ delete_json_obj();
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ delete_json_obj();
+
+ return 0;
+}
+
+static int do_show(int argc, char **argv)
+{
+ int ifindex;
+
+ if (argc == 0)
+ return do_dump(0);
+
+ ifindex = ll_name_to_index(*argv);
+ if (ifindex == 0) {
+ fprintf(stderr, "Device \"%s\" does not exist.\n", *argv);
+ return -1;
+ }
+
+ argc--, argv++;
+ if (argc == 0)
+ return do_dump(ifindex);
+
+ ipmacsec_usage();
+ return -1;
+}
+
+int do_ipmacsec(int argc, char **argv)
+{
+ if (argc < 1)
+ ipmacsec_usage();
+
+ if (matches(*argv, "help") == 0)
+ ipmacsec_usage();
+
+ if (genl_init_handle(&genl_rth, MACSEC_GENL_NAME, &genl_family))
+ exit(1);
+
+ if (matches(*argv, "show") == 0)
+ return do_show(argc-1, argv+1);
+
+ if (matches(*argv, "add") == 0)
+ return do_modify(CMD_ADD, argc-1, argv+1);
+ if (matches(*argv, "set") == 0)
+ return do_modify(CMD_UPD, argc-1, argv+1);
+ if (matches(*argv, "delete") == 0)
+ return do_modify(CMD_DEL, argc-1, argv+1);
+ if (matches(*argv, "offload") == 0)
+ return do_offload(CMD_OFFLOAD, argc-1, argv+1);
+
+ fprintf(stderr, "Command \"%s\" is unknown, try \"ip macsec help\".\n",
+ *argv);
+ exit(-1);
+}
+
+/* device creation */
+static void macsec_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ if (!tb)
+ return;
+
+ if (tb[IFLA_MACSEC_SCI]) {
+ if (is_json_context()) {
+ SPRINT_BUF(b1);
+
+ snprintf(b1, sizeof(b1), "%016llx",
+ ntohll(rta_getattr_u64(tb[IFLA_MACSEC_SCI])));
+ print_string(PRINT_JSON, "sci", NULL, b1);
+ } else {
+ fprintf(f, "sci %016llx ",
+ ntohll(rta_getattr_u64(tb[IFLA_MACSEC_SCI])));
+ }
+ }
+
+ print_flag(tb, "protect", IFLA_MACSEC_PROTECT);
+
+ if (tb[IFLA_MACSEC_CIPHER_SUITE]) {
+ __u64 csid
+ = rta_getattr_u64(tb[IFLA_MACSEC_CIPHER_SUITE]);
+
+ print_string(PRINT_ANY,
+ "cipher_suite",
+ "cipher %s ",
+ cs_id_to_name(csid));
+ }
+
+ if (tb[IFLA_MACSEC_ICV_LEN]) {
+ if (is_json_context()) {
+ char b2[4];
+
+ snprintf(b2, sizeof(b2), "%hhu",
+ rta_getattr_u8(tb[IFLA_MACSEC_ICV_LEN]));
+ print_uint(PRINT_JSON, "icv_len", NULL, atoi(b2));
+ } else {
+ fprintf(f, "icvlen %hhu ",
+ rta_getattr_u8(tb[IFLA_MACSEC_ICV_LEN]));
+ }
+ }
+
+ if (tb[IFLA_MACSEC_ENCODING_SA]) {
+ if (is_json_context()) {
+ char b2[4];
+
+ snprintf(b2, sizeof(b2), "%hhu",
+ rta_getattr_u8(tb[IFLA_MACSEC_ENCODING_SA]));
+ print_uint(PRINT_JSON, "encoding_sa", NULL, atoi(b2));
+ } else {
+ fprintf(f, "encodingsa %hhu ",
+ rta_getattr_u8(tb[IFLA_MACSEC_ENCODING_SA]));
+ }
+ }
+
+ if (tb[IFLA_MACSEC_VALIDATION]) {
+ __u8 val = rta_getattr_u8(tb[IFLA_MACSEC_VALIDATION]);
+
+ print_string(PRINT_ANY,
+ "validation",
+ "validate %s ",
+ validate_to_str(val));
+ }
+
+ if (tb[IFLA_MACSEC_OFFLOAD]) {
+ __u8 val = rta_getattr_u8(tb[IFLA_MACSEC_OFFLOAD]);
+
+ print_string(PRINT_ANY,
+ "offload",
+ "offload %s ",
+ offload_to_str(val));
+ }
+
+ const char *inc_sci, *es, *replay;
+
+ if (is_json_context()) {
+ inc_sci = "inc_sci";
+ replay = "replay_protect";
+ es = "es";
+ } else {
+ inc_sci = "send_sci";
+ es = "end_station";
+ replay = "replay";
+ }
+
+ print_flag(tb, "encrypt", IFLA_MACSEC_ENCRYPT);
+ print_flag(tb, inc_sci, IFLA_MACSEC_INC_SCI);
+ print_flag(tb, es, IFLA_MACSEC_ES);
+ print_flag(tb, "scb", IFLA_MACSEC_SCB);
+ print_flag(tb, replay, IFLA_MACSEC_REPLAY_PROTECT);
+
+ if (tb[IFLA_MACSEC_WINDOW])
+ print_int(PRINT_ANY,
+ "window",
+ "window %d ",
+ rta_getattr_u32(tb[IFLA_MACSEC_WINDOW]));
+}
+
+static bool check_txsc_flags(bool es, bool scb, bool sci)
+{
+ if (sci && (es || scb))
+ return false;
+ if (es && scb)
+ return false;
+ return true;
+}
+
+static void usage(FILE *f)
+{
+ fprintf(f,
+ "Usage: ... macsec [ [ address <lladdr> ] port { 1..2^16-1 } | sci <u64> ]\n"
+ " [ cipher { default | gcm-aes-128 | gcm-aes-256 | gcm-aes-xpn-128 | gcm-aes-xpn-256 } ]\n"
+ " [ icvlen { 8..16 } ]\n"
+ " [ encrypt { on | off } ]\n"
+ " [ send_sci { on | off } ]\n"
+ " [ end_station { on | off } ]\n"
+ " [ scb { on | off } ]\n"
+ " [ protect { on | off } ]\n"
+ " [ replay { on | off} window { 0..2^32-1 } ]\n"
+ " [ validate { strict | check | disabled } ]\n"
+ " [ encodingsa { 0..3 } ]\n"
+ " [ offload { mac | phy | off } ]\n"
+ );
+}
+
+static int macsec_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ int ret;
+ __u8 encoding_sa = 0xff;
+ __u32 window = -1;
+ enum macsec_offload offload;
+ struct cipher_args cipher = {0};
+ enum macsec_validation_type validate;
+ bool es = false, scb = false, send_sci = false;
+ int replay_protect = -1;
+ struct sci sci = { 0 };
+
+ ret = get_sci_portaddr(&sci, &argc, &argv, true, true);
+ if (ret < 0) {
+ fprintf(stderr, "expected sci\n");
+ return -1;
+ }
+
+ if (ret > 0) {
+ if (sci.sci)
+ addattr_l(n, MACSEC_BUFLEN, IFLA_MACSEC_SCI,
+ &sci.sci, sizeof(sci.sci));
+ else
+ addattr_l(n, MACSEC_BUFLEN, IFLA_MACSEC_PORT,
+ &sci.port, sizeof(sci.port));
+ }
+
+ while (argc > 0) {
+ if (strcmp(*argv, "cipher") == 0) {
+ NEXT_ARG();
+ if (cipher.id)
+ duparg("cipher", *argv);
+ if (strcmp(*argv, "default") == 0)
+ cipher.id = MACSEC_DEFAULT_CIPHER_ID;
+ else if (strcmp(*argv, "gcm-aes-128") == 0 ||
+ strcmp(*argv, "GCM-AES-128") == 0)
+ cipher.id = MACSEC_CIPHER_ID_GCM_AES_128;
+ else if (strcmp(*argv, "gcm-aes-256") == 0 ||
+ strcmp(*argv, "GCM-AES-256") == 0)
+ cipher.id = MACSEC_CIPHER_ID_GCM_AES_256;
+ else if (strcmp(*argv, "gcm-aes-xpn-128") == 0 ||
+ strcmp(*argv, "GCM-AES-XPN-128") == 0)
+ cipher.id = MACSEC_CIPHER_ID_GCM_AES_XPN_128;
+ else if (strcmp(*argv, "gcm-aes-xpn-256") == 0 ||
+ strcmp(*argv, "GCM-AES-XPN-256") == 0)
+ cipher.id = MACSEC_CIPHER_ID_GCM_AES_XPN_256;
+ else
+ invarg("expected: default, gcm-aes-128, gcm-aes-256,"
+ " gcm-aes-xpn-128 or gcm-aes-xpn-256", *argv);
+ } else if (strcmp(*argv, "icvlen") == 0) {
+ NEXT_ARG();
+ if (cipher.icv_len)
+ duparg("icvlen", *argv);
+ get_icvlen(&cipher.icv_len, *argv);
+ } else if (strcmp(*argv, "encrypt") == 0) {
+ NEXT_ARG();
+ int i;
+
+ i = parse_on_off("encrypt", *argv, &ret);
+ if (ret != 0)
+ return ret;
+ addattr8(n, MACSEC_BUFLEN, IFLA_MACSEC_ENCRYPT, i);
+ } else if (strcmp(*argv, "send_sci") == 0) {
+ NEXT_ARG();
+ int i;
+
+ i = parse_on_off("send_sci", *argv, &ret);
+ if (ret != 0)
+ return ret;
+ send_sci = i;
+ addattr8(n, MACSEC_BUFLEN,
+ IFLA_MACSEC_INC_SCI, send_sci);
+ } else if (strcmp(*argv, "end_station") == 0) {
+ NEXT_ARG();
+ int i;
+
+ i = parse_on_off("end_station", *argv, &ret);
+ if (ret != 0)
+ return ret;
+ es = i;
+ addattr8(n, MACSEC_BUFLEN, IFLA_MACSEC_ES, es);
+ } else if (strcmp(*argv, "scb") == 0) {
+ NEXT_ARG();
+ int i;
+
+ i = parse_on_off("scb", *argv, &ret);
+ if (ret != 0)
+ return ret;
+ scb = i;
+ addattr8(n, MACSEC_BUFLEN, IFLA_MACSEC_SCB, scb);
+ } else if (strcmp(*argv, "protect") == 0) {
+ NEXT_ARG();
+ int i;
+
+ i = parse_on_off("protect", *argv, &ret);
+ if (ret != 0)
+ return ret;
+ addattr8(n, MACSEC_BUFLEN, IFLA_MACSEC_PROTECT, i);
+ } else if (strcmp(*argv, "replay") == 0) {
+ NEXT_ARG();
+ int i;
+
+ i = parse_on_off("replay", *argv, &ret);
+ if (ret != 0)
+ return ret;
+ replay_protect = !!i;
+ } else if (strcmp(*argv, "window") == 0) {
+ NEXT_ARG();
+ ret = get_u32(&window, *argv, 0);
+ if (ret)
+ invarg("expected replay window size", *argv);
+ } else if (strcmp(*argv, "validate") == 0) {
+ NEXT_ARG();
+ validate = parse_one_of_deprecated("validate", *argv,
+ validate_str,
+ ARRAY_SIZE(validate_str),
+ &ret);
+ if (ret != 0)
+ return ret;
+ addattr8(n, MACSEC_BUFLEN,
+ IFLA_MACSEC_VALIDATION, validate);
+ } else if (strcmp(*argv, "encodingsa") == 0) {
+ if (encoding_sa != 0xff)
+ duparg2("encodingsa", "encodingsa");
+ NEXT_ARG();
+ ret = get_an(&encoding_sa, *argv);
+ if (ret)
+ invarg("expected an { 0..3 }", *argv);
+ } else if (strcmp(*argv, "offload") == 0) {
+ NEXT_ARG();
+ offload = parse_one_of("offload", *argv, offload_str,
+ ARRAY_SIZE(offload_str), &ret);
+ if (ret != 0)
+ return ret;
+ addattr8(n, MACSEC_BUFLEN,
+ IFLA_MACSEC_OFFLOAD, offload);
+ } else {
+ fprintf(stderr, "macsec: unknown command \"%s\"?\n",
+ *argv);
+ usage(stderr);
+ return -1;
+ }
+
+ argv++; argc--;
+ }
+
+ if (!check_txsc_flags(es, scb, send_sci)) {
+ fprintf(stderr,
+ "invalid combination of send_sci/end_station/scb\n");
+ return -1;
+ }
+
+ if (window != -1 && replay_protect == -1) {
+ fprintf(stderr,
+ "replay window set, but replay protection not enabled. did you mean 'replay on window %u'?\n",
+ window);
+ return -1;
+ } else if (window == -1 && replay_protect == 1) {
+ fprintf(stderr,
+ "replay protection enabled, but no window set. did you mean 'replay on window VALUE'?\n");
+ return -1;
+ }
+
+ if (cipher.id)
+ addattr_l(n, MACSEC_BUFLEN, IFLA_MACSEC_CIPHER_SUITE,
+ &cipher.id, sizeof(cipher.id));
+ if (cipher.icv_len)
+ addattr_l(n, MACSEC_BUFLEN, IFLA_MACSEC_ICV_LEN,
+ &cipher.icv_len, sizeof(cipher.icv_len));
+
+ if (replay_protect != -1) {
+ if (replay_protect)
+ addattr32(n, MACSEC_BUFLEN, IFLA_MACSEC_WINDOW, window);
+ addattr8(n, MACSEC_BUFLEN, IFLA_MACSEC_REPLAY_PROTECT,
+ replay_protect);
+ }
+
+ if (encoding_sa != 0xff) {
+ addattr_l(n, MACSEC_BUFLEN, IFLA_MACSEC_ENCODING_SA,
+ &encoding_sa, sizeof(encoding_sa));
+ }
+
+ return 0;
+}
+
+static void macsec_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ usage(f);
+}
+
+struct link_util macsec_link_util = {
+ .id = "macsec",
+ .maxattr = IFLA_MACSEC_MAX,
+ .parse_opt = macsec_parse_opt,
+ .print_help = macsec_print_help,
+ .print_opt = macsec_print_opt,
+};
diff --git a/ip/ipmaddr.c b/ip/ipmaddr.c
new file mode 100644
index 0000000..2418b30
--- /dev/null
+++ b/ip/ipmaddr.c
@@ -0,0 +1,387 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * ipmaddr.c "ip maddress".
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include <linux/netdevice.h>
+#include <linux/if.h>
+#include <linux/if_arp.h>
+#include <linux/sockios.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+static struct {
+ char *dev;
+ int family;
+} filter;
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip maddr [ add | del ] MULTIADDR dev STRING\n"
+ " ip maddr show [ dev STRING ]\n");
+ exit(-1);
+}
+
+static int parse_hex(char *str, unsigned char *addr, size_t size)
+{
+ int len = 0;
+
+ while (*str && (len < 2 * size)) {
+ int tmp;
+
+ if (str[1] == 0)
+ return -1;
+ if (sscanf(str, "%02x", &tmp) != 1)
+ return -1;
+ addr[len] = tmp;
+ len++;
+ str += 2;
+ }
+ return len;
+}
+
+struct ma_info {
+ struct ma_info *next;
+ int index;
+ int users;
+ char *features;
+ char name[IFNAMSIZ];
+ inet_prefix addr;
+};
+
+static void maddr_ins(struct ma_info **lst, struct ma_info *m)
+{
+ struct ma_info *mp;
+
+ for (; (mp = *lst) != NULL; lst = &mp->next) {
+ if (mp->index > m->index)
+ break;
+ }
+ m->next = *lst;
+ *lst = m;
+}
+
+static void maddr_clear(struct ma_info *lst)
+{
+ struct ma_info *mp;
+
+ while ((mp = lst) != NULL) {
+ lst = mp->next;
+ free(mp);
+ }
+}
+
+static void read_dev_mcast(struct ma_info **result_p)
+{
+ char buf[256];
+ FILE *fp = fopen("/proc/net/dev_mcast", "r");
+
+ if (!fp)
+ return;
+
+ while (fgets(buf, sizeof(buf), fp)) {
+ char hexa[256];
+ struct ma_info m = { .addr.family = AF_PACKET };
+ int len;
+ int st;
+
+ sscanf(buf, "%d%s%d%d%s", &m.index, m.name, &m.users, &st,
+ hexa);
+ if (filter.dev && strcmp(filter.dev, m.name))
+ continue;
+
+ len = parse_hex(hexa, (unsigned char *)&m.addr.data, sizeof(m.addr.data));
+ if (len >= 0) {
+ struct ma_info *ma = malloc(sizeof(m));
+
+ if (ma == NULL)
+ break;
+ memcpy(ma, &m, sizeof(m));
+ ma->addr.bytelen = len;
+ ma->addr.bitlen = len<<3;
+ if (st)
+ ma->features = "static";
+ maddr_ins(result_p, ma);
+ }
+ }
+ fclose(fp);
+}
+
+static void read_igmp(struct ma_info **result_p)
+{
+ struct ma_info m = {
+ .addr.family = AF_INET,
+ .addr.bitlen = 32,
+ .addr.bytelen = 4,
+ };
+ char buf[256];
+ FILE *fp = fopen("/proc/net/igmp", "r");
+
+ if (!fp)
+ return;
+ if (!fgets(buf, sizeof(buf), fp)) {
+ fclose(fp);
+ return;
+ }
+
+ while (fgets(buf, sizeof(buf), fp)) {
+ struct ma_info *ma;
+
+ if (buf[0] != '\t') {
+ size_t len;
+
+ sscanf(buf, "%d%s", &m.index, m.name);
+ len = strlen(m.name);
+ if (m.name[len - 1] == ':')
+ m.name[len - 1] = '\0';
+ continue;
+ }
+
+ if (filter.dev && strcmp(filter.dev, m.name))
+ continue;
+
+ sscanf(buf, "%08x%d", (__u32 *)&m.addr.data, &m.users);
+
+ ma = malloc(sizeof(m));
+ if (ma == NULL)
+ break;
+
+ memcpy(ma, &m, sizeof(m));
+ maddr_ins(result_p, ma);
+ }
+ fclose(fp);
+}
+
+
+static void read_igmp6(struct ma_info **result_p)
+{
+ char buf[256];
+ FILE *fp = fopen("/proc/net/igmp6", "r");
+
+ if (!fp)
+ return;
+
+ while (fgets(buf, sizeof(buf), fp)) {
+ char hexa[256];
+ struct ma_info m = { .addr.family = AF_INET6 };
+ int len;
+
+ sscanf(buf, "%d%s%s%d", &m.index, m.name, hexa, &m.users);
+
+ if (filter.dev && strcmp(filter.dev, m.name))
+ continue;
+
+ len = parse_hex(hexa, (unsigned char *)&m.addr.data, sizeof(m.addr.data));
+ if (len >= 0) {
+ struct ma_info *ma = malloc(sizeof(m));
+
+ if (ma == NULL)
+ break;
+
+ memcpy(ma, &m, sizeof(m));
+ ma->addr.bytelen = len;
+ ma->addr.bitlen = len<<3;
+ maddr_ins(result_p, ma);
+ }
+ }
+ fclose(fp);
+}
+
+static void print_maddr(FILE *fp, struct ma_info *list)
+{
+ print_string(PRINT_FP, NULL, "\t", NULL);
+
+ open_json_object(NULL);
+ if (list->addr.family == AF_PACKET) {
+ SPRINT_BUF(b1);
+
+ print_string(PRINT_FP, NULL, "link ", NULL);
+ print_color_string(PRINT_ANY, COLOR_MAC, "link", "%s",
+ ll_addr_n2a((void *)list->addr.data, list->addr.bytelen,
+ 0, b1, sizeof(b1)));
+ } else {
+ print_string(PRINT_ANY, "family", "%-5s ",
+ family_name(list->addr.family));
+ print_color_string(PRINT_ANY, ifa_family_color(list->addr.family),
+ "address", "%s",
+ format_host(list->addr.family,
+ -1, list->addr.data));
+ }
+
+ if (list->users != 1)
+ print_uint(PRINT_ANY, "users", " users %u", list->users);
+
+ if (list->features)
+ print_string(PRINT_ANY, "features", " %s", list->features);
+
+ print_string(PRINT_FP, NULL, "\n", NULL);
+ close_json_object();
+}
+
+static void print_mlist(FILE *fp, struct ma_info *list)
+{
+ int cur_index = 0;
+
+ new_json_obj(json);
+ for (; list; list = list->next) {
+
+ if (list->index != cur_index || oneline) {
+ if (cur_index) {
+ close_json_array(PRINT_JSON, NULL);
+ close_json_object();
+ }
+ open_json_object(NULL);
+
+ print_uint(PRINT_ANY, "ifindex", "%d:", list->index);
+ print_color_string(PRINT_ANY, COLOR_IFNAME,
+ "ifname", "\t%s", list->name);
+ print_nl();
+ cur_index = list->index;
+
+ open_json_array(PRINT_JSON, "maddr");
+ }
+
+ print_maddr(fp, list);
+ }
+ if (cur_index) {
+ close_json_array(PRINT_JSON, NULL);
+ close_json_object();
+ }
+
+ delete_json_obj();
+}
+
+static int multiaddr_list(int argc, char **argv)
+{
+ struct ma_info *list = NULL;
+
+ if (!filter.family)
+ filter.family = preferred_family;
+
+ while (argc > 0) {
+ if (1) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ } else if (matches(*argv, "help") == 0)
+ usage();
+ if (filter.dev)
+ duparg2("dev", *argv);
+ filter.dev = *argv;
+ }
+ argv++; argc--;
+ }
+
+ if (!filter.family || filter.family == AF_PACKET)
+ read_dev_mcast(&list);
+ if (!filter.family || filter.family == AF_INET)
+ read_igmp(&list);
+ if (!filter.family || filter.family == AF_INET6)
+ read_igmp6(&list);
+ print_mlist(stdout, list);
+ maddr_clear(list);
+ return 0;
+}
+
+static int multiaddr_modify(int cmd, int argc, char **argv)
+{
+ struct ifreq ifr = {};
+ int family;
+ int fd, len;
+
+ if (cmd == RTM_NEWADDR)
+ cmd = SIOCADDMULTI;
+ else
+ cmd = SIOCDELMULTI;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (ifr.ifr_name[0])
+ duparg("dev", *argv);
+ if (get_ifname(ifr.ifr_name, *argv))
+ invarg("\"dev\" not a valid ifname", *argv);
+ } else {
+ if (matches(*argv, "address") == 0) {
+ NEXT_ARG();
+ }
+ if (matches(*argv, "help") == 0)
+ usage();
+ if (ifr.ifr_hwaddr.sa_data[0])
+ duparg("address", *argv);
+ len = ll_addr_a2n(ifr.ifr_hwaddr.sa_data,
+ sizeof(ifr.ifr_hwaddr.sa_data),
+ *argv);
+ if (len < 0)
+ exit(1);
+
+ if (len != ETH_ALEN) {
+ fprintf(stderr, "Error: Invalid address length %d - must be %d bytes\n", len, ETH_ALEN);
+ exit(1);
+ }
+ }
+ argc--; argv++;
+ }
+ if (ifr.ifr_name[0] == 0) {
+ fprintf(stderr, "Not enough information: \"dev\" is required.\n");
+ exit(-1);
+ }
+
+ switch (preferred_family) {
+ case AF_INET6:
+ case AF_PACKET:
+ case AF_INET:
+ family = preferred_family;
+ break;
+ default:
+ family = AF_INET;
+ }
+
+ fd = socket(family, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ perror("Cannot create socket");
+ exit(1);
+ }
+ if (ioctl(fd, cmd, (char *)&ifr) != 0) {
+ perror("ioctl");
+ exit(1);
+ }
+ close(fd);
+
+ exit(0);
+}
+
+
+int do_multiaddr(int argc, char **argv)
+{
+ if (argc < 1)
+ return multiaddr_list(0, NULL);
+ if (matches(*argv, "add") == 0)
+ return multiaddr_modify(RTM_NEWADDR, argc-1, argv+1);
+ if (matches(*argv, "delete") == 0)
+ return multiaddr_modify(RTM_DELADDR, argc-1, argv+1);
+ if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
+ || matches(*argv, "lst") == 0)
+ return multiaddr_list(argc-1, argv+1);
+ if (matches(*argv, "help") == 0)
+ usage();
+ fprintf(stderr, "Command \"%s\" is unknown, try \"ip maddr help\".\n", *argv);
+ exit(-1);
+}
diff --git a/ip/ipmonitor.c b/ip/ipmonitor.c
new file mode 100644
index 0000000..9b05526
--- /dev/null
+++ b/ip/ipmonitor.c
@@ -0,0 +1,345 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * ipmonitor.c "ip monitor".
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <time.h>
+
+#include "utils.h"
+#include "ip_common.h"
+#include "nh_common.h"
+
+static void usage(void) __attribute__((noreturn));
+static int prefix_banner;
+int listen_all_nsid;
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip monitor [ all | OBJECTS ] [ FILE ] [ label ] [ all-nsid ]\n"
+ " [ dev DEVICE ]\n"
+ "OBJECTS := address | link | mroute | neigh | netconf |\n"
+ " nexthop | nsid | prefix | route | rule | stats\n"
+ "FILE := file FILENAME\n");
+ exit(-1);
+}
+
+static void print_headers(FILE *fp, char *label, struct rtnl_ctrl_data *ctrl)
+{
+ if (timestamp)
+ print_timestamp(fp);
+
+ if (listen_all_nsid) {
+ if (ctrl == NULL || ctrl->nsid < 0)
+ fprintf(fp, "[nsid current]");
+ else
+ fprintf(fp, "[nsid %d]", ctrl->nsid);
+ }
+
+ if (prefix_banner)
+ fprintf(fp, "%s", label);
+}
+
+static int accept_msg(struct rtnl_ctrl_data *ctrl,
+ struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+
+ switch (n->nlmsg_type) {
+ case RTM_NEWROUTE:
+ case RTM_DELROUTE: {
+ struct rtmsg *r = NLMSG_DATA(n);
+ int len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*r));
+
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ if (r->rtm_flags & RTM_F_CLONED)
+ return 0;
+
+ if (r->rtm_family == RTNL_FAMILY_IPMR ||
+ r->rtm_family == RTNL_FAMILY_IP6MR) {
+ print_headers(fp, "[MROUTE]", ctrl);
+ print_mroute(n, arg);
+ return 0;
+ } else {
+ print_headers(fp, "[ROUTE]", ctrl);
+ print_route(n, arg);
+ return 0;
+ }
+ }
+
+ case RTM_NEWNEXTHOP:
+ case RTM_DELNEXTHOP:
+ print_headers(fp, "[NEXTHOP]", ctrl);
+ print_cache_nexthop(n, arg, true);
+ return 0;
+
+ case RTM_NEWNEXTHOPBUCKET:
+ case RTM_DELNEXTHOPBUCKET:
+ print_headers(fp, "[NEXTHOPBUCKET]", ctrl);
+ print_nexthop_bucket(n, arg);
+ return 0;
+
+ case RTM_NEWLINK:
+ case RTM_DELLINK:
+ ll_remember_index(n, NULL);
+ print_headers(fp, "[LINK]", ctrl);
+ print_linkinfo(n, arg);
+ return 0;
+
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ print_headers(fp, "[ADDR]", ctrl);
+ print_addrinfo(n, arg);
+ return 0;
+
+ case RTM_NEWADDRLABEL:
+ case RTM_DELADDRLABEL:
+ print_headers(fp, "[ADDRLABEL]", ctrl);
+ print_addrlabel(n, arg);
+ return 0;
+
+ case RTM_NEWNEIGH:
+ case RTM_DELNEIGH:
+ case RTM_GETNEIGH:
+ if (preferred_family) {
+ struct ndmsg *r = NLMSG_DATA(n);
+
+ if (r->ndm_family != preferred_family)
+ return 0;
+ }
+
+ print_headers(fp, "[NEIGH]", ctrl);
+ print_neigh(n, arg);
+ return 0;
+
+ case RTM_NEWPREFIX:
+ print_headers(fp, "[PREFIX]", ctrl);
+ print_prefix(n, arg);
+ return 0;
+
+ case RTM_NEWRULE:
+ case RTM_DELRULE:
+ print_headers(fp, "[RULE]", ctrl);
+ print_rule(n, arg);
+ return 0;
+
+ case NLMSG_TSTAMP:
+ print_nlmsg_timestamp(fp, n);
+ return 0;
+
+ case RTM_NEWNETCONF:
+ case RTM_DELNETCONF:
+ print_headers(fp, "[NETCONF]", ctrl);
+ print_netconf(ctrl, n, arg);
+ return 0;
+
+ case RTM_DELNSID:
+ case RTM_NEWNSID:
+ print_headers(fp, "[NSID]", ctrl);
+ print_nsid(n, arg);
+ return 0;
+
+ case RTM_NEWSTATS:
+ print_headers(fp, "[STATS]", ctrl);
+ ipstats_print(n, arg);
+ return 0;
+
+ case NLMSG_ERROR:
+ case NLMSG_NOOP:
+ case NLMSG_DONE:
+ break; /* ignore */
+
+ default:
+ fprintf(stderr,
+ "Unknown message: type=0x%08x(%d) flags=0x%08x(%d) len=0x%08x(%d)\n",
+ n->nlmsg_type, n->nlmsg_type,
+ n->nlmsg_flags, n->nlmsg_flags, n->nlmsg_len,
+ n->nlmsg_len);
+ }
+ return 0;
+}
+
+#define IPMON_LLINK BIT(0)
+#define IPMON_LADDR BIT(1)
+#define IPMON_LROUTE BIT(2)
+#define IPMON_LMROUTE BIT(3)
+#define IPMON_LPREFIX BIT(4)
+#define IPMON_LNEIGH BIT(5)
+#define IPMON_LNETCONF BIT(6)
+#define IPMON_LSTATS BIT(7)
+#define IPMON_LRULE BIT(8)
+#define IPMON_LNSID BIT(9)
+#define IPMON_LNEXTHOP BIT(10)
+
+#define IPMON_L_ALL (~0)
+
+int do_ipmonitor(int argc, char **argv)
+{
+ unsigned int groups = 0, lmask = 0;
+ /* "needed" mask, failure to enable is an error */
+ unsigned int nmask;
+ char *file = NULL;
+ int ifindex = 0;
+
+ rtnl_close(&rth);
+
+ while (argc > 0) {
+ if (matches(*argv, "file") == 0) {
+ NEXT_ARG();
+ file = *argv;
+ } else if (matches(*argv, "label") == 0) {
+ prefix_banner = 1;
+ } else if (matches(*argv, "link") == 0) {
+ lmask |= IPMON_LLINK;
+ } else if (matches(*argv, "address") == 0) {
+ lmask |= IPMON_LADDR;
+ } else if (matches(*argv, "route") == 0) {
+ lmask |= IPMON_LROUTE;
+ } else if (matches(*argv, "mroute") == 0) {
+ lmask |= IPMON_LMROUTE;
+ } else if (matches(*argv, "prefix") == 0) {
+ lmask |= IPMON_LPREFIX;
+ } else if (matches(*argv, "neigh") == 0) {
+ lmask |= IPMON_LNEIGH;
+ } else if (matches(*argv, "netconf") == 0) {
+ lmask |= IPMON_LNETCONF;
+ } else if (matches(*argv, "rule") == 0) {
+ lmask |= IPMON_LRULE;
+ } else if (matches(*argv, "nsid") == 0) {
+ lmask |= IPMON_LNSID;
+ } else if (matches(*argv, "nexthop") == 0) {
+ lmask |= IPMON_LNEXTHOP;
+ } else if (strcmp(*argv, "stats") == 0) {
+ lmask |= IPMON_LSTATS;
+ } else if (strcmp(*argv, "all") == 0) {
+ prefix_banner = 1;
+ } else if (matches(*argv, "all-nsid") == 0) {
+ listen_all_nsid = 1;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+
+ ifindex = ll_name_to_index(*argv);
+ if (!ifindex)
+ invarg("Device does not exist\n", *argv);
+ } else {
+ fprintf(stderr, "Argument \"%s\" is unknown, try \"ip monitor help\".\n", *argv);
+ exit(-1);
+ }
+ argc--; argv++;
+ }
+
+ ipaddr_reset_filter(1, ifindex);
+ iproute_reset_filter(ifindex);
+ ipmroute_reset_filter(ifindex);
+ ipneigh_reset_filter(ifindex);
+ ipnetconf_reset_filter(ifindex);
+
+ nmask = lmask;
+ if (!lmask)
+ lmask = IPMON_L_ALL;
+
+ if (lmask & IPMON_LLINK)
+ groups |= nl_mgrp(RTNLGRP_LINK);
+ if (lmask & IPMON_LADDR) {
+ if (!preferred_family || preferred_family == AF_INET)
+ groups |= nl_mgrp(RTNLGRP_IPV4_IFADDR);
+ if (!preferred_family || preferred_family == AF_INET6)
+ groups |= nl_mgrp(RTNLGRP_IPV6_IFADDR);
+ }
+ if (lmask & IPMON_LROUTE) {
+ if (!preferred_family || preferred_family == AF_INET)
+ groups |= nl_mgrp(RTNLGRP_IPV4_ROUTE);
+ if (!preferred_family || preferred_family == AF_INET6)
+ groups |= nl_mgrp(RTNLGRP_IPV6_ROUTE);
+ if (!preferred_family || preferred_family == AF_MPLS)
+ groups |= nl_mgrp(RTNLGRP_MPLS_ROUTE);
+ }
+ if (lmask & IPMON_LMROUTE) {
+ if (!preferred_family || preferred_family == AF_INET)
+ groups |= nl_mgrp(RTNLGRP_IPV4_MROUTE);
+ if (!preferred_family || preferred_family == AF_INET6)
+ groups |= nl_mgrp(RTNLGRP_IPV6_MROUTE);
+ }
+ if (lmask & IPMON_LPREFIX) {
+ if (!preferred_family || preferred_family == AF_INET6)
+ groups |= nl_mgrp(RTNLGRP_IPV6_PREFIX);
+ }
+ if (lmask & IPMON_LNEIGH) {
+ groups |= nl_mgrp(RTNLGRP_NEIGH);
+ }
+ if (lmask & IPMON_LNETCONF) {
+ if (!preferred_family || preferred_family == AF_INET)
+ groups |= nl_mgrp(RTNLGRP_IPV4_NETCONF);
+ if (!preferred_family || preferred_family == AF_INET6)
+ groups |= nl_mgrp(RTNLGRP_IPV6_NETCONF);
+ if (!preferred_family || preferred_family == AF_MPLS)
+ groups |= nl_mgrp(RTNLGRP_MPLS_NETCONF);
+ }
+ if (lmask & IPMON_LRULE) {
+ if (!preferred_family || preferred_family == AF_INET)
+ groups |= nl_mgrp(RTNLGRP_IPV4_RULE);
+ if (!preferred_family || preferred_family == AF_INET6)
+ groups |= nl_mgrp(RTNLGRP_IPV6_RULE);
+ }
+ if (lmask & IPMON_LNSID) {
+ groups |= nl_mgrp(RTNLGRP_NSID);
+ }
+
+ if (file) {
+ FILE *fp;
+ int err;
+
+ fp = fopen(file, "r");
+ if (fp == NULL) {
+ perror("Cannot fopen");
+ exit(-1);
+ }
+ err = rtnl_from_file(fp, accept_msg, stdout);
+ fclose(fp);
+ return err;
+ }
+
+ if (rtnl_open(&rth, groups) < 0)
+ exit(1);
+
+ if (lmask & IPMON_LNEXTHOP &&
+ rtnl_add_nl_group(&rth, RTNLGRP_NEXTHOP) < 0) {
+ fprintf(stderr, "Failed to add nexthop group to list\n");
+ exit(1);
+ }
+
+ if (lmask & IPMON_LSTATS &&
+ rtnl_add_nl_group(&rth, RTNLGRP_STATS) < 0 &&
+ nmask & IPMON_LSTATS) {
+ fprintf(stderr, "Failed to add stats group to list\n");
+ exit(1);
+ }
+
+ if (listen_all_nsid && rtnl_listen_all_nsid(&rth) < 0)
+ exit(1);
+
+ ll_init_map(&rth);
+ netns_nsid_socket_init();
+ netns_map_init();
+
+ if (rtnl_listen(&rth, accept_msg, stdout) < 0)
+ exit(2);
+
+ return 0;
+}
diff --git a/ip/ipmptcp.c b/ip/ipmptcp.c
new file mode 100644
index 0000000..9847f95
--- /dev/null
+++ b/ip/ipmptcp.c
@@ -0,0 +1,615 @@
+// 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");
+ }
+
+ 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);
+}
diff --git a/ip/ipmroute.c b/ip/ipmroute.c
new file mode 100644
index 0000000..b6d9e61
--- /dev/null
+++ b/ip/ipmroute.c
@@ -0,0 +1,325 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * ipmroute.c "ip mroute".
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include <linux/netdevice.h>
+#include <linux/if.h>
+#include <linux/if_arp.h>
+#include <linux/sockios.h>
+
+#include <rt_names.h>
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip mroute show [ [ to ] PREFIX ] [ from PREFIX ] [ iif DEVICE ]\n"
+ " [ table TABLE_ID ]\n"
+ "TABLE_ID := [ local | main | default | all | NUMBER ]\n"
+ );
+ exit(-1);
+}
+
+static struct rtfilter {
+ int tb;
+ int af;
+ int iif;
+ inet_prefix mdst;
+ inet_prefix msrc;
+} filter;
+
+int print_mroute(struct nlmsghdr *n, void *arg)
+{
+ struct rtmsg *r = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *tb[RTA_MAX+1];
+ FILE *fp = arg;
+ const char *src, *dst;
+ SPRINT_BUF(b1);
+ SPRINT_BUF(b2);
+ __u32 table;
+ int iif = 0;
+ int family;
+
+ if ((n->nlmsg_type != RTM_NEWROUTE &&
+ n->nlmsg_type != RTM_DELROUTE)) {
+ fprintf(stderr, "Not a multicast route: %08x %08x %08x\n",
+ n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+ return 0;
+ }
+ len -= NLMSG_LENGTH(sizeof(*r));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ if (r->rtm_type != RTN_MULTICAST) {
+ fprintf(stderr,
+ "Non multicast route received, kernel does support IP multicast?\n");
+ return -1;
+ }
+
+ parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
+ table = rtm_get_table(r, tb);
+
+ if (filter.tb > 0 && filter.tb != table)
+ return 0;
+
+ if (tb[RTA_IIF])
+ iif = rta_getattr_u32(tb[RTA_IIF]);
+ if (filter.iif && filter.iif != iif)
+ return 0;
+
+ if (filter.af && filter.af != r->rtm_family)
+ return 0;
+
+ if (inet_addr_match_rta(&filter.mdst, tb[RTA_DST]))
+ return 0;
+
+ if (inet_addr_match_rta(&filter.msrc, tb[RTA_SRC]))
+ return 0;
+
+ family = get_real_family(r->rtm_type, r->rtm_family);
+
+ open_json_object(NULL);
+ if (n->nlmsg_type == RTM_DELROUTE)
+ print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+ if (tb[RTA_SRC])
+ src = rt_addr_n2a_r(family, RTA_PAYLOAD(tb[RTA_SRC]),
+ RTA_DATA(tb[RTA_SRC]), b1, sizeof(b1));
+ else
+ src = "unknown";
+
+ if (tb[RTA_DST])
+ dst = rt_addr_n2a_r(family, RTA_PAYLOAD(tb[RTA_DST]),
+ RTA_DATA(tb[RTA_DST]), b2, sizeof(b2));
+ else
+ dst = "unknown";
+
+ if (is_json_context()) {
+ print_string(PRINT_JSON, "src", NULL, src);
+ print_string(PRINT_JSON, "dst", NULL, dst);
+ } else {
+ char obuf[256];
+
+ snprintf(obuf, sizeof(obuf), "(%s,%s)", src, dst);
+ print_string(PRINT_FP, NULL,
+ "%-32s Iif: ", obuf);
+ }
+
+ if (iif)
+ print_color_string(PRINT_ANY, COLOR_IFNAME,
+ "iif", "%-10s ", ll_index_to_name(iif));
+ else
+ print_string(PRINT_ANY,"iif", "%s ", "unresolved");
+
+ if (tb[RTA_MULTIPATH]) {
+ struct rtnexthop *nh = RTA_DATA(tb[RTA_MULTIPATH]);
+ int first = 1;
+
+ open_json_array(PRINT_JSON, "multipath");
+ len = RTA_PAYLOAD(tb[RTA_MULTIPATH]);
+
+ for (;;) {
+ if (len < sizeof(*nh))
+ break;
+ if (nh->rtnh_len > len)
+ break;
+
+ open_json_object(NULL);
+ if (first) {
+ print_string(PRINT_FP, NULL, "Oifs: ", NULL);
+ first = 0;
+ }
+
+ print_color_string(PRINT_ANY, COLOR_IFNAME,
+ "oif", "%s", ll_index_to_name(nh->rtnh_ifindex));
+
+ if (nh->rtnh_hops > 1)
+ print_uint(PRINT_ANY,
+ "ttl", "(ttl %u) ", nh->rtnh_hops);
+ else
+ print_string(PRINT_FP, NULL, " ", NULL);
+
+ close_json_object();
+ len -= NLMSG_ALIGN(nh->rtnh_len);
+ nh = RTNH_NEXT(nh);
+ }
+ close_json_array(PRINT_JSON, NULL);
+ }
+
+ print_string(PRINT_ANY, "state", " State: %s",
+ (r->rtm_flags & RTNH_F_UNRESOLVED) ? "unresolved" : "resolved");
+
+ if (r->rtm_flags & RTNH_F_OFFLOAD)
+ print_null(PRINT_ANY, "offload", " offload", NULL);
+
+ if (show_stats && tb[RTA_MFC_STATS]) {
+ struct rta_mfc_stats *mfcs = RTA_DATA(tb[RTA_MFC_STATS]);
+
+ print_nl();
+ print_u64(PRINT_ANY, "packets", " %"PRIu64" packets,",
+ mfcs->mfcs_packets);
+ print_u64(PRINT_ANY, "bytes", " %"PRIu64" bytes", mfcs->mfcs_bytes);
+
+ if (mfcs->mfcs_wrong_if)
+ print_u64(PRINT_ANY, "wrong_if",
+ ", %"PRIu64" arrived on wrong iif.",
+ mfcs->mfcs_wrong_if);
+ }
+
+ if (show_stats && tb[RTA_EXPIRES]) {
+ struct timeval tv;
+ double age;
+
+ __jiffies_to_tv(&tv, rta_getattr_u64(tb[RTA_EXPIRES]));
+ age = tv.tv_sec;
+ age += tv.tv_usec / 1000000.;
+ print_float(PRINT_ANY, "expires",
+ ", Age %.2f", age);
+ }
+
+ if (table && (table != RT_TABLE_MAIN || show_details > 0) && !filter.tb)
+ print_string(PRINT_ANY, "table", " Table: %s",
+ rtnl_rttable_n2a(table, b1, sizeof(b1)));
+
+ print_string(PRINT_FP, NULL, "\n", NULL);
+ close_json_object();
+ fflush(fp);
+ return 0;
+}
+
+void ipmroute_reset_filter(int ifindex)
+{
+ memset(&filter, 0, sizeof(filter));
+ filter.mdst.bitlen = -1;
+ filter.msrc.bitlen = -1;
+ filter.iif = ifindex;
+}
+
+static int iproute_dump_filter(struct nlmsghdr *nlh, int reqlen)
+{
+ int err;
+
+ if (filter.tb) {
+ err = addattr32(nlh, reqlen, RTA_TABLE, filter.tb);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int mroute_list(int argc, char **argv)
+{
+ char *id = NULL;
+ int family = preferred_family;
+
+ ipmroute_reset_filter(0);
+ if (family == AF_INET || family == AF_UNSPEC) {
+ family = RTNL_FAMILY_IPMR;
+ filter.af = RTNL_FAMILY_IPMR;
+ filter.tb = RT_TABLE_DEFAULT; /* for backward compatibility */
+ } else if (family == AF_INET6) {
+ family = RTNL_FAMILY_IP6MR;
+ filter.af = RTNL_FAMILY_IP6MR;
+ } else {
+ /* family does not have multicast routing */
+ return 0;
+ }
+
+ filter.msrc.family = filter.mdst.family = family;
+
+ while (argc > 0) {
+ if (matches(*argv, "table") == 0) {
+ __u32 tid;
+
+ NEXT_ARG();
+ if (rtnl_rttable_a2n(&tid, *argv)) {
+ if (strcmp(*argv, "all") == 0) {
+ filter.tb = 0;
+ } else if (strcmp(*argv, "help") == 0) {
+ usage();
+ } else {
+ invarg("table id value is invalid\n", *argv);
+ }
+ } else
+ filter.tb = tid;
+ } else if (strcmp(*argv, "iif") == 0) {
+ NEXT_ARG();
+ id = *argv;
+ } else if (matches(*argv, "from") == 0) {
+ NEXT_ARG();
+ if (get_prefix(&filter.msrc, *argv, family))
+ invarg("from value is invalid\n", *argv);
+ } else {
+ if (strcmp(*argv, "to") == 0) {
+ NEXT_ARG();
+ }
+ if (matches(*argv, "help") == 0)
+ usage();
+ if (get_prefix(&filter.mdst, *argv, family))
+ invarg("to value is invalid\n", *argv);
+ }
+ argc--; argv++;
+ }
+
+ ll_init_map(&rth);
+
+ if (id) {
+ int idx;
+
+ idx = ll_name_to_index(id);
+ if (!idx)
+ return nodev(id);
+ filter.iif = idx;
+ }
+
+ if (rtnl_routedump_req(&rth, filter.af, iproute_dump_filter) < 0) {
+ perror("Cannot send dump request");
+ return 1;
+ }
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&rth, print_mroute, stdout) < 0) {
+ delete_json_obj();
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ delete_json_obj();
+
+ return 0;
+}
+
+int do_multiroute(int argc, char **argv)
+{
+ if (argc < 1)
+ return mroute_list(0, NULL);
+
+ if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
+ || matches(*argv, "lst") == 0)
+ return mroute_list(argc-1, argv+1);
+ if (matches(*argv, "help") == 0)
+ usage();
+ fprintf(stderr, "Command \"%s\" is unknown, try \"ip mroute help\".\n", *argv);
+ exit(-1);
+}
diff --git a/ip/ipneigh.c b/ip/ipneigh.c
new file mode 100644
index 0000000..ee14ffc
--- /dev/null
+++ b/ip/ipneigh.c
@@ -0,0 +1,767 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * ipneigh.c "ip neigh".
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+#define NUD_VALID (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE|NUD_PROBE|NUD_STALE|NUD_DELAY)
+#define MAX_ROUNDS 10
+
+static struct
+{
+ int family;
+ int index;
+ int state;
+ int unused_only;
+ inet_prefix pfx;
+ int flushed;
+ char *flushb;
+ int flushp;
+ int flushe;
+ int master;
+ int protocol;
+ __u8 ndm_flags;
+} filter;
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip neigh { add | del | change | replace }\n"
+ " { ADDR [ lladdr LLADDR ] [ nud STATE ] proxy ADDR }\n"
+ " [ dev DEV ] [ router ] [ use ] [ managed ] [ extern_learn ]\n"
+ " [ protocol PROTO ]\n"
+ "\n"
+ " ip neigh { show | flush } [ proxy ] [ to PREFIX ] [ dev DEV ] [ nud STATE ]\n"
+ " [ vrf NAME ] [ nomaster ]\n"
+ " ip neigh get { ADDR | proxy ADDR } dev DEV\n"
+ "\n"
+ "STATE := { delay | failed | incomplete | noarp | none |\n"
+ " permanent | probe | reachable | stale }\n");
+ exit(-1);
+}
+
+static int nud_state_a2n(unsigned int *state, const char *arg)
+{
+ if (matches(arg, "permanent") == 0)
+ *state = NUD_PERMANENT;
+ else if (matches(arg, "reachable") == 0)
+ *state = NUD_REACHABLE;
+ else if (strcmp(arg, "noarp") == 0)
+ *state = NUD_NOARP;
+ else if (strcmp(arg, "none") == 0)
+ *state = NUD_NONE;
+ else if (strcmp(arg, "stale") == 0)
+ *state = NUD_STALE;
+ else if (strcmp(arg, "incomplete") == 0)
+ *state = NUD_INCOMPLETE;
+ else if (strcmp(arg, "delay") == 0)
+ *state = NUD_DELAY;
+ else if (strcmp(arg, "probe") == 0)
+ *state = NUD_PROBE;
+ else if (matches(arg, "failed") == 0)
+ *state = NUD_FAILED;
+ else {
+ if (get_unsigned(state, arg, 0))
+ return -1;
+ if (*state >= 0x100 || (*state&((*state)-1)))
+ return -1;
+ }
+ return 0;
+}
+
+static int flush_update(void)
+{
+ if (rtnl_send_check(&rth, filter.flushb, filter.flushp) < 0) {
+ perror("Failed to send flush request");
+ return -1;
+ }
+ filter.flushp = 0;
+ return 0;
+}
+
+
+static int ipneigh_modify(int cmd, int flags, int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct ndmsg ndm;
+ char buf[256];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST | flags,
+ .n.nlmsg_type = cmd,
+ .ndm.ndm_family = preferred_family,
+ .ndm.ndm_state = NUD_PERMANENT,
+ };
+ __u32 ext_flags = 0;
+ char *dev = NULL;
+ int dst_ok = 0;
+ int dev_ok = 0;
+ int lladdr_ok = 0;
+ char *lla = NULL;
+ inet_prefix dst;
+
+ while (argc > 0) {
+ if (matches(*argv, "lladdr") == 0) {
+ NEXT_ARG();
+ if (lladdr_ok)
+ duparg("lladdr", *argv);
+ lla = *argv;
+ lladdr_ok = 1;
+ } else if (strcmp(*argv, "nud") == 0) {
+ unsigned int state;
+
+ NEXT_ARG();
+ if (nud_state_a2n(&state, *argv))
+ invarg("nud state is bad", *argv);
+ req.ndm.ndm_state = state;
+ } else if (matches(*argv, "proxy") == 0) {
+ NEXT_ARG();
+ if (matches(*argv, "help") == 0)
+ usage();
+ if (dst_ok)
+ duparg("address", *argv);
+ get_addr(&dst, *argv, preferred_family);
+ dst_ok = 1;
+ dev_ok = 1;
+ req.ndm.ndm_flags |= NTF_PROXY;
+ } else if (strcmp(*argv, "router") == 0) {
+ req.ndm.ndm_flags |= NTF_ROUTER;
+ } else if (strcmp(*argv, "use") == 0) {
+ req.ndm.ndm_flags |= NTF_USE;
+ } else if (strcmp(*argv, "managed") == 0) {
+ ext_flags |= NTF_EXT_MANAGED;
+ req.ndm.ndm_state = NUD_NONE;
+ } else if (matches(*argv, "extern_learn") == 0) {
+ req.ndm.ndm_flags |= NTF_EXT_LEARNED;
+ } else if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ dev = *argv;
+ dev_ok = 1;
+ } else if (matches(*argv, "protocol") == 0) {
+ __u32 proto;
+
+ NEXT_ARG();
+ if (rtnl_rtprot_a2n(&proto, *argv))
+ invarg("\"protocol\" value is invalid\n", *argv);
+ if (addattr8(&req.n, sizeof(req), NDA_PROTOCOL, proto))
+ return -1;
+ } else {
+ if (strcmp(*argv, "to") == 0) {
+ NEXT_ARG();
+ }
+ if (matches(*argv, "help") == 0) {
+ NEXT_ARG();
+ }
+ if (dst_ok)
+ duparg2("to", *argv);
+ get_addr(&dst, *argv, preferred_family);
+ dst_ok = 1;
+ }
+ argc--; argv++;
+ }
+ if (!dev_ok || !dst_ok || dst.family == AF_UNSPEC) {
+ fprintf(stderr, "Device and destination are required arguments.\n");
+ exit(-1);
+ }
+ req.ndm.ndm_family = dst.family;
+ if (addattr_l(&req.n, sizeof(req), NDA_DST, &dst.data, dst.bytelen) < 0)
+ return -1;
+ if (ext_flags &&
+ addattr_l(&req.n, sizeof(req), NDA_FLAGS_EXT, &ext_flags,
+ sizeof(ext_flags)) < 0)
+ return -1;
+ if (lla && strcmp(lla, "null")) {
+ char llabuf[20];
+ int l;
+
+ l = ll_addr_a2n(llabuf, sizeof(llabuf), lla);
+ if (l < 0)
+ return -1;
+
+ if (addattr_l(&req.n, sizeof(req), NDA_LLADDR, llabuf, l) < 0)
+ return -1;
+ }
+
+ ll_init_map(&rth);
+
+ if (dev) {
+ req.ndm.ndm_ifindex = ll_name_to_index(dev);
+ if (!req.ndm.ndm_ifindex)
+ return nodev(dev);
+ }
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ exit(2);
+
+ return 0;
+}
+
+static void print_cacheinfo(const struct nda_cacheinfo *ci)
+{
+ static int hz;
+
+ if (!hz)
+ hz = get_user_hz();
+
+ if (ci->ndm_refcnt)
+ print_uint(PRINT_ANY, "refcnt",
+ " ref %u", ci->ndm_refcnt);
+
+ print_uint(PRINT_ANY, "used", " used %u", ci->ndm_used / hz);
+ print_uint(PRINT_ANY, "confirmed", "/%u", ci->ndm_confirmed / hz);
+ print_uint(PRINT_ANY, "updated", "/%u", ci->ndm_updated / hz);
+}
+
+static void print_neigh_state(unsigned int nud)
+{
+
+ open_json_array(PRINT_JSON,
+ is_json_context() ? "state" : "");
+
+#define PRINT_FLAG(f) \
+ if (nud & NUD_##f) { \
+ nud &= ~NUD_##f; \
+ print_string(PRINT_ANY, NULL, "%s ", #f); \
+ }
+
+ PRINT_FLAG(INCOMPLETE);
+ PRINT_FLAG(REACHABLE);
+ PRINT_FLAG(STALE);
+ PRINT_FLAG(DELAY);
+ PRINT_FLAG(PROBE);
+ PRINT_FLAG(FAILED);
+ PRINT_FLAG(NOARP);
+ PRINT_FLAG(PERMANENT);
+#undef PRINT_FLAG
+
+ close_json_array(PRINT_JSON, NULL);
+}
+
+static int print_neigh_brief(FILE *fp, struct ndmsg *r, struct rtattr *tb[])
+{
+ if (tb[NDA_DST]) {
+ const char *dst;
+ int family = r->ndm_family;
+
+ if (family == AF_BRIDGE) {
+ if (RTA_PAYLOAD(tb[NDA_DST]) == sizeof(struct in6_addr))
+ family = AF_INET6;
+ else
+ family = AF_INET;
+ }
+
+ dst = format_host_rta(family, tb[NDA_DST]);
+ print_color_string(PRINT_ANY, ifa_family_color(family),
+ "dst", "%-39s ", dst);
+ }
+
+ if (!filter.index && r->ndm_ifindex) {
+ print_color_string(PRINT_ANY, COLOR_IFNAME,
+ "dev", "%-16s ",
+ ll_index_to_name(r->ndm_ifindex));
+ }
+
+ if (tb[NDA_LLADDR]) {
+ const char *lladdr;
+
+ SPRINT_BUF(b1);
+
+ lladdr = ll_addr_n2a(RTA_DATA(tb[NDA_LLADDR]),
+ RTA_PAYLOAD(tb[NDA_LLADDR]),
+ ll_index_to_type(r->ndm_ifindex),
+ b1, sizeof(b1));
+
+ print_color_string(PRINT_ANY, COLOR_MAC,
+ "lladdr", "%s", lladdr);
+ }
+
+ print_string(PRINT_FP, NULL, "%s", "\n");
+ close_json_object();
+ fflush(fp);
+
+ return 0;
+}
+
+int print_neigh(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+ struct ndmsg *r = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *tb[NDA_MAX+1];
+ static int logit = 1;
+ __u32 ext_flags = 0;
+ __u8 protocol = 0;
+
+ if (n->nlmsg_type != RTM_NEWNEIGH && n->nlmsg_type != RTM_DELNEIGH &&
+ n->nlmsg_type != RTM_GETNEIGH) {
+ fprintf(stderr, "Not RTM_NEWNEIGH: %08x %08x %08x\n",
+ n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+
+ return 0;
+ }
+ len -= NLMSG_LENGTH(sizeof(*r));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ if (filter.flushb && n->nlmsg_type != RTM_NEWNEIGH)
+ return 0;
+
+ if (filter.family && filter.family != r->ndm_family)
+ return 0;
+ if (filter.index && filter.index != r->ndm_ifindex)
+ return 0;
+ if (!(filter.state&r->ndm_state) &&
+ !(r->ndm_flags & NTF_PROXY) &&
+ !(r->ndm_flags & NTF_EXT_LEARNED) &&
+ (r->ndm_state || !(filter.state&0x100)))
+ return 0;
+
+ if (filter.master && !(n->nlmsg_flags & NLM_F_DUMP_FILTERED)) {
+ if (logit) {
+ logit = 0;
+ fprintf(fp,
+ "\nWARNING: Kernel does not support filtering by master device\n\n");
+ }
+ }
+
+ parse_rtattr(tb, NDA_MAX, NDA_RTA(r), n->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
+
+ if (inet_addr_match_rta(&filter.pfx, tb[NDA_DST]))
+ return 0;
+
+ if (tb[NDA_PROTOCOL])
+ protocol = rta_getattr_u8(tb[NDA_PROTOCOL]);
+ if (tb[NDA_FLAGS_EXT])
+ ext_flags = rta_getattr_u32(tb[NDA_FLAGS_EXT]);
+
+ if (filter.protocol && filter.protocol != protocol)
+ return 0;
+
+ if (filter.unused_only && tb[NDA_CACHEINFO]) {
+ struct nda_cacheinfo *ci = RTA_DATA(tb[NDA_CACHEINFO]);
+
+ if (ci->ndm_refcnt)
+ return 0;
+ }
+
+ if (filter.flushb) {
+ struct nlmsghdr *fn;
+
+ if (NLMSG_ALIGN(filter.flushp) + n->nlmsg_len > filter.flushe) {
+ if (flush_update())
+ return -1;
+ }
+ fn = (struct nlmsghdr *)(filter.flushb + NLMSG_ALIGN(filter.flushp));
+ memcpy(fn, n, n->nlmsg_len);
+ fn->nlmsg_type = RTM_DELNEIGH;
+ fn->nlmsg_flags = NLM_F_REQUEST;
+ fn->nlmsg_seq = ++rth.seq;
+ filter.flushp = (((char *)fn) + n->nlmsg_len) - filter.flushb;
+ filter.flushed++;
+ if (show_stats < 2)
+ return 0;
+ }
+
+ open_json_object(NULL);
+ if (n->nlmsg_type == RTM_DELNEIGH)
+ print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+ else if (n->nlmsg_type == RTM_GETNEIGH)
+ print_null(PRINT_ANY, "miss", "%s ", "miss");
+
+ if (brief)
+ return print_neigh_brief(fp, r, tb);
+
+ if (tb[NDA_DST]) {
+ const char *dst;
+ int family = r->ndm_family;
+
+ if (family == AF_BRIDGE) {
+ if (RTA_PAYLOAD(tb[NDA_DST]) == sizeof(struct in6_addr))
+ family = AF_INET6;
+ else
+ family = AF_INET;
+ }
+
+ dst = format_host_rta(family, tb[NDA_DST]);
+ print_color_string(PRINT_ANY,
+ ifa_family_color(family),
+ "dst", "%s ", dst);
+ }
+
+ if (!filter.index && r->ndm_ifindex) {
+ if (!is_json_context())
+ fprintf(fp, "dev ");
+
+ print_color_string(PRINT_ANY, COLOR_IFNAME,
+ "dev", "%s ",
+ ll_index_to_name(r->ndm_ifindex));
+ }
+
+ if (tb[NDA_LLADDR]) {
+ const char *lladdr;
+ SPRINT_BUF(b1);
+
+ lladdr = ll_addr_n2a(RTA_DATA(tb[NDA_LLADDR]),
+ RTA_PAYLOAD(tb[NDA_LLADDR]),
+ ll_index_to_type(r->ndm_ifindex),
+ b1, sizeof(b1));
+
+ if (!is_json_context())
+ fprintf(fp, "lladdr ");
+
+ print_color_string(PRINT_ANY, COLOR_MAC,
+ "lladdr", "%s ", lladdr);
+ }
+
+ if (r->ndm_flags & NTF_ROUTER)
+ print_null(PRINT_ANY, "router", "%s ", "router");
+ if (r->ndm_flags & NTF_PROXY)
+ print_null(PRINT_ANY, "proxy", "%s ", "proxy");
+ if (ext_flags & NTF_EXT_MANAGED)
+ print_null(PRINT_ANY, "managed", "%s ", "managed");
+ if (r->ndm_flags & NTF_EXT_LEARNED)
+ print_null(PRINT_ANY, "extern_learn", "%s ", "extern_learn");
+ if (r->ndm_flags & NTF_OFFLOADED)
+ print_null(PRINT_ANY, "offload", "%s ", "offload");
+
+ if (show_stats) {
+ if (tb[NDA_CACHEINFO])
+ print_cacheinfo(RTA_DATA(tb[NDA_CACHEINFO]));
+
+ if (tb[NDA_PROBES])
+ print_uint(PRINT_ANY, "probes", "probes %u ",
+ rta_getattr_u32(tb[NDA_PROBES]));
+ }
+
+ if (r->ndm_state)
+ print_neigh_state(r->ndm_state);
+
+ if (protocol) {
+ SPRINT_BUF(b1);
+
+ print_string(PRINT_ANY, "protocol", "proto %s ",
+ rtnl_rtprot_n2a(protocol, b1, sizeof(b1)));
+ }
+
+ print_string(PRINT_FP, NULL, "\n", "");
+ close_json_object();
+ fflush(fp);
+
+ return 0;
+}
+
+void ipneigh_reset_filter(int ifindex)
+{
+ memset(&filter, 0, sizeof(filter));
+ filter.state = ~0;
+ filter.index = ifindex;
+}
+
+static int ipneigh_dump_filter(struct nlmsghdr *nlh, int reqlen)
+{
+ struct ndmsg *ndm = NLMSG_DATA(nlh);
+ int err;
+
+ ndm->ndm_flags = filter.ndm_flags;
+
+ if (filter.index) {
+ err = addattr32(nlh, reqlen, NDA_IFINDEX, filter.index);
+ if (err)
+ return err;
+ }
+ if (filter.master) {
+ err = addattr32(nlh, reqlen, NDA_MASTER, filter.master);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int do_show_or_flush(int argc, char **argv, int flush)
+{
+ char *filter_dev = NULL;
+ int state_given = 0;
+
+ ipneigh_reset_filter(0);
+
+ if (!filter.family)
+ filter.family = preferred_family;
+
+ if (flush) {
+ if (argc <= 0) {
+ fprintf(stderr, "Flush requires arguments.\n");
+ return -1;
+ }
+ filter.state = ~(NUD_PERMANENT|NUD_NOARP);
+ } else
+ filter.state = 0xFF & ~NUD_NOARP;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (filter_dev)
+ duparg("dev", *argv);
+ filter_dev = *argv;
+ } else if (strcmp(*argv, "master") == 0) {
+ int ifindex;
+
+ NEXT_ARG();
+ ifindex = ll_name_to_index(*argv);
+ if (!ifindex)
+ invarg("Device does not exist\n", *argv);
+ filter.master = ifindex;
+ } else if (strcmp(*argv, "vrf") == 0) {
+ int ifindex;
+
+ NEXT_ARG();
+ ifindex = ll_name_to_index(*argv);
+ if (!ifindex)
+ invarg("Not a valid VRF name\n", *argv);
+ if (!name_is_vrf(*argv))
+ invarg("Not a valid VRF name\n", *argv);
+ filter.master = ifindex;
+ } else if (strcmp(*argv, "nomaster") == 0) {
+ filter.master = -1;
+ } else if (strcmp(*argv, "unused") == 0) {
+ filter.unused_only = 1;
+ } else if (strcmp(*argv, "nud") == 0) {
+ unsigned int state;
+
+ NEXT_ARG();
+ if (!state_given) {
+ state_given = 1;
+ filter.state = 0;
+ }
+ if (nud_state_a2n(&state, *argv)) {
+ if (strcmp(*argv, "all") != 0)
+ invarg("nud state is bad", *argv);
+ state = ~0;
+ if (flush)
+ state &= ~NUD_NOARP;
+ }
+ if (state == 0)
+ state = 0x100;
+ filter.state |= state;
+ } else if (strcmp(*argv, "proxy") == 0) {
+ filter.ndm_flags = NTF_PROXY;
+ } else if (matches(*argv, "protocol") == 0) {
+ __u32 prot;
+
+ NEXT_ARG();
+ if (rtnl_rtprot_a2n(&prot, *argv)) {
+ if (strcmp(*argv, "all"))
+ invarg("invalid \"protocol\"\n", *argv);
+ prot = 0;
+ }
+ filter.protocol = prot;
+ } else {
+ if (strcmp(*argv, "to") == 0) {
+ NEXT_ARG();
+ }
+ if (matches(*argv, "help") == 0)
+ usage();
+ if (get_prefix(&filter.pfx, *argv, filter.family))
+ invarg("to value is invalid\n", *argv);
+ if (filter.family == AF_UNSPEC)
+ filter.family = filter.pfx.family;
+ }
+ argc--; argv++;
+ }
+
+ ll_init_map(&rth);
+
+ if (filter_dev) {
+ filter.index = ll_name_to_index(filter_dev);
+ if (!filter.index)
+ return nodev(filter_dev);
+ }
+
+ if (flush) {
+ int round = 0;
+ char flushb[4096-512];
+
+ filter.flushb = flushb;
+ filter.flushp = 0;
+ filter.flushe = sizeof(flushb);
+
+ while (round < MAX_ROUNDS) {
+ if (rtnl_neighdump_req(&rth, filter.family,
+ ipneigh_dump_filter) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+ filter.flushed = 0;
+ if (rtnl_dump_filter(&rth, print_neigh, stdout) < 0) {
+ fprintf(stderr, "Flush terminated\n");
+ exit(1);
+ }
+ if (filter.flushed == 0) {
+ if (show_stats) {
+ if (round == 0)
+ printf("Nothing to flush.\n");
+ else
+ printf("*** Flush is complete after %d round%s ***\n", round, round > 1?"s":"");
+ }
+ fflush(stdout);
+ return 0;
+ }
+ round++;
+ if (flush_update() < 0)
+ exit(1);
+ if (show_stats) {
+ printf("\n*** Round %d, deleting %d entries ***\n", round, filter.flushed);
+ fflush(stdout);
+ }
+ filter.state &= ~NUD_FAILED;
+ }
+ printf("*** Flush not complete bailing out after %d rounds\n",
+ MAX_ROUNDS);
+ return 1;
+ }
+
+ if (rtnl_neighdump_req(&rth, filter.family, ipneigh_dump_filter) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&rth, print_neigh, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ delete_json_obj();
+
+ return 0;
+}
+
+static int ipneigh_get(int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct ndmsg ndm;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_GETNEIGH,
+ .ndm.ndm_family = preferred_family,
+ };
+ struct nlmsghdr *answer;
+ char *d = NULL;
+ int dst_ok = 0;
+ int dev_ok = 0;
+ inet_prefix dst;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ d = *argv;
+ dev_ok = 1;
+ } else if (matches(*argv, "proxy") == 0) {
+ NEXT_ARG();
+ if (matches(*argv, "help") == 0)
+ usage();
+ if (dst_ok)
+ duparg("address", *argv);
+ get_addr(&dst, *argv, preferred_family);
+ dst_ok = 1;
+ dev_ok = 1;
+ req.ndm.ndm_flags |= NTF_PROXY;
+ } else {
+ if (strcmp(*argv, "to") == 0)
+ NEXT_ARG();
+
+ if (matches(*argv, "help") == 0)
+ usage();
+ if (dst_ok)
+ duparg2("to", *argv);
+ get_addr(&dst, *argv, preferred_family);
+ dst_ok = 1;
+ }
+ argc--; argv++;
+ }
+
+ if (!dev_ok || !dst_ok || dst.family == AF_UNSPEC) {
+ fprintf(stderr, "Device and address are required arguments.\n");
+ return -1;
+ }
+
+ req.ndm.ndm_family = dst.family;
+ if (addattr_l(&req.n, sizeof(req), NDA_DST, &dst.data, dst.bytelen) < 0)
+ return -1;
+
+ if (d) {
+ req.ndm.ndm_ifindex = ll_name_to_index(d);
+ if (!req.ndm.ndm_ifindex) {
+ fprintf(stderr, "Cannot find device \"%s\"\n", d);
+ return -1;
+ }
+ }
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0)
+ return -2;
+
+ ipneigh_reset_filter(0);
+ new_json_obj(json);
+ if (print_neigh(answer, stdout) < 0) {
+ fprintf(stderr, "An error :-)\n");
+ free(answer);
+ delete_json_obj();
+ return -1;
+ }
+ free(answer);
+ delete_json_obj();
+
+ return 0;
+}
+
+int do_ipneigh(int argc, char **argv)
+{
+ if (argc > 0) {
+ if (matches(*argv, "add") == 0)
+ return ipneigh_modify(RTM_NEWNEIGH, NLM_F_CREATE|NLM_F_EXCL, argc-1, argv+1);
+ if (matches(*argv, "change") == 0 ||
+ strcmp(*argv, "chg") == 0)
+ return ipneigh_modify(RTM_NEWNEIGH, NLM_F_REPLACE, argc-1, argv+1);
+ if (matches(*argv, "replace") == 0)
+ return ipneigh_modify(RTM_NEWNEIGH, NLM_F_CREATE|NLM_F_REPLACE, argc-1, argv+1);
+ if (matches(*argv, "delete") == 0)
+ return ipneigh_modify(RTM_DELNEIGH, 0, argc-1, argv+1);
+ if (matches(*argv, "get") == 0)
+ return ipneigh_get(argc-1, argv+1);
+ if (matches(*argv, "show") == 0 ||
+ matches(*argv, "lst") == 0 ||
+ matches(*argv, "list") == 0)
+ return do_show_or_flush(argc-1, argv+1, 0);
+ if (matches(*argv, "flush") == 0)
+ return do_show_or_flush(argc-1, argv+1, 1);
+ if (matches(*argv, "help") == 0)
+ usage();
+ } else
+ return do_show_or_flush(0, NULL, 0);
+
+ fprintf(stderr, "Command \"%s\" is unknown, try \"ip neigh help\".\n", *argv);
+ exit(-1);
+}
diff --git a/ip/ipnetconf.c b/ip/ipnetconf.c
new file mode 100644
index 0000000..a0c7e05
--- /dev/null
+++ b/ip/ipnetconf.c
@@ -0,0 +1,247 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * ipnetconf.c "ip netconf".
+ *
+ * Authors: Nicolas Dichtel, <nicolas.dichtel@6wind.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <errno.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static struct {
+ int family;
+ int ifindex;
+} filter;
+
+static const char * const rp_filter_names[] = {
+ "off", "strict", "loose"
+};
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr, "Usage: ip netconf show [ dev STRING ]\n");
+ exit(-1);
+}
+
+static struct rtattr *netconf_rta(struct netconfmsg *ncm)
+{
+ return (struct rtattr *)((char *)ncm
+ + NLMSG_ALIGN(sizeof(struct netconfmsg)));
+}
+
+int print_netconf(struct rtnl_ctrl_data *ctrl, struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+ struct netconfmsg *ncm = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *tb[NETCONFA_MAX+1];
+ int ifindex = 0;
+
+ if (n->nlmsg_type == NLMSG_ERROR)
+ return -1;
+
+ if (n->nlmsg_type != RTM_NEWNETCONF &&
+ n->nlmsg_type != RTM_DELNETCONF) {
+ fprintf(stderr, "Not a netconf message: %08x %08x %08x\n",
+ n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+
+ return -1;
+ }
+ len -= NLMSG_SPACE(sizeof(*ncm));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ if (filter.family && filter.family != ncm->ncm_family)
+ return 0;
+
+ parse_rtattr(tb, NETCONFA_MAX, netconf_rta(ncm),
+ NLMSG_PAYLOAD(n, sizeof(*ncm)));
+
+ if (tb[NETCONFA_IFINDEX])
+ ifindex = rta_getattr_u32(tb[NETCONFA_IFINDEX]);
+
+ if (filter.ifindex && filter.ifindex != ifindex)
+ return 0;
+
+ open_json_object(NULL);
+ if (n->nlmsg_type == RTM_DELNETCONF)
+ print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+ print_string(PRINT_ANY, "family",
+ "%s ", family_name(ncm->ncm_family));
+
+ if (tb[NETCONFA_IFINDEX]) {
+ const char *dev;
+
+ switch (ifindex) {
+ case NETCONFA_IFINDEX_ALL:
+ dev = "all";
+ break;
+ case NETCONFA_IFINDEX_DEFAULT:
+ dev = "default";
+ break;
+ default:
+ dev = ll_index_to_name(ifindex);
+ break;
+ }
+ print_color_string(PRINT_ANY, COLOR_IFNAME,
+ "interface", "%s ", dev);
+ }
+
+ if (tb[NETCONFA_FORWARDING])
+ print_on_off(PRINT_ANY, "forwarding", "forwarding %s ",
+ rta_getattr_u32(tb[NETCONFA_FORWARDING]));
+
+ if (tb[NETCONFA_RP_FILTER]) {
+ __u32 rp_filter = rta_getattr_u32(tb[NETCONFA_RP_FILTER]);
+
+ if (rp_filter < ARRAY_SIZE(rp_filter_names))
+ print_string(PRINT_ANY, "rp_filter",
+ "rp_filter %s ",
+ rp_filter_names[rp_filter]);
+ else
+ print_uint(PRINT_ANY, "rp_filter",
+ "rp_filter %u ", rp_filter);
+ }
+
+ if (tb[NETCONFA_MC_FORWARDING])
+ print_on_off(PRINT_ANY, "mc_forwarding", "mc_forwarding %s ",
+ rta_getattr_u32(tb[NETCONFA_MC_FORWARDING]));
+
+ if (tb[NETCONFA_PROXY_NEIGH])
+ print_on_off(PRINT_ANY, "proxy_neigh", "proxy_neigh %s ",
+ rta_getattr_u32(tb[NETCONFA_PROXY_NEIGH]));
+
+ if (tb[NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN])
+ print_on_off(PRINT_ANY, "ignore_routes_with_linkdown",
+ "ignore_routes_with_linkdown %s ",
+ rta_getattr_u32(tb[NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN]));
+
+ if (tb[NETCONFA_INPUT])
+ print_on_off(PRINT_ANY, "input", "input %s ",
+ rta_getattr_u32(tb[NETCONFA_INPUT]));
+
+ close_json_object();
+ print_string(PRINT_FP, NULL, "\n", NULL);
+ fflush(fp);
+ return 0;
+}
+
+static int print_netconf2(struct nlmsghdr *n, void *arg)
+{
+ return print_netconf(NULL, n, arg);
+}
+
+void ipnetconf_reset_filter(int ifindex)
+{
+ memset(&filter, 0, sizeof(filter));
+ filter.ifindex = ifindex;
+}
+
+static int do_show(int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct netconfmsg ncm;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct netconfmsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
+ .n.nlmsg_type = RTM_GETNETCONF,
+ };
+
+ ipnetconf_reset_filter(0);
+ filter.family = preferred_family;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ filter.ifindex = ll_name_to_index(*argv);
+ if (filter.ifindex <= 0) {
+ fprintf(stderr,
+ "Device \"%s\" does not exist.\n",
+ *argv);
+ return -1;
+ }
+ }
+ argv++; argc--;
+ }
+
+ ll_init_map(&rth);
+
+ if (filter.ifindex && filter.family != AF_UNSPEC) {
+ req.ncm.ncm_family = filter.family;
+ addattr_l(&req.n, sizeof(req), NETCONFA_IFINDEX,
+ &filter.ifindex, sizeof(filter.ifindex));
+
+ if (rtnl_send(&rth, &req.n, req.n.nlmsg_len) < 0) {
+ perror("Can not send request");
+ exit(1);
+ }
+ if (rtnl_listen(&rth, print_netconf, stdout) < 0)
+ exit(2);
+ } else {
+ rth.flags = RTNL_HANDLE_F_SUPPRESS_NLERR;
+dump:
+ if (rtnl_netconfdump_req(&rth, filter.family) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&rth, print_netconf2, stdout) < 0) {
+ /* kernel does not support netconf dump on AF_UNSPEC;
+ * fall back to requesting by family
+ */
+ if (errno == EOPNOTSUPP &&
+ filter.family == AF_UNSPEC) {
+ delete_json_obj();
+ filter.family = AF_INET;
+ goto dump;
+ }
+ perror("RTNETLINK answers");
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ delete_json_obj();
+ if (preferred_family == AF_UNSPEC && filter.family == AF_INET) {
+ preferred_family = AF_INET6;
+ filter.family = AF_INET6;
+ goto dump;
+ }
+ }
+ return 0;
+}
+
+int do_ipnetconf(int argc, char **argv)
+{
+ if (argc > 0) {
+ if (matches(*argv, "show") == 0 ||
+ matches(*argv, "lst") == 0 ||
+ matches(*argv, "list") == 0)
+ return do_show(argc-1, argv+1);
+ if (matches(*argv, "help") == 0)
+ usage();
+ } else
+ return do_show(0, NULL);
+
+ fprintf(stderr,
+ "Command \"%s\" is unknown, try \"ip netconf help\".\n",
+ *argv);
+ exit(-1);
+}
diff --git a/ip/ipnetns.c b/ip/ipnetns.c
new file mode 100644
index 0000000..594b2ef
--- /dev/null
+++ b/ip/ipnetns.c
@@ -0,0 +1,1052 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#define _ATFILE_SOURCE
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/inotify.h>
+#include <sys/mount.h>
+#include <sys/syscall.h>
+#include <stdio.h>
+#include <string.h>
+#include <sched.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <linux/limits.h>
+
+#include <linux/net_namespace.h>
+
+#include "utils.h"
+#include "list.h"
+#include "ip_common.h"
+#include "namespace.h"
+#include "json_print.h"
+
+static int usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip netns list\n"
+ " ip netns add NAME\n"
+ " ip netns attach NAME PID\n"
+ " ip netns set NAME NETNSID\n"
+ " ip [-all] netns delete [NAME]\n"
+ " ip netns identify [PID]\n"
+ " ip netns pids NAME\n"
+ " ip [-all] netns exec [NAME] cmd ...\n"
+ " ip netns monitor\n"
+ " ip netns list-id [target-nsid POSITIVE-INT] [nsid POSITIVE-INT]\n"
+ "NETNSID := auto | POSITIVE-INT\n");
+ exit(-1);
+}
+
+/* This socket is used to get nsid */
+static struct rtnl_handle rtnsh = { .fd = -1 };
+
+static int have_rtnl_getnsid = -1;
+static int saved_netns = -1;
+static struct link_filter filter;
+
+static int ipnetns_accept_msg(struct rtnl_ctrl_data *ctrl,
+ struct nlmsghdr *n, void *arg)
+{
+ struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(n);
+
+ if (n->nlmsg_type == NLMSG_ERROR &&
+ (err->error == -EOPNOTSUPP || err->error == -EINVAL))
+ have_rtnl_getnsid = 0;
+ else
+ have_rtnl_getnsid = 1;
+ return -1;
+}
+
+static int ipnetns_have_nsid(void)
+{
+ struct {
+ struct nlmsghdr n;
+ struct rtgenmsg g;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_GETNSID,
+ .g.rtgen_family = AF_UNSPEC,
+ };
+ int fd;
+
+ if (have_rtnl_getnsid >= 0) {
+ fd = open("/proc/self/ns/net", O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr,
+ "/proc/self/ns/net: %s. Continuing anyway.\n",
+ strerror(errno));
+ have_rtnl_getnsid = 0;
+ return 0;
+ }
+
+ addattr32(&req.n, 1024, NETNSA_FD, fd);
+
+ if (rtnl_send(&rth, &req.n, req.n.nlmsg_len) < 0) {
+ fprintf(stderr,
+ "rtnl_send(RTM_GETNSID): %s. Continuing anyway.\n",
+ strerror(errno));
+ have_rtnl_getnsid = 0;
+ close(fd);
+ return 0;
+ }
+ if (rtnl_listen(&rth, ipnetns_accept_msg, NULL) < 0)
+ exit(2);
+ close(fd);
+ }
+
+ return have_rtnl_getnsid;
+}
+
+int get_netnsid_from_name(const char *name)
+{
+ netns_nsid_socket_init();
+
+ return netns_id_from_name(&rtnsh, name);
+}
+
+struct nsid_cache {
+ struct hlist_node nsid_hash;
+ struct hlist_node name_hash;
+ int nsid;
+ char name[0];
+};
+
+#define NSIDMAP_SIZE 128
+#define NSID_HASH_NSID(nsid) (nsid & (NSIDMAP_SIZE - 1))
+#define NSID_HASH_NAME(name) (namehash(name) & (NSIDMAP_SIZE - 1))
+
+static struct hlist_head nsid_head[NSIDMAP_SIZE];
+static struct hlist_head name_head[NSIDMAP_SIZE];
+
+static struct nsid_cache *netns_map_get_by_nsid(int nsid)
+{
+ struct hlist_node *n;
+ uint32_t h;
+
+ if (nsid < 0)
+ return NULL;
+
+ h = NSID_HASH_NSID(nsid);
+ hlist_for_each(n, &nsid_head[h]) {
+ struct nsid_cache *c = container_of(n, struct nsid_cache,
+ nsid_hash);
+ if (c->nsid == nsid)
+ return c;
+ }
+
+ return NULL;
+}
+
+char *get_name_from_nsid(int nsid)
+{
+ struct nsid_cache *c;
+
+ if (nsid < 0)
+ return NULL;
+
+ netns_nsid_socket_init();
+ netns_map_init();
+
+ c = netns_map_get_by_nsid(nsid);
+ if (c)
+ return c->name;
+
+ return NULL;
+}
+
+static int netns_map_add(int nsid, const char *name)
+{
+ struct nsid_cache *c;
+ uint32_t h;
+
+ if (netns_map_get_by_nsid(nsid) != NULL)
+ return -EEXIST;
+
+ c = malloc(sizeof(*c) + strlen(name) + 1);
+ if (c == NULL) {
+ perror("malloc");
+ return -ENOMEM;
+ }
+ c->nsid = nsid;
+ strcpy(c->name, name);
+
+ h = NSID_HASH_NSID(nsid);
+ hlist_add_head(&c->nsid_hash, &nsid_head[h]);
+
+ h = NSID_HASH_NAME(name);
+ hlist_add_head(&c->name_hash, &name_head[h]);
+
+ return 0;
+}
+
+static void netns_map_del(struct nsid_cache *c)
+{
+ hlist_del(&c->name_hash);
+ hlist_del(&c->nsid_hash);
+ free(c);
+}
+
+void netns_nsid_socket_init(void)
+{
+ if (rtnsh.fd > -1 || !ipnetns_have_nsid())
+ return;
+
+ if (rtnl_open(&rtnsh, 0) < 0) {
+ fprintf(stderr, "Cannot open rtnetlink\n");
+ exit(1);
+ }
+
+}
+
+void netns_map_init(void)
+{
+ static int initialized;
+ struct dirent *entry;
+ DIR *dir;
+ int nsid;
+
+ if (initialized || !ipnetns_have_nsid())
+ return;
+
+ dir = opendir(NETNS_RUN_DIR);
+ if (!dir)
+ return;
+
+ while ((entry = readdir(dir)) != NULL) {
+ if (strcmp(entry->d_name, ".") == 0)
+ continue;
+ if (strcmp(entry->d_name, "..") == 0)
+ continue;
+ nsid = get_netnsid_from_name(entry->d_name);
+
+ if (nsid >= 0)
+ netns_map_add(nsid, entry->d_name);
+ }
+ closedir(dir);
+ initialized = 1;
+}
+
+static int netns_get_name(int nsid, char *name)
+{
+ struct dirent *entry;
+ DIR *dir;
+ int id;
+
+ if (nsid < 0)
+ return -EINVAL;
+
+ dir = opendir(NETNS_RUN_DIR);
+ if (!dir)
+ return -ENOENT;
+
+ while ((entry = readdir(dir)) != NULL) {
+ if (strcmp(entry->d_name, ".") == 0)
+ continue;
+ if (strcmp(entry->d_name, "..") == 0)
+ continue;
+ id = get_netnsid_from_name(entry->d_name);
+
+ if (id >= 0 && nsid == id) {
+ strcpy(name, entry->d_name);
+ closedir(dir);
+ return 0;
+ }
+ }
+ closedir(dir);
+ return -ENOENT;
+}
+
+int print_nsid(struct nlmsghdr *n, void *arg)
+{
+ struct rtgenmsg *rthdr = NLMSG_DATA(n);
+ struct rtattr *tb[NETNSA_MAX+1];
+ int len = n->nlmsg_len;
+ FILE *fp = (FILE *)arg;
+ struct nsid_cache *c;
+ char name[NAME_MAX];
+ int nsid, current;
+
+ if (n->nlmsg_type != RTM_NEWNSID && n->nlmsg_type != RTM_DELNSID)
+ return 0;
+
+ len -= NLMSG_SPACE(sizeof(*rthdr));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d in %s\n", len,
+ __func__);
+ return -1;
+ }
+
+ parse_rtattr(tb, NETNSA_MAX, NETNS_RTA(rthdr), len);
+ if (tb[NETNSA_NSID] == NULL) {
+ fprintf(stderr, "BUG: NETNSA_NSID is missing %s\n", __func__);
+ return -1;
+ }
+
+ open_json_object(NULL);
+ if (n->nlmsg_type == RTM_DELNSID)
+ print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+ nsid = rta_getattr_s32(tb[NETNSA_NSID]);
+ if (nsid < 0)
+ print_string(PRINT_FP, NULL, "nsid unassigned ", NULL);
+ else
+ print_int(PRINT_ANY, "nsid", "nsid %d ", nsid);
+
+ if (tb[NETNSA_CURRENT_NSID]) {
+ current = rta_getattr_s32(tb[NETNSA_CURRENT_NSID]);
+ if (current < 0)
+ print_string(PRINT_FP, NULL,
+ "current-nsid unassigned ", NULL);
+ else
+ print_int(PRINT_ANY, "current-nsid",
+ "current-nsid %d ", current);
+ }
+
+ c = netns_map_get_by_nsid(tb[NETNSA_CURRENT_NSID] ? current : nsid);
+ if (c != NULL) {
+ print_string(PRINT_ANY, "name",
+ "(iproute2 netns name: %s)", c->name);
+ netns_map_del(c);
+ }
+
+ /* nsid might not be in cache */
+ if (c == NULL && n->nlmsg_type == RTM_NEWNSID)
+ if (netns_get_name(nsid, name) == 0) {
+ print_string(PRINT_ANY, "name",
+ "(iproute2 netns name: %s)", name);
+ netns_map_add(nsid, name);
+ }
+
+ print_string(PRINT_FP, NULL, "\n", NULL);
+ close_json_object();
+ fflush(fp);
+ return 0;
+}
+
+static int get_netnsid_from_netnsid(int nsid)
+{
+ struct {
+ struct nlmsghdr n;
+ struct rtgenmsg g;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(NLMSG_ALIGN(sizeof(struct rtgenmsg))),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_GETNSID,
+ .g.rtgen_family = AF_UNSPEC,
+ };
+ struct nlmsghdr *answer;
+ int err;
+
+ netns_nsid_socket_init();
+
+ err = addattr32(&req.n, sizeof(req), NETNSA_NSID, nsid);
+ if (err)
+ return err;
+
+ if (filter.target_nsid >= 0) {
+ err = addattr32(&req.n, sizeof(req), NETNSA_TARGET_NSID,
+ filter.target_nsid);
+ if (err)
+ return err;
+ }
+
+ if (rtnl_talk(&rtnsh, &req.n, &answer) < 0)
+ return -2;
+
+ /* Validate message and parse attributes */
+ if (answer->nlmsg_type == NLMSG_ERROR)
+ goto err_out;
+
+ new_json_obj(json);
+ err = print_nsid(answer, stdout);
+ delete_json_obj();
+err_out:
+ free(answer);
+ return err;
+}
+
+static int netns_filter_req(struct nlmsghdr *nlh, int reqlen)
+{
+ int err;
+
+ if (filter.target_nsid >= 0) {
+ err = addattr32(nlh, reqlen, NETNSA_TARGET_NSID,
+ filter.target_nsid);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int netns_list_id(int argc, char **argv)
+{
+ int nsid = -1;
+
+ if (!ipnetns_have_nsid()) {
+ fprintf(stderr,
+ "RTM_GETNSID is not supported by the kernel.\n");
+ return -ENOTSUP;
+ }
+
+ filter.target_nsid = -1;
+ while (argc > 0) {
+ if (strcmp(*argv, "target-nsid") == 0) {
+ if (filter.target_nsid >= 0)
+ duparg("target-nsid", *argv);
+ NEXT_ARG();
+
+ if (get_integer(&filter.target_nsid, *argv, 0))
+ invarg("\"target-nsid\" value is invalid",
+ *argv);
+ else if (filter.target_nsid < 0)
+ invarg("\"target-nsid\" value should be >= 0",
+ argv[1]);
+ } else if (strcmp(*argv, "nsid") == 0) {
+ if (nsid >= 0)
+ duparg("nsid", *argv);
+ NEXT_ARG();
+
+ if (get_integer(&nsid, *argv, 0))
+ invarg("\"nsid\" value is invalid", *argv);
+ else if (nsid < 0)
+ invarg("\"nsid\" value should be >= 0",
+ argv[1]);
+ } else
+ usage();
+ argc--; argv++;
+ }
+
+ if (nsid >= 0)
+ return get_netnsid_from_netnsid(nsid);
+
+ if (rtnl_nsiddump_req_filter_fn(&rth, AF_UNSPEC,
+ netns_filter_req) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&rth, print_nsid, stdout) < 0) {
+ delete_json_obj();
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ delete_json_obj();
+ return 0;
+}
+
+static int netns_list(int argc, char **argv)
+{
+ struct dirent *entry;
+ DIR *dir;
+ int id;
+
+ dir = opendir(NETNS_RUN_DIR);
+ if (!dir)
+ return 0;
+
+ new_json_obj(json);
+ while ((entry = readdir(dir)) != NULL) {
+ if (strcmp(entry->d_name, ".") == 0)
+ continue;
+ if (strcmp(entry->d_name, "..") == 0)
+ continue;
+
+ open_json_object(NULL);
+ print_string(PRINT_ANY, "name",
+ "%s", entry->d_name);
+ if (ipnetns_have_nsid()) {
+ id = get_netnsid_from_name(entry->d_name);
+ if (id >= 0)
+ print_int(PRINT_ANY, "id", " (id: %d)", id);
+ }
+ print_string(PRINT_FP, NULL, "\n", NULL);
+ close_json_object();
+ }
+ closedir(dir);
+ delete_json_obj();
+ return 0;
+}
+
+static int do_switch(void *arg)
+{
+ char *netns = arg;
+
+ /* we just changed namespaces. clear any vrf association
+ * with prior namespace before exec'ing command
+ */
+ vrf_reset();
+
+ return netns_switch(netns);
+}
+
+static int on_netns_exec(char *nsname, void *arg)
+{
+ char **argv = arg;
+
+ printf("\nnetns: %s\n", nsname);
+ cmd_exec(argv[0], argv, true, do_switch, nsname);
+ return 0;
+}
+
+static int netns_exec(int argc, char **argv)
+{
+ /* Setup the proper environment for apps that are not netns
+ * aware, and execute a program in that environment.
+ */
+ if (argc < 1 && !do_all) {
+ fprintf(stderr, "No netns name specified\n");
+ return -1;
+ }
+ if ((argc < 2 && !do_all) || (argc < 1 && do_all)) {
+ fprintf(stderr, "No command specified\n");
+ return -1;
+ }
+
+ if (do_all)
+ return netns_foreach(on_netns_exec, argv);
+
+ /* ip must return the status of the child,
+ * but do_cmd() will add a minus to this,
+ * so let's add another one here to cancel it.
+ */
+ return -cmd_exec(argv[1], argv + 1, !!batch_mode, do_switch, argv[0]);
+}
+
+static int is_pid(const char *str)
+{
+ int ch;
+
+ for (; (ch = *str); str++) {
+ if (!isdigit(ch))
+ return 0;
+ }
+ return 1;
+}
+
+static int netns_pids(int argc, char **argv)
+{
+ const char *name;
+ char net_path[PATH_MAX];
+ int netns = -1, ret = -1;
+ struct stat netst;
+ DIR *dir;
+ struct dirent *entry;
+
+ if (argc < 1) {
+ fprintf(stderr, "No netns name specified\n");
+ goto out;
+ }
+ if (argc > 1) {
+ fprintf(stderr, "extra arguments specified\n");
+ goto out;
+ }
+
+ name = argv[0];
+ snprintf(net_path, sizeof(net_path), "%s/%s", NETNS_RUN_DIR, name);
+ netns = open(net_path, O_RDONLY);
+ if (netns < 0) {
+ fprintf(stderr, "Cannot open network namespace: %s\n",
+ strerror(errno));
+ goto out;
+ }
+ if (fstat(netns, &netst) < 0) {
+ fprintf(stderr, "Stat of netns failed: %s\n",
+ strerror(errno));
+ goto out;
+ }
+ dir = opendir("/proc/");
+ if (!dir) {
+ fprintf(stderr, "Open of /proc failed: %s\n",
+ strerror(errno));
+ goto out;
+ }
+ while ((entry = readdir(dir))) {
+ char pid_net_path[PATH_MAX];
+ struct stat st;
+
+ if (!is_pid(entry->d_name))
+ continue;
+ snprintf(pid_net_path, sizeof(pid_net_path), "/proc/%s/ns/net",
+ entry->d_name);
+ if (stat(pid_net_path, &st) != 0)
+ continue;
+ if ((st.st_dev == netst.st_dev) &&
+ (st.st_ino == netst.st_ino)) {
+ printf("%s\n", entry->d_name);
+ }
+ }
+ ret = 0;
+ closedir(dir);
+out:
+ if (netns >= 0)
+ close(netns);
+ return ret;
+
+}
+
+int netns_identify_pid(const char *pidstr, char *name, int len)
+{
+ char net_path[PATH_MAX];
+ int netns = -1, ret = -1;
+ struct stat netst;
+ DIR *dir;
+ struct dirent *entry;
+
+ name[0] = '\0';
+
+ snprintf(net_path, sizeof(net_path), "/proc/%s/ns/net", pidstr);
+ netns = open(net_path, O_RDONLY);
+ if (netns < 0) {
+ fprintf(stderr, "Cannot open network namespace: %s\n",
+ strerror(errno));
+ goto out;
+ }
+ if (fstat(netns, &netst) < 0) {
+ fprintf(stderr, "Stat of netns failed: %s\n",
+ strerror(errno));
+ goto out;
+ }
+ dir = opendir(NETNS_RUN_DIR);
+ if (!dir) {
+ /* Succeed treat a missing directory as an empty directory */
+ if (errno == ENOENT) {
+ ret = 0;
+ goto out;
+ }
+
+ fprintf(stderr, "Failed to open directory %s:%s\n",
+ NETNS_RUN_DIR, strerror(errno));
+ goto out;
+ }
+
+ while ((entry = readdir(dir))) {
+ char name_path[PATH_MAX];
+ struct stat st;
+
+ if (strcmp(entry->d_name, ".") == 0)
+ continue;
+ if (strcmp(entry->d_name, "..") == 0)
+ continue;
+
+ snprintf(name_path, sizeof(name_path), "%s/%s", NETNS_RUN_DIR,
+ entry->d_name);
+
+ if (stat(name_path, &st) != 0)
+ continue;
+
+ if ((st.st_dev == netst.st_dev) &&
+ (st.st_ino == netst.st_ino)) {
+ strlcpy(name, entry->d_name, len);
+ }
+ }
+ ret = 0;
+ closedir(dir);
+out:
+ if (netns >= 0)
+ close(netns);
+ return ret;
+
+}
+
+static int netns_identify(int argc, char **argv)
+{
+ const char *pidstr;
+ char name[256];
+ int rc;
+
+ if (argc < 1) {
+ pidstr = "self";
+ } else if (argc > 1) {
+ fprintf(stderr, "extra arguments specified\n");
+ return -1;
+ } else {
+ pidstr = argv[0];
+ if (!is_pid(pidstr)) {
+ fprintf(stderr, "Specified string '%s' is not a pid\n",
+ pidstr);
+ return -1;
+ }
+ }
+
+ rc = netns_identify_pid(pidstr, name, sizeof(name));
+ if (!rc)
+ printf("%s\n", name);
+
+ return rc;
+}
+
+static int on_netns_del(char *nsname, void *arg)
+{
+ char netns_path[PATH_MAX];
+
+ snprintf(netns_path, sizeof(netns_path), "%s/%s", NETNS_RUN_DIR, nsname);
+ umount2(netns_path, MNT_DETACH);
+ if (unlink(netns_path) < 0) {
+ fprintf(stderr, "Cannot remove namespace file \"%s\": %s\n",
+ netns_path, strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+static int netns_delete(int argc, char **argv)
+{
+ if (argc < 1 && !do_all) {
+ fprintf(stderr, "No netns name specified\n");
+ return -1;
+ }
+
+ if (do_all)
+ return netns_foreach(on_netns_del, NULL);
+
+ return on_netns_del(argv[0], NULL);
+}
+
+static int create_netns_dir(void)
+{
+ /* Create the base netns directory if it doesn't exist */
+ if (mkdir(NETNS_RUN_DIR, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)) {
+ if (errno != EEXIST) {
+ fprintf(stderr, "mkdir %s failed: %s\n",
+ NETNS_RUN_DIR, strerror(errno));
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/* Obtain a FD for the current namespace, so we can reenter it later */
+static void netns_save(void)
+{
+ if (saved_netns != -1)
+ return;
+
+ saved_netns = open("/proc/self/ns/net", O_RDONLY | O_CLOEXEC);
+ if (saved_netns == -1) {
+ perror("Cannot open init namespace");
+ exit(1);
+ }
+}
+
+static void netns_restore(void)
+{
+ if (saved_netns == -1)
+ return;
+
+ if (setns(saved_netns, CLONE_NEWNET)) {
+ perror("setns");
+ exit(1);
+ }
+
+ close(saved_netns);
+ saved_netns = -1;
+}
+
+static int netns_add(int argc, char **argv, bool create)
+{
+ /* This function creates a new network namespace and
+ * a new mount namespace and bind them into a well known
+ * location in the filesystem based on the name provided.
+ *
+ * If create is true, a new namespace will be created,
+ * otherwise an existing one will be attached to the file.
+ *
+ * The mount namespace is created so that any necessary
+ * userspace tweaks like remounting /sys, or bind mounting
+ * a new /etc/resolv.conf can be shared between users.
+ */
+ char netns_path[PATH_MAX], proc_path[PATH_MAX];
+ const char *name;
+ pid_t pid;
+ int fd;
+ int lock;
+ int made_netns_run_dir_mount = 0;
+
+ if (create) {
+ if (argc < 1) {
+ fprintf(stderr, "No netns name specified\n");
+ return -1;
+ }
+ } else {
+ if (argc < 2) {
+ fprintf(stderr, "No netns name and PID specified\n");
+ return -1;
+ }
+
+ if (get_s32(&pid, argv[1], 0) || !pid) {
+ fprintf(stderr, "Invalid PID: %s\n", argv[1]);
+ return -1;
+ }
+ }
+ name = argv[0];
+
+ snprintf(netns_path, sizeof(netns_path), "%s/%s", NETNS_RUN_DIR, name);
+
+ if (create_netns_dir())
+ return -1;
+
+ /* Make it possible for network namespace mounts to propagate between
+ * mount namespaces. This makes it likely that a unmounting a network
+ * namespace file in one namespace will unmount the network namespace
+ * file in all namespaces allowing the network namespace to be freed
+ * sooner.
+ * These setup steps need to happen only once, as if multiple ip processes
+ * try to attempt the same operation at the same time, the mountpoints will
+ * be recursively created multiple times, eventually causing the system
+ * to lock up. For example, this has been observed when multiple netns
+ * namespaces are created in parallel at boot. See:
+ * https://bugs.debian.org/949235
+ * Try to take an exclusive file lock on the top level directory to ensure
+ * this cannot happen, but proceed nonetheless if it cannot happen for any
+ * reason.
+ */
+ lock = open(NETNS_RUN_DIR, O_RDONLY|O_DIRECTORY, 0);
+ if (lock < 0) {
+ fprintf(stderr, "Cannot open netns runtime directory \"%s\": %s\n",
+ NETNS_RUN_DIR, strerror(errno));
+ return -1;
+ }
+ if (flock(lock, LOCK_EX) < 0) {
+ fprintf(stderr, "Warning: could not flock netns runtime directory \"%s\": %s\n",
+ NETNS_RUN_DIR, strerror(errno));
+ close(lock);
+ lock = -1;
+ }
+ while (mount("", NETNS_RUN_DIR, "none", MS_SHARED | MS_REC, NULL)) {
+ /* Fail unless we need to make the mount point */
+ if (errno != EINVAL || made_netns_run_dir_mount) {
+ fprintf(stderr, "mount --make-shared %s failed: %s\n",
+ NETNS_RUN_DIR, strerror(errno));
+ if (lock != -1) {
+ flock(lock, LOCK_UN);
+ close(lock);
+ }
+ return -1;
+ }
+
+ /* Upgrade NETNS_RUN_DIR to a mount point */
+ if (mount(NETNS_RUN_DIR, NETNS_RUN_DIR, "none", MS_BIND | MS_REC, NULL)) {
+ fprintf(stderr, "mount --bind %s %s failed: %s\n",
+ NETNS_RUN_DIR, NETNS_RUN_DIR, strerror(errno));
+ if (lock != -1) {
+ flock(lock, LOCK_UN);
+ close(lock);
+ }
+ return -1;
+ }
+ made_netns_run_dir_mount = 1;
+ }
+ if (lock != -1) {
+ flock(lock, LOCK_UN);
+ close(lock);
+ }
+
+ /* Create the filesystem state */
+ fd = open(netns_path, O_RDONLY|O_CREAT|O_EXCL, 0);
+ if (fd < 0) {
+ fprintf(stderr, "Cannot create namespace file \"%s\": %s\n",
+ netns_path, strerror(errno));
+ return -1;
+ }
+ close(fd);
+
+ if (create) {
+ netns_save();
+ if (unshare(CLONE_NEWNET) < 0) {
+ fprintf(stderr, "Failed to create a new network namespace \"%s\": %s\n",
+ name, strerror(errno));
+ goto out_delete;
+ }
+
+ strcpy(proc_path, "/proc/self/ns/net");
+ } else {
+ snprintf(proc_path, sizeof(proc_path), "/proc/%d/ns/net", pid);
+ }
+
+ /* Bind the netns last so I can watch for it */
+ if (mount(proc_path, netns_path, "none", MS_BIND, NULL) < 0) {
+ fprintf(stderr, "Bind %s -> %s failed: %s\n",
+ proc_path, netns_path, strerror(errno));
+ goto out_delete;
+ }
+ netns_restore();
+
+ return 0;
+out_delete:
+ if (create) {
+ netns_restore();
+ netns_delete(argc, argv);
+ } else if (unlink(netns_path) < 0) {
+ fprintf(stderr, "Cannot remove namespace file \"%s\": %s\n",
+ netns_path, strerror(errno));
+ }
+ return -1;
+}
+
+int set_netnsid_from_name(const char *name, int nsid)
+{
+ struct {
+ struct nlmsghdr n;
+ struct rtgenmsg g;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_NEWNSID,
+ .g.rtgen_family = AF_UNSPEC,
+ };
+ int fd, err = 0;
+
+ netns_nsid_socket_init();
+
+ fd = netns_get_fd(name);
+ if (fd < 0)
+ return fd;
+
+ addattr32(&req.n, 1024, NETNSA_FD, fd);
+ addattr32(&req.n, 1024, NETNSA_NSID, nsid);
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ err = -2;
+
+ close(fd);
+ return err;
+}
+
+static int netns_set(int argc, char **argv)
+{
+ const char *name;
+ int nsid;
+
+ if (argc < 1) {
+ fprintf(stderr, "No netns name specified\n");
+ return -1;
+ }
+ if (argc < 2) {
+ fprintf(stderr, "No nsid specified\n");
+ return -1;
+ }
+ name = argv[0];
+ /* If a negative nsid is specified the kernel will select the nsid. */
+ if (strcmp(argv[1], "auto") == 0)
+ nsid = -1;
+ else if (get_integer(&nsid, argv[1], 0))
+ invarg("Invalid \"netnsid\" value", argv[1]);
+ else if (nsid < 0)
+ invarg("\"netnsid\" value should be >= 0", argv[1]);
+
+ return set_netnsid_from_name(name, nsid);
+}
+
+static int netns_monitor(int argc, char **argv)
+{
+ char buf[4096];
+ struct inotify_event *event;
+ int fd;
+
+ fd = inotify_init();
+ if (fd < 0) {
+ fprintf(stderr, "inotify_init failed: %s\n",
+ strerror(errno));
+ return -1;
+ }
+
+ if (create_netns_dir())
+ return -1;
+
+ if (inotify_add_watch(fd, NETNS_RUN_DIR, IN_CREATE | IN_DELETE) < 0) {
+ fprintf(stderr, "inotify_add_watch failed: %s\n",
+ strerror(errno));
+ return -1;
+ }
+ for (;;) {
+ ssize_t len = read(fd, buf, sizeof(buf));
+
+ if (len < 0) {
+ fprintf(stderr, "read failed: %s\n",
+ strerror(errno));
+ return -1;
+ }
+ for (event = (struct inotify_event *)buf;
+ (char *)event < &buf[len];
+ event = (struct inotify_event *)((char *)event + sizeof(*event) + event->len)) {
+ if (event->mask & IN_CREATE)
+ printf("add %s\n", event->name);
+ if (event->mask & IN_DELETE)
+ printf("delete %s\n", event->name);
+ }
+ }
+ return 0;
+}
+
+static int invalid_name(const char *name)
+{
+ return !*name || strlen(name) > NAME_MAX ||
+ strchr(name, '/') || !strcmp(name, ".") || !strcmp(name, "..");
+}
+
+int do_netns(int argc, char **argv)
+{
+ netns_nsid_socket_init();
+
+ if (argc < 1) {
+ netns_map_init();
+ return netns_list(0, NULL);
+ }
+
+ if (!do_all && argc > 1 && invalid_name(argv[1])) {
+ fprintf(stderr, "Invalid netns name \"%s\"\n", argv[1]);
+ exit(-1);
+ }
+
+ if ((matches(*argv, "list") == 0) || (matches(*argv, "show") == 0) ||
+ (matches(*argv, "lst") == 0)) {
+ netns_map_init();
+ return netns_list(argc-1, argv+1);
+ }
+
+ if ((matches(*argv, "list-id") == 0)) {
+ netns_map_init();
+ return netns_list_id(argc-1, argv+1);
+ }
+
+ if (matches(*argv, "help") == 0)
+ return usage();
+
+ if (matches(*argv, "add") == 0)
+ return netns_add(argc-1, argv+1, true);
+
+ if (matches(*argv, "set") == 0)
+ return netns_set(argc-1, argv+1);
+
+ if (matches(*argv, "delete") == 0)
+ return netns_delete(argc-1, argv+1);
+
+ if (matches(*argv, "identify") == 0)
+ return netns_identify(argc-1, argv+1);
+
+ if (matches(*argv, "pids") == 0)
+ return netns_pids(argc-1, argv+1);
+
+ if (matches(*argv, "exec") == 0)
+ return netns_exec(argc-1, argv+1);
+
+ if (matches(*argv, "monitor") == 0)
+ return netns_monitor(argc-1, argv+1);
+
+ if (matches(*argv, "attach") == 0)
+ return netns_add(argc-1, argv+1, false);
+
+ fprintf(stderr, "Command \"%s\" is unknown, try \"ip netns help\".\n", *argv);
+ exit(-1);
+}
diff --git a/ip/ipnexthop.c b/ip/ipnexthop.c
new file mode 100644
index 0000000..e946d6f
--- /dev/null
+++ b/ip/ipnexthop.c
@@ -0,0 +1,1321 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ip nexthop
+ *
+ * Copyright (c) 2017-19 David Ahern <dsahern@gmail.com>
+ */
+
+#include <linux/nexthop.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <rt_names.h>
+#include <errno.h>
+
+#include "utils.h"
+#include "ip_common.h"
+#include "nh_common.h"
+
+static struct {
+ unsigned int flushed;
+ unsigned int groups;
+ unsigned int ifindex;
+ unsigned int master;
+ unsigned int proto;
+ unsigned int fdb;
+ unsigned int id;
+ unsigned int nhid;
+} filter;
+
+enum {
+ IPNH_LIST,
+ IPNH_FLUSH,
+};
+
+#define RTM_NHA(h) ((struct rtattr *)(((char *)(h)) + \
+ NLMSG_ALIGN(sizeof(struct nhmsg))))
+
+static struct hlist_head nh_cache[NH_CACHE_SIZE];
+static struct rtnl_handle nh_cache_rth = { .fd = -1 };
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip nexthop { list | flush } [ protocol ID ] SELECTOR\n"
+ " ip nexthop { add | replace } id ID NH [ protocol ID ]\n"
+ " ip nexthop { get | del } id ID\n"
+ " ip nexthop bucket list BUCKET_SELECTOR\n"
+ " ip nexthop bucket get id ID index INDEX\n"
+ "SELECTOR := [ id ID ] [ dev DEV ] [ vrf NAME ] [ master DEV ]\n"
+ " [ groups ] [ fdb ]\n"
+ "BUCKET_SELECTOR := SELECTOR | [ nhid ID ]\n"
+ "NH := { blackhole | [ via ADDRESS ] [ dev DEV ] [ onlink ]\n"
+ " [ encap ENCAPTYPE ENCAPHDR ] |\n"
+ " group GROUP [ fdb ] [ type TYPE [ TYPE_ARGS ] ] }\n"
+ "GROUP := [ <id[,weight]>/<id[,weight]>/... ]\n"
+ "TYPE := { mpath | resilient }\n"
+ "TYPE_ARGS := [ RESILIENT_ARGS ]\n"
+ "RESILIENT_ARGS := [ buckets BUCKETS ] [ idle_timer IDLE ]\n"
+ " [ unbalanced_timer UNBALANCED ]\n"
+ "ENCAPTYPE := [ mpls ]\n"
+ "ENCAPHDR := [ MPLSLABEL ]\n");
+ exit(-1);
+}
+
+static int nh_dump_filter(struct nlmsghdr *nlh, int reqlen)
+{
+ int err;
+
+ if (filter.ifindex) {
+ err = addattr32(nlh, reqlen, NHA_OIF, filter.ifindex);
+ if (err)
+ return err;
+ }
+
+ if (filter.groups) {
+ err = addattr_l(nlh, reqlen, NHA_GROUPS, NULL, 0);
+ if (err)
+ return err;
+ }
+
+ if (filter.master) {
+ err = addattr32(nlh, reqlen, NHA_MASTER, filter.master);
+ if (err)
+ return err;
+ }
+
+ if (filter.fdb) {
+ err = addattr_l(nlh, reqlen, NHA_FDB, NULL, 0);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int nh_dump_bucket_filter(struct nlmsghdr *nlh, int reqlen)
+{
+ struct rtattr *nest;
+ int err = 0;
+
+ err = nh_dump_filter(nlh, reqlen);
+ if (err)
+ return err;
+
+ if (filter.id) {
+ err = addattr32(nlh, reqlen, NHA_ID, filter.id);
+ if (err)
+ return err;
+ }
+
+ if (filter.nhid) {
+ nest = addattr_nest(nlh, reqlen, NHA_RES_BUCKET);
+ nest->rta_type |= NLA_F_NESTED;
+
+ err = addattr32(nlh, reqlen, NHA_RES_BUCKET_NH_ID,
+ filter.nhid);
+ if (err)
+ return err;
+
+ addattr_nest_end(nlh, nest);
+ }
+
+ return err;
+}
+
+static struct rtnl_handle rth_del = { .fd = -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_del.seq;
+
+ addattr32(&req.n, sizeof(req), NHA_ID, id);
+
+ if (rtnl_talk(&rth_del, &req.n, NULL) < 0)
+ return -1;
+ return 0;
+}
+
+static int flush_nexthop(struct nlmsghdr *nlh, void *arg)
+{
+ struct nhmsg *nhm = NLMSG_DATA(nlh);
+ struct rtattr *tb[NHA_MAX+1];
+ __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;
+ }
+
+ if (filter.proto && nhm->nh_protocol != filter.proto)
+ return 0;
+
+ parse_rtattr(tb, NHA_MAX, RTM_NHA(nhm), len);
+ if (tb[NHA_ID])
+ id = rta_getattr_u32(tb[NHA_ID]);
+
+ if (id && !delete_nexthop(id))
+ filter.flushed++;
+
+ return 0;
+}
+
+static int ipnh_flush(unsigned int all)
+{
+ int rc = -2;
+
+ if (all) {
+ filter.groups = 1;
+ filter.ifindex = 0;
+ filter.master = 0;
+ }
+
+ if (rtnl_open(&rth_del, 0) < 0) {
+ fprintf(stderr, "Cannot open rtnetlink\n");
+ return EXIT_FAILURE;
+ }
+again:
+ if (rtnl_nexthopdump_req(&rth, preferred_family, nh_dump_filter) < 0) {
+ perror("Cannot send dump request");
+ goto out;
+ }
+
+ if (rtnl_dump_filter(&rth, flush_nexthop, stdout) < 0) {
+ fprintf(stderr, "Dump terminated. Failed to flush nexthops\n");
+ goto out;
+ }
+
+ /* if deleting all, then remove groups first */
+ if (all && filter.groups) {
+ filter.groups = 0;
+ goto again;
+ }
+
+ rc = 0;
+out:
+ rtnl_close(&rth_del);
+ if (!filter.flushed)
+ printf("Nothing to flush\n");
+ else
+ printf("Flushed %d nexthops\n", filter.flushed);
+
+ return rc;
+}
+
+static bool __valid_nh_group_attr(const struct rtattr *g_attr)
+{
+ int num = RTA_PAYLOAD(g_attr) / sizeof(struct nexthop_grp);
+
+ return num && num * sizeof(struct nexthop_grp) == RTA_PAYLOAD(g_attr);
+}
+
+static void print_nh_group(const struct nh_entry *nhe)
+{
+ int i;
+
+ open_json_array(PRINT_JSON, "group");
+ print_string(PRINT_FP, NULL, "%s", "group ");
+ for (i = 0; i < nhe->nh_groups_cnt; ++i) {
+ open_json_object(NULL);
+
+ if (i)
+ print_string(PRINT_FP, NULL, "%s", "/");
+
+ print_uint(PRINT_ANY, "id", "%u", nhe->nh_groups[i].id);
+ if (nhe->nh_groups[i].weight)
+ print_uint(PRINT_ANY, "weight", ",%u",
+ nhe->nh_groups[i].weight + 1);
+
+ close_json_object();
+ }
+ print_string(PRINT_FP, NULL, "%s", " ");
+ close_json_array(PRINT_JSON, NULL);
+}
+
+static const char *nh_group_type_name(__u16 type)
+{
+ switch (type) {
+ case NEXTHOP_GRP_TYPE_MPATH:
+ return "mpath";
+ case NEXTHOP_GRP_TYPE_RES:
+ return "resilient";
+ default:
+ return "<unknown type>";
+ }
+}
+
+static void print_nh_group_type(__u16 nh_grp_type)
+{
+ if (nh_grp_type == NEXTHOP_GRP_TYPE_MPATH)
+ /* Do not print type in order not to break existing output. */
+ return;
+
+ print_string(PRINT_ANY, "type", "type %s ", nh_group_type_name(nh_grp_type));
+}
+
+static void parse_nh_res_group_rta(const struct rtattr *res_grp_attr,
+ struct nha_res_grp *res_grp)
+{
+ struct rtattr *tb[NHA_RES_GROUP_MAX + 1];
+ struct rtattr *rta;
+
+ memset(res_grp, 0, sizeof(*res_grp));
+ parse_rtattr_nested(tb, NHA_RES_GROUP_MAX, res_grp_attr);
+
+ if (tb[NHA_RES_GROUP_BUCKETS])
+ res_grp->buckets = rta_getattr_u16(tb[NHA_RES_GROUP_BUCKETS]);
+
+ if (tb[NHA_RES_GROUP_IDLE_TIMER]) {
+ rta = tb[NHA_RES_GROUP_IDLE_TIMER];
+ res_grp->idle_timer = rta_getattr_u32(rta);
+ }
+
+ if (tb[NHA_RES_GROUP_UNBALANCED_TIMER]) {
+ rta = tb[NHA_RES_GROUP_UNBALANCED_TIMER];
+ res_grp->unbalanced_timer = rta_getattr_u32(rta);
+ }
+
+ if (tb[NHA_RES_GROUP_UNBALANCED_TIME]) {
+ rta = tb[NHA_RES_GROUP_UNBALANCED_TIME];
+ res_grp->unbalanced_time = rta_getattr_u64(rta);
+ }
+}
+
+static void print_nh_res_group(const struct nha_res_grp *res_grp)
+{
+ struct timeval tv;
+
+ open_json_object("resilient_args");
+
+ print_uint(PRINT_ANY, "buckets", "buckets %u ", res_grp->buckets);
+
+ __jiffies_to_tv(&tv, res_grp->idle_timer);
+ print_tv(PRINT_ANY, "idle_timer", "idle_timer %g ", &tv);
+
+ __jiffies_to_tv(&tv, res_grp->unbalanced_timer);
+ print_tv(PRINT_ANY, "unbalanced_timer", "unbalanced_timer %g ", &tv);
+
+ __jiffies_to_tv(&tv, res_grp->unbalanced_time);
+ print_tv(PRINT_ANY, "unbalanced_time", "unbalanced_time %g ", &tv);
+
+ close_json_object();
+}
+
+static void print_nh_res_bucket(FILE *fp, const struct rtattr *res_bucket_attr)
+{
+ struct rtattr *tb[NHA_RES_BUCKET_MAX + 1];
+
+ parse_rtattr_nested(tb, NHA_RES_BUCKET_MAX, res_bucket_attr);
+
+ open_json_object("bucket");
+
+ if (tb[NHA_RES_BUCKET_INDEX])
+ print_uint(PRINT_ANY, "index", "index %u ",
+ rta_getattr_u16(tb[NHA_RES_BUCKET_INDEX]));
+
+ if (tb[NHA_RES_BUCKET_IDLE_TIME]) {
+ struct rtattr *rta = tb[NHA_RES_BUCKET_IDLE_TIME];
+ struct timeval tv;
+
+ __jiffies_to_tv(&tv, rta_getattr_u64(rta));
+ print_tv(PRINT_ANY, "idle_time", "idle_time %g ", &tv);
+ }
+
+ if (tb[NHA_RES_BUCKET_NH_ID])
+ print_uint(PRINT_ANY, "nhid", "nhid %u ",
+ rta_getattr_u32(tb[NHA_RES_BUCKET_NH_ID]));
+
+ close_json_object();
+}
+
+static void ipnh_destroy_entry(struct nh_entry *nhe)
+{
+ free(nhe->nh_encap);
+ free(nhe->nh_groups);
+}
+
+/* parse nhmsg into nexthop entry struct which must be destroyed by
+ * ipnh_destroy_enty when it's not needed anymore
+ */
+static int ipnh_parse_nhmsg(FILE *fp, const struct nhmsg *nhm, int len,
+ struct nh_entry *nhe)
+{
+ struct rtattr *tb[NHA_MAX+1];
+ int err = 0;
+
+ memset(nhe, 0, sizeof(*nhe));
+ parse_rtattr_flags(tb, NHA_MAX, RTM_NHA(nhm), len, NLA_F_NESTED);
+
+ if (tb[NHA_ID])
+ nhe->nh_id = rta_getattr_u32(tb[NHA_ID]);
+
+ if (tb[NHA_OIF])
+ nhe->nh_oif = rta_getattr_u32(tb[NHA_OIF]);
+
+ if (tb[NHA_GROUP_TYPE])
+ nhe->nh_grp_type = rta_getattr_u16(tb[NHA_GROUP_TYPE]);
+
+ if (tb[NHA_GATEWAY]) {
+ if (RTA_PAYLOAD(tb[NHA_GATEWAY]) > sizeof(nhe->nh_gateway)) {
+ fprintf(fp, "<nexthop id %u invalid gateway length %lu>\n",
+ nhe->nh_id, RTA_PAYLOAD(tb[NHA_GATEWAY]));
+ err = -EINVAL;
+ goto out_err;
+ }
+ nhe->nh_gateway_len = RTA_PAYLOAD(tb[NHA_GATEWAY]);
+ memcpy(&nhe->nh_gateway, RTA_DATA(tb[NHA_GATEWAY]),
+ RTA_PAYLOAD(tb[NHA_GATEWAY]));
+ }
+
+ if (tb[NHA_ENCAP]) {
+ nhe->nh_encap = malloc(RTA_LENGTH(RTA_PAYLOAD(tb[NHA_ENCAP])));
+ if (!nhe->nh_encap) {
+ err = -ENOMEM;
+ goto out_err;
+ }
+ memcpy(nhe->nh_encap, tb[NHA_ENCAP],
+ RTA_LENGTH(RTA_PAYLOAD(tb[NHA_ENCAP])));
+ memcpy(&nhe->nh_encap_type, tb[NHA_ENCAP_TYPE],
+ sizeof(nhe->nh_encap_type));
+ }
+
+ if (tb[NHA_GROUP]) {
+ if (!__valid_nh_group_attr(tb[NHA_GROUP])) {
+ fprintf(fp, "<nexthop id %u invalid nexthop group>",
+ nhe->nh_id);
+ err = -EINVAL;
+ goto out_err;
+ }
+
+ nhe->nh_groups = malloc(RTA_PAYLOAD(tb[NHA_GROUP]));
+ if (!nhe->nh_groups) {
+ err = -ENOMEM;
+ goto out_err;
+ }
+ nhe->nh_groups_cnt = RTA_PAYLOAD(tb[NHA_GROUP]) /
+ sizeof(struct nexthop_grp);
+ memcpy(nhe->nh_groups, RTA_DATA(tb[NHA_GROUP]),
+ RTA_PAYLOAD(tb[NHA_GROUP]));
+ }
+
+ if (tb[NHA_RES_GROUP]) {
+ parse_nh_res_group_rta(tb[NHA_RES_GROUP], &nhe->nh_res_grp);
+ nhe->nh_has_res_grp = true;
+ }
+
+ nhe->nh_blackhole = !!tb[NHA_BLACKHOLE];
+ nhe->nh_fdb = !!tb[NHA_FDB];
+
+ nhe->nh_family = nhm->nh_family;
+ nhe->nh_protocol = nhm->nh_protocol;
+ nhe->nh_scope = nhm->nh_scope;
+ nhe->nh_flags = nhm->nh_flags;
+
+ return 0;
+
+out_err:
+ ipnh_destroy_entry(nhe);
+ return err;
+}
+
+static void __print_nexthop_entry(FILE *fp, const char *jsobj,
+ struct nh_entry *nhe,
+ bool deleted)
+{
+ SPRINT_BUF(b1);
+
+ open_json_object(jsobj);
+
+ if (deleted)
+ print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+ print_uint(PRINT_ANY, "id", "id %u ", nhe->nh_id);
+
+ if (nhe->nh_groups)
+ print_nh_group(nhe);
+
+ print_nh_group_type(nhe->nh_grp_type);
+
+ if (nhe->nh_has_res_grp)
+ print_nh_res_group(&nhe->nh_res_grp);
+
+ if (nhe->nh_encap)
+ lwt_print_encap(fp, &nhe->nh_encap_type.rta, nhe->nh_encap);
+
+ if (nhe->nh_gateway_len)
+ __print_rta_gateway(fp, nhe->nh_family,
+ format_host(nhe->nh_family,
+ nhe->nh_gateway_len,
+ &nhe->nh_gateway));
+
+ if (nhe->nh_oif)
+ print_rta_ifidx(fp, nhe->nh_oif, "dev");
+
+ if (nhe->nh_scope != RT_SCOPE_UNIVERSE || show_details > 0) {
+ print_string(PRINT_ANY, "scope", "scope %s ",
+ rtnl_rtscope_n2a(nhe->nh_scope, b1, sizeof(b1)));
+ }
+
+ if (nhe->nh_blackhole)
+ print_null(PRINT_ANY, "blackhole", "blackhole ", NULL);
+
+ if (nhe->nh_protocol != RTPROT_UNSPEC || show_details > 0) {
+ print_string(PRINT_ANY, "protocol", "proto %s ",
+ rtnl_rtprot_n2a(nhe->nh_protocol, b1, sizeof(b1)));
+ }
+
+ print_rt_flags(fp, nhe->nh_flags);
+
+ if (nhe->nh_fdb)
+ print_null(PRINT_ANY, "fdb", "fdb", NULL);
+
+ close_json_object();
+}
+
+static int __ipnh_get_id(struct rtnl_handle *rthp, __u32 nh_id,
+ struct nlmsghdr **answer)
+{
+ struct {
+ struct nlmsghdr n;
+ struct nhmsg nhm;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_GETNEXTHOP,
+ .nhm.nh_family = preferred_family,
+ };
+
+ addattr32(&req.n, sizeof(req), NHA_ID, nh_id);
+
+ return rtnl_talk(rthp, &req.n, answer);
+}
+
+static struct hlist_head *ipnh_cache_head(__u32 nh_id)
+{
+ nh_id ^= nh_id >> 20;
+ nh_id ^= nh_id >> 10;
+
+ return &nh_cache[nh_id % NH_CACHE_SIZE];
+}
+
+static void ipnh_cache_link_entry(struct nh_entry *nhe)
+{
+ struct hlist_head *head = ipnh_cache_head(nhe->nh_id);
+
+ hlist_add_head(&nhe->nh_hash, head);
+}
+
+static void ipnh_cache_unlink_entry(struct nh_entry *nhe)
+{
+ hlist_del(&nhe->nh_hash);
+}
+
+static struct nh_entry *ipnh_cache_get(__u32 nh_id)
+{
+ struct hlist_head *head = ipnh_cache_head(nh_id);
+ struct nh_entry *nhe;
+ struct hlist_node *n;
+
+ hlist_for_each(n, head) {
+ nhe = container_of(n, struct nh_entry, nh_hash);
+ if (nhe->nh_id == nh_id)
+ return nhe;
+ }
+
+ return NULL;
+}
+
+static int __ipnh_cache_parse_nlmsg(const struct nlmsghdr *n,
+ struct nh_entry *nhe)
+{
+ int err, len;
+
+ len = n->nlmsg_len - NLMSG_SPACE(sizeof(struct nhmsg));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -EINVAL;
+ }
+
+ err = ipnh_parse_nhmsg(stderr, NLMSG_DATA(n), len, nhe);
+ if (err) {
+ fprintf(stderr, "Error parsing nexthop: %s\n", strerror(-err));
+ return err;
+ }
+
+ return 0;
+}
+
+static struct nh_entry *ipnh_cache_add(__u32 nh_id)
+{
+ struct nlmsghdr *answer = NULL;
+ struct nh_entry *nhe = NULL;
+
+ if (nh_cache_rth.fd < 0 && rtnl_open(&nh_cache_rth, 0) < 0) {
+ nh_cache_rth.fd = -1;
+ goto out;
+ }
+
+ if (__ipnh_get_id(&nh_cache_rth, nh_id, &answer) < 0)
+ goto out;
+
+ nhe = malloc(sizeof(*nhe));
+ if (!nhe)
+ goto out;
+
+ if (__ipnh_cache_parse_nlmsg(answer, nhe))
+ goto out_free_nhe;
+
+ ipnh_cache_link_entry(nhe);
+
+out:
+ free(answer);
+
+ return nhe;
+
+out_free_nhe:
+ free(nhe);
+ nhe = NULL;
+ goto out;
+}
+
+static void ipnh_cache_del(struct nh_entry *nhe)
+{
+ ipnh_cache_unlink_entry(nhe);
+ ipnh_destroy_entry(nhe);
+ free(nhe);
+}
+
+/* update, add or delete a nexthop entry based on nlmsghdr */
+static int ipnh_cache_process_nlmsg(const struct nlmsghdr *n,
+ struct nh_entry *new_nhe)
+{
+ struct nh_entry *nhe;
+
+ nhe = ipnh_cache_get(new_nhe->nh_id);
+ switch (n->nlmsg_type) {
+ case RTM_DELNEXTHOP:
+ if (nhe)
+ ipnh_cache_del(nhe);
+ ipnh_destroy_entry(new_nhe);
+ break;
+ case RTM_NEWNEXTHOP:
+ if (!nhe) {
+ nhe = malloc(sizeof(*nhe));
+ if (!nhe) {
+ ipnh_destroy_entry(new_nhe);
+ return -1;
+ }
+ } else {
+ /* this allows us to save 1 allocation on updates by
+ * reusing the old nh entry, but we need to cleanup its
+ * internal storage
+ */
+ ipnh_cache_unlink_entry(nhe);
+ ipnh_destroy_entry(nhe);
+ }
+ memcpy(nhe, new_nhe, sizeof(*nhe));
+ ipnh_cache_link_entry(nhe);
+ break;
+ }
+
+ return 0;
+}
+
+void print_cache_nexthop_id(FILE *fp, const char *fp_prefix, const char *jsobj,
+ __u32 nh_id)
+{
+ struct nh_entry *nhe = ipnh_cache_get(nh_id);
+
+ if (!nhe) {
+ nhe = ipnh_cache_add(nh_id);
+ if (!nhe)
+ return;
+ }
+
+ if (fp_prefix)
+ print_string(PRINT_FP, NULL, "%s", fp_prefix);
+ __print_nexthop_entry(fp, jsobj, nhe, false);
+}
+
+int print_cache_nexthop(struct nlmsghdr *n, void *arg, bool process_cache)
+{
+ struct nhmsg *nhm = NLMSG_DATA(n);
+ FILE *fp = (FILE *)arg;
+ struct nh_entry nhe;
+ int len, err;
+
+ if (n->nlmsg_type != RTM_DELNEXTHOP &&
+ n->nlmsg_type != RTM_NEWNEXTHOP) {
+ fprintf(stderr, "Not a nexthop: %08x %08x %08x\n",
+ n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+ return -1;
+ }
+
+ len = n->nlmsg_len - NLMSG_SPACE(sizeof(*nhm));
+ if (len < 0) {
+ close_json_object();
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ if (filter.proto && filter.proto != nhm->nh_protocol)
+ return 0;
+
+ err = ipnh_parse_nhmsg(fp, nhm, len, &nhe);
+ if (err) {
+ close_json_object();
+ fprintf(stderr, "Error parsing nexthop: %s\n", strerror(-err));
+ return -1;
+ }
+ __print_nexthop_entry(fp, NULL, &nhe, n->nlmsg_type == RTM_DELNEXTHOP);
+ print_string(PRINT_FP, NULL, "%s", "\n");
+ fflush(fp);
+
+ if (process_cache)
+ ipnh_cache_process_nlmsg(n, &nhe);
+ else
+ ipnh_destroy_entry(&nhe);
+
+ return 0;
+}
+
+static int print_nexthop_nocache(struct nlmsghdr *n, void *arg)
+{
+ return print_cache_nexthop(n, arg, false);
+}
+
+int print_nexthop_bucket(struct nlmsghdr *n, void *arg)
+{
+ struct nhmsg *nhm = NLMSG_DATA(n);
+ struct rtattr *tb[NHA_MAX+1];
+ FILE *fp = (FILE *)arg;
+ int len;
+
+ if (n->nlmsg_type != RTM_DELNEXTHOPBUCKET &&
+ n->nlmsg_type != RTM_NEWNEXTHOPBUCKET) {
+ fprintf(stderr, "Not a nexthop bucket: %08x %08x %08x\n",
+ n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+ return -1;
+ }
+
+ len = n->nlmsg_len - NLMSG_SPACE(sizeof(*nhm));
+ if (len < 0) {
+ close_json_object();
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ parse_rtattr_flags(tb, NHA_MAX, RTM_NHA(nhm), len, NLA_F_NESTED);
+
+ open_json_object(NULL);
+
+ if (n->nlmsg_type == RTM_DELNEXTHOP)
+ print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+ if (tb[NHA_ID])
+ print_uint(PRINT_ANY, "id", "id %u ",
+ rta_getattr_u32(tb[NHA_ID]));
+
+ if (tb[NHA_RES_BUCKET])
+ print_nh_res_bucket(fp, tb[NHA_RES_BUCKET]);
+
+ print_rt_flags(fp, nhm->nh_flags);
+
+ print_string(PRINT_FP, NULL, "%s", "\n");
+ close_json_object();
+ fflush(fp);
+
+ return 0;
+}
+
+static int add_nh_group_attr(struct nlmsghdr *n, int maxlen, char *argv)
+{
+ struct nexthop_grp *grps = NULL;
+ int count = 0, i;
+ int err = -1;
+ char *sep, *wsep;
+
+ if (*argv != '\0')
+ count = 1;
+
+ /* separator is '/' */
+ sep = strchr(argv, '/');
+ while (sep) {
+ count++;
+ sep = strchr(sep + 1, '/');
+ }
+
+ if (count == 0)
+ goto out;
+
+ grps = calloc(count, sizeof(*grps));
+ if (!grps)
+ goto out;
+
+ for (i = 0; i < count; ++i) {
+ sep = strchr(argv, '/');
+ if (sep)
+ *sep = '\0';
+
+ wsep = strchr(argv, ',');
+ if (wsep)
+ *wsep = '\0';
+
+ if (get_unsigned(&grps[i].id, argv, 0))
+ goto out;
+ if (wsep) {
+ unsigned int w;
+
+ wsep++;
+ if (get_unsigned(&w, wsep, 0) || w == 0 || w > 256)
+ invarg("\"weight\" is invalid\n", wsep);
+ grps[i].weight = w - 1;
+ }
+
+ if (!sep)
+ break;
+
+ argv = sep + 1;
+ }
+
+ err = addattr_l(n, maxlen, NHA_GROUP, grps, count * sizeof(*grps));
+out:
+ free(grps);
+ return err;
+}
+
+static int read_nh_group_type(const char *name)
+{
+ if (strcmp(name, "mpath") == 0)
+ return NEXTHOP_GRP_TYPE_MPATH;
+ else if (strcmp(name, "resilient") == 0)
+ return NEXTHOP_GRP_TYPE_RES;
+
+ return __NEXTHOP_GRP_TYPE_MAX;
+}
+
+static void parse_nh_group_type_res(struct nlmsghdr *n, int maxlen, int *argcp,
+ char ***argvp)
+{
+ char **argv = *argvp;
+ struct rtattr *nest;
+ int argc = *argcp;
+
+ if (!NEXT_ARG_OK())
+ return;
+
+ nest = addattr_nest(n, maxlen, NHA_RES_GROUP);
+ nest->rta_type |= NLA_F_NESTED;
+
+ NEXT_ARG_FWD();
+ while (argc > 0) {
+ if (strcmp(*argv, "buckets") == 0) {
+ __u16 buckets;
+
+ NEXT_ARG();
+ if (get_u16(&buckets, *argv, 0))
+ invarg("invalid buckets value", *argv);
+
+ addattr16(n, maxlen, NHA_RES_GROUP_BUCKETS, buckets);
+ } else if (strcmp(*argv, "idle_timer") == 0) {
+ __u32 idle_timer;
+
+ NEXT_ARG();
+ if (get_unsigned(&idle_timer, *argv, 0) ||
+ idle_timer >= UINT32_MAX / 100)
+ invarg("invalid idle timer value", *argv);
+
+ addattr32(n, maxlen, NHA_RES_GROUP_IDLE_TIMER,
+ idle_timer * 100);
+ } else if (strcmp(*argv, "unbalanced_timer") == 0) {
+ __u32 unbalanced_timer;
+
+ NEXT_ARG();
+ if (get_unsigned(&unbalanced_timer, *argv, 0) ||
+ unbalanced_timer >= UINT32_MAX / 100)
+ invarg("invalid unbalanced timer value", *argv);
+
+ addattr32(n, maxlen, NHA_RES_GROUP_UNBALANCED_TIMER,
+ unbalanced_timer * 100);
+ } else {
+ break;
+ }
+ argc--; argv++;
+ }
+
+ /* argv is currently the first unparsed argument, but ipnh_modify()
+ * will move to the next, so step back.
+ */
+ *argcp = argc + 1;
+ *argvp = argv - 1;
+
+ addattr_nest_end(n, nest);
+}
+
+static void parse_nh_group_type(struct nlmsghdr *n, int maxlen, int *argcp,
+ char ***argvp)
+{
+ char **argv = *argvp;
+ int argc = *argcp;
+ __u16 type;
+
+ NEXT_ARG();
+ type = read_nh_group_type(*argv);
+ if (type > NEXTHOP_GRP_TYPE_MAX)
+ invarg("\"type\" value is invalid\n", *argv);
+
+ switch (type) {
+ case NEXTHOP_GRP_TYPE_MPATH:
+ /* No additional arguments */
+ break;
+ case NEXTHOP_GRP_TYPE_RES:
+ parse_nh_group_type_res(n, maxlen, &argc, &argv);
+ break;
+ }
+
+ *argcp = argc;
+ *argvp = argv;
+
+ addattr16(n, maxlen, NHA_GROUP_TYPE, type);
+}
+
+static int ipnh_parse_id(const char *argv)
+{
+ __u32 id;
+
+ if (get_unsigned(&id, argv, 0))
+ invarg("invalid id value", argv);
+ return id;
+}
+
+static int ipnh_modify(int cmd, unsigned int flags, int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct nhmsg nhm;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST | flags,
+ .n.nlmsg_type = cmd,
+ .nhm.nh_family = preferred_family,
+ };
+ __u32 nh_flags = 0;
+ int ret;
+
+ while (argc > 0) {
+ if (!strcmp(*argv, "id")) {
+ NEXT_ARG();
+ addattr32(&req.n, sizeof(req), NHA_ID,
+ ipnh_parse_id(*argv));
+ } else if (!strcmp(*argv, "dev")) {
+ int ifindex;
+
+ NEXT_ARG();
+ ifindex = ll_name_to_index(*argv);
+ if (!ifindex)
+ invarg("Device does not exist\n", *argv);
+ addattr32(&req.n, sizeof(req), NHA_OIF, ifindex);
+ if (req.nhm.nh_family == AF_UNSPEC)
+ req.nhm.nh_family = AF_INET;
+ } else if (strcmp(*argv, "via") == 0) {
+ inet_prefix addr;
+ int family;
+
+ NEXT_ARG();
+ family = read_family(*argv);
+ if (family == AF_UNSPEC)
+ family = req.nhm.nh_family;
+ else
+ NEXT_ARG();
+ get_addr(&addr, *argv, family);
+ if (req.nhm.nh_family == AF_UNSPEC)
+ req.nhm.nh_family = addr.family;
+ else if (req.nhm.nh_family != addr.family)
+ invarg("address family mismatch\n", *argv);
+ addattr_l(&req.n, sizeof(req), NHA_GATEWAY,
+ &addr.data, addr.bytelen);
+ } else if (strcmp(*argv, "encap") == 0) {
+ char buf[1024];
+ struct rtattr *rta = (void *)buf;
+
+ rta->rta_type = NHA_ENCAP;
+ rta->rta_len = RTA_LENGTH(0);
+
+ lwt_parse_encap(rta, sizeof(buf), &argc, &argv,
+ NHA_ENCAP, NHA_ENCAP_TYPE);
+
+ if (rta->rta_len > RTA_LENGTH(0)) {
+ addraw_l(&req.n, 1024, RTA_DATA(rta),
+ RTA_PAYLOAD(rta));
+ }
+ } else if (!strcmp(*argv, "blackhole")) {
+ addattr_l(&req.n, sizeof(req), NHA_BLACKHOLE, NULL, 0);
+ if (req.nhm.nh_family == AF_UNSPEC)
+ req.nhm.nh_family = AF_INET;
+ } else if (!strcmp(*argv, "fdb")) {
+ addattr_l(&req.n, sizeof(req), NHA_FDB, NULL, 0);
+ } else if (!strcmp(*argv, "onlink")) {
+ nh_flags |= RTNH_F_ONLINK;
+ } else if (!strcmp(*argv, "group")) {
+ NEXT_ARG();
+
+ if (add_nh_group_attr(&req.n, sizeof(req), *argv))
+ invarg("\"group\" value is invalid\n", *argv);
+ } else if (!strcmp(*argv, "type")) {
+ parse_nh_group_type(&req.n, sizeof(req), &argc, &argv);
+ } else if (matches(*argv, "protocol") == 0) {
+ __u32 prot;
+
+ NEXT_ARG();
+ if (rtnl_rtprot_a2n(&prot, *argv))
+ invarg("\"protocol\" value is invalid\n", *argv);
+ req.nhm.nh_protocol = prot;
+ } else if (strcmp(*argv, "help") == 0) {
+ usage();
+ } else {
+ invarg("", *argv);
+ }
+ argc--; argv++;
+ }
+
+ req.nhm.nh_flags = nh_flags;
+
+ if (echo_request)
+ ret = rtnl_echo_talk(&rth, &req.n, json, print_nexthop_nocache);
+ else
+ ret = rtnl_talk(&rth, &req.n, NULL);
+
+ if (ret)
+ return -2;
+
+ return 0;
+}
+
+static int ipnh_get_id(__u32 id)
+{
+ struct nlmsghdr *answer;
+
+ if (__ipnh_get_id(&rth, id, &answer) < 0)
+ return -2;
+
+ new_json_obj(json);
+
+ if (print_nexthop_nocache(answer, (void *)stdout) < 0) {
+ delete_json_obj();
+ free(answer);
+ return -1;
+ }
+
+ delete_json_obj();
+ fflush(stdout);
+
+ free(answer);
+
+ return 0;
+}
+
+static int ipnh_list_flush_id(__u32 id, int action)
+{
+ int err;
+
+ if (action == IPNH_LIST)
+ return ipnh_get_id(id);
+
+ if (rtnl_open(&rth_del, 0) < 0) {
+ fprintf(stderr, "Cannot open rtnetlink\n");
+ return EXIT_FAILURE;
+ }
+
+ err = delete_nexthop(id);
+ rtnl_close(&rth_del);
+
+ return err;
+}
+
+static int ipnh_list_flush(int argc, char **argv, int action)
+{
+ unsigned int all = (argc == 0);
+
+ while (argc > 0) {
+ if (!matches(*argv, "dev")) {
+ NEXT_ARG();
+ filter.ifindex = ll_name_to_index(*argv);
+ if (!filter.ifindex)
+ invarg("Device does not exist\n", *argv);
+ } else if (!matches(*argv, "groups")) {
+ filter.groups = 1;
+ } else if (!matches(*argv, "master")) {
+ NEXT_ARG();
+ filter.master = ll_name_to_index(*argv);
+ if (!filter.master)
+ invarg("Device does not exist\n", *argv);
+ } else if (matches(*argv, "vrf") == 0) {
+ NEXT_ARG();
+ if (!name_is_vrf(*argv))
+ invarg("Invalid VRF\n", *argv);
+ filter.master = ll_name_to_index(*argv);
+ if (!filter.master)
+ invarg("VRF does not exist\n", *argv);
+ } else if (!strcmp(*argv, "id")) {
+ NEXT_ARG();
+ return ipnh_list_flush_id(ipnh_parse_id(*argv), action);
+ } else if (!matches(*argv, "protocol")) {
+ __u32 proto;
+
+ NEXT_ARG();
+ if (get_unsigned(&proto, *argv, 0))
+ invarg("invalid protocol value", *argv);
+ filter.proto = proto;
+ } else if (!matches(*argv, "fdb")) {
+ filter.fdb = 1;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ invarg("", *argv);
+ }
+ argc--; argv++;
+ }
+
+ if (action == IPNH_FLUSH)
+ return ipnh_flush(all);
+
+ if (rtnl_nexthopdump_req(&rth, preferred_family, nh_dump_filter) < 0) {
+ perror("Cannot send dump request");
+ return -2;
+ }
+
+ new_json_obj(json);
+
+ if (rtnl_dump_filter(&rth, print_nexthop_nocache, stdout) < 0) {
+ delete_json_obj();
+ fprintf(stderr, "Dump terminated\n");
+ return -2;
+ }
+
+ delete_json_obj();
+ fflush(stdout);
+
+ return 0;
+}
+
+static int ipnh_get(int argc, char **argv)
+{
+ __u32 id = 0;
+
+ while (argc > 0) {
+ if (!strcmp(*argv, "id")) {
+ NEXT_ARG();
+ id = ipnh_parse_id(*argv);
+ } else {
+ usage();
+ }
+ argc--; argv++;
+ }
+
+ if (!id) {
+ usage();
+ return -1;
+ }
+
+ return ipnh_get_id(id);
+}
+
+static int ipnh_bucket_list(int argc, char **argv)
+{
+ while (argc > 0) {
+ if (!matches(*argv, "dev")) {
+ NEXT_ARG();
+ filter.ifindex = ll_name_to_index(*argv);
+ if (!filter.ifindex)
+ invarg("Device does not exist\n", *argv);
+ } else if (!matches(*argv, "master")) {
+ NEXT_ARG();
+ filter.master = ll_name_to_index(*argv);
+ if (!filter.master)
+ invarg("Device does not exist\n", *argv);
+ } else if (matches(*argv, "vrf") == 0) {
+ NEXT_ARG();
+ if (!name_is_vrf(*argv))
+ invarg("Invalid VRF\n", *argv);
+ filter.master = ll_name_to_index(*argv);
+ if (!filter.master)
+ invarg("VRF does not exist\n", *argv);
+ } else if (!strcmp(*argv, "id")) {
+ NEXT_ARG();
+ filter.id = ipnh_parse_id(*argv);
+ } else if (!strcmp(*argv, "nhid")) {
+ NEXT_ARG();
+ filter.nhid = ipnh_parse_id(*argv);
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ invarg("", *argv);
+ }
+ argc--; argv++;
+ }
+
+ if (rtnl_nexthop_bucket_dump_req(&rth, preferred_family,
+ nh_dump_bucket_filter) < 0) {
+ perror("Cannot send dump request");
+ return -2;
+ }
+
+ new_json_obj(json);
+
+ if (rtnl_dump_filter(&rth, print_nexthop_bucket, stdout) < 0) {
+ delete_json_obj();
+ fprintf(stderr, "Dump terminated\n");
+ return -2;
+ }
+
+ delete_json_obj();
+ fflush(stdout);
+
+ return 0;
+}
+
+static int ipnh_bucket_get_id(__u32 id, __u16 bucket_index)
+{
+ struct {
+ struct nlmsghdr n;
+ struct nhmsg nhm;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_GETNEXTHOPBUCKET,
+ .nhm.nh_family = preferred_family,
+ };
+ struct nlmsghdr *answer;
+ struct rtattr *nest;
+
+ addattr32(&req.n, sizeof(req), NHA_ID, id);
+
+ nest = addattr_nest(&req.n, sizeof(req), NHA_RES_BUCKET);
+ nest->rta_type |= NLA_F_NESTED;
+
+ addattr16(&req.n, sizeof(req), NHA_RES_BUCKET_INDEX, bucket_index);
+
+ addattr_nest_end(&req.n, nest);
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0)
+ return -2;
+
+ new_json_obj(json);
+
+ if (print_nexthop_bucket(answer, (void *)stdout) < 0) {
+ delete_json_obj();
+ free(answer);
+ return -1;
+ }
+
+ delete_json_obj();
+ fflush(stdout);
+
+ free(answer);
+
+ return 0;
+}
+
+static int ipnh_bucket_get(int argc, char **argv)
+{
+ bool bucket_valid = false;
+ __u16 bucket_index;
+ __u32 id = 0;
+
+ while (argc > 0) {
+ if (!strcmp(*argv, "id")) {
+ NEXT_ARG();
+ id = ipnh_parse_id(*argv);
+ } else if (!strcmp(*argv, "index")) {
+ NEXT_ARG();
+ if (get_u16(&bucket_index, *argv, 0))
+ invarg("invalid bucket index value", *argv);
+ bucket_valid = true;
+ } else {
+ usage();
+ }
+ argc--; argv++;
+ }
+
+ if (!id || !bucket_valid) {
+ usage();
+ return -1;
+ }
+
+ return ipnh_bucket_get_id(id, bucket_index);
+}
+
+static int do_ipnh_bucket(int argc, char **argv)
+{
+ if (argc < 1)
+ return ipnh_bucket_list(0, NULL);
+
+ if (!matches(*argv, "list") ||
+ !matches(*argv, "show") ||
+ !matches(*argv, "lst"))
+ return ipnh_bucket_list(argc-1, argv+1);
+
+ if (!matches(*argv, "get"))
+ return ipnh_bucket_get(argc-1, argv+1);
+
+ if (!matches(*argv, "help"))
+ usage();
+
+ fprintf(stderr,
+ "Command \"%s\" is unknown, try \"ip nexthop help\".\n", *argv);
+ exit(-1);
+}
+
+int do_ipnh(int argc, char **argv)
+{
+ if (argc < 1)
+ return ipnh_list_flush(0, NULL, IPNH_LIST);
+
+ if (!matches(*argv, "add"))
+ return ipnh_modify(RTM_NEWNEXTHOP, NLM_F_CREATE|NLM_F_EXCL,
+ argc-1, argv+1);
+ if (!matches(*argv, "replace"))
+ return ipnh_modify(RTM_NEWNEXTHOP, NLM_F_CREATE|NLM_F_REPLACE,
+ argc-1, argv+1);
+ if (!matches(*argv, "delete"))
+ return ipnh_modify(RTM_DELNEXTHOP, 0, argc-1, argv+1);
+
+ if (!matches(*argv, "list") ||
+ !matches(*argv, "show") ||
+ !matches(*argv, "lst"))
+ return ipnh_list_flush(argc-1, argv+1, IPNH_LIST);
+
+ if (!matches(*argv, "get"))
+ return ipnh_get(argc-1, argv+1);
+
+ if (!matches(*argv, "flush"))
+ return ipnh_list_flush(argc-1, argv+1, IPNH_FLUSH);
+
+ if (!matches(*argv, "bucket"))
+ return do_ipnh_bucket(argc-1, argv+1);
+
+ if (!matches(*argv, "help"))
+ usage();
+
+ fprintf(stderr,
+ "Command \"%s\" is unknown, try \"ip nexthop help\".\n", *argv);
+ exit(-1);
+}
diff --git a/ip/ipntable.c b/ip/ipntable.c
new file mode 100644
index 0000000..4ce02a3
--- /dev/null
+++ b/ip/ipntable.c
@@ -0,0 +1,687 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C)2006 USAGI/WIDE Project
+ *
+ * based on ipneigh.c
+ *
+ * Authors:
+ * Masahide NAKAMURA @USAGI
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <time.h>
+
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+static struct
+{
+ int family;
+ int index;
+#define NONE_DEV (-1)
+ const char *name;
+} filter;
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip ntable change name NAME [ dev DEV ]\n"
+ " [ thresh1 VAL ] [ thresh2 VAL ] [ thresh3 VAL ] [ gc_int MSEC ]\n"
+ " [ PARMS ]\n"
+ "Usage: ip ntable show [ dev DEV ] [ name NAME ]\n"
+
+ "PARMS := [ base_reachable MSEC ] [ retrans MSEC ] [ gc_stale MSEC ]\n"
+ " [ delay_probe MSEC ] [ queue LEN ]\n"
+ " [ app_probes VAL ] [ ucast_probes VAL ] [ mcast_probes VAL ]\n"
+ " [ anycast_delay MSEC ] [ proxy_delay MSEC ] [ proxy_queue LEN ]\n"
+ " [ locktime MSEC ]\n"
+ );
+
+ exit(-1);
+}
+
+static int ipntable_modify(int cmd, int flags, int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct ndtmsg ndtm;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndtmsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST | flags,
+ .n.nlmsg_type = cmd,
+ .ndtm.ndtm_family = preferred_family,
+ };
+ char *namep = NULL;
+ char *threshsp = NULL;
+ char *gc_intp = NULL;
+ char parms_buf[1024] = {};
+ struct rtattr *parms_rta = (struct rtattr *)parms_buf;
+ int parms_change = 0;
+
+ parms_rta->rta_type = NDTA_PARMS;
+ parms_rta->rta_len = RTA_LENGTH(0);
+
+ while (argc > 0) {
+ if (strcmp(*argv, "name") == 0) {
+ int len;
+
+ NEXT_ARG();
+ if (namep)
+ duparg("NAME", *argv);
+
+ namep = *argv;
+ len = strlen(namep) + 1;
+ addattr_l(&req.n, sizeof(req), NDTA_NAME, namep, len);
+ } else if (strcmp(*argv, "thresh1") == 0) {
+ __u32 thresh1;
+
+ NEXT_ARG();
+ threshsp = *argv;
+
+ if (get_u32(&thresh1, *argv, 0))
+ invarg("\"thresh1\" value is invalid", *argv);
+
+ addattr32(&req.n, sizeof(req), NDTA_THRESH1, thresh1);
+ } else if (strcmp(*argv, "thresh2") == 0) {
+ __u32 thresh2;
+
+ NEXT_ARG();
+ threshsp = *argv;
+
+ if (get_u32(&thresh2, *argv, 0))
+ invarg("\"thresh2\" value is invalid", *argv);
+
+ addattr32(&req.n, sizeof(req), NDTA_THRESH2, thresh2);
+ } else if (strcmp(*argv, "thresh3") == 0) {
+ __u32 thresh3;
+
+ NEXT_ARG();
+ threshsp = *argv;
+
+ if (get_u32(&thresh3, *argv, 0))
+ invarg("\"thresh3\" value is invalid", *argv);
+
+ addattr32(&req.n, sizeof(req), NDTA_THRESH3, thresh3);
+ } else if (strcmp(*argv, "gc_int") == 0) {
+ __u64 gc_int;
+
+ NEXT_ARG();
+ gc_intp = *argv;
+
+ if (get_u64(&gc_int, *argv, 0))
+ invarg("\"gc_int\" value is invalid", *argv);
+
+ addattr_l(&req.n, sizeof(req), NDTA_GC_INTERVAL,
+ &gc_int, sizeof(gc_int));
+ } else if (strcmp(*argv, "dev") == 0) {
+ __u32 ifindex;
+
+ NEXT_ARG();
+ ifindex = ll_name_to_index(*argv);
+ if (!ifindex)
+ return nodev(*argv);
+
+ rta_addattr32(parms_rta, sizeof(parms_buf),
+ NDTPA_IFINDEX, ifindex);
+ } else if (strcmp(*argv, "base_reachable") == 0) {
+ __u64 breachable;
+
+ NEXT_ARG();
+
+ if (get_u64(&breachable, *argv, 0))
+ invarg("\"base_reachable\" value is invalid", *argv);
+
+ rta_addattr_l(parms_rta, sizeof(parms_buf),
+ NDTPA_BASE_REACHABLE_TIME,
+ &breachable, sizeof(breachable));
+ parms_change = 1;
+ } else if (strcmp(*argv, "retrans") == 0) {
+ __u64 retrans;
+
+ NEXT_ARG();
+
+ if (get_u64(&retrans, *argv, 0))
+ invarg("\"retrans\" value is invalid", *argv);
+
+ rta_addattr_l(parms_rta, sizeof(parms_buf),
+ NDTPA_RETRANS_TIME,
+ &retrans, sizeof(retrans));
+ parms_change = 1;
+ } else if (strcmp(*argv, "gc_stale") == 0) {
+ __u64 gc_stale;
+
+ NEXT_ARG();
+
+ if (get_u64(&gc_stale, *argv, 0))
+ invarg("\"gc_stale\" value is invalid", *argv);
+
+ rta_addattr_l(parms_rta, sizeof(parms_buf),
+ NDTPA_GC_STALETIME,
+ &gc_stale, sizeof(gc_stale));
+ parms_change = 1;
+ } else if (strcmp(*argv, "delay_probe") == 0) {
+ __u64 delay_probe;
+
+ NEXT_ARG();
+
+ if (get_u64(&delay_probe, *argv, 0))
+ invarg("\"delay_probe\" value is invalid", *argv);
+
+ rta_addattr_l(parms_rta, sizeof(parms_buf),
+ NDTPA_DELAY_PROBE_TIME,
+ &delay_probe, sizeof(delay_probe));
+ parms_change = 1;
+ } else if (strcmp(*argv, "queue") == 0) {
+ __u32 queue;
+
+ NEXT_ARG();
+
+ if (get_u32(&queue, *argv, 0))
+ invarg("\"queue\" value is invalid", *argv);
+
+ rta_addattr32(parms_rta, sizeof(parms_buf),
+ NDTPA_QUEUE_LEN, queue);
+ parms_change = 1;
+ } else if (strcmp(*argv, "app_probes") == 0) {
+ __u32 aprobe;
+
+ NEXT_ARG();
+
+ if (get_u32(&aprobe, *argv, 0))
+ invarg("\"app_probes\" value is invalid", *argv);
+
+ rta_addattr32(parms_rta, sizeof(parms_buf),
+ NDTPA_APP_PROBES, aprobe);
+ parms_change = 1;
+ } else if (strcmp(*argv, "ucast_probes") == 0) {
+ __u32 uprobe;
+
+ NEXT_ARG();
+
+ if (get_u32(&uprobe, *argv, 0))
+ invarg("\"ucast_probes\" value is invalid", *argv);
+
+ rta_addattr32(parms_rta, sizeof(parms_buf),
+ NDTPA_UCAST_PROBES, uprobe);
+ parms_change = 1;
+ } else if (strcmp(*argv, "mcast_probes") == 0) {
+ __u32 mprobe;
+
+ NEXT_ARG();
+
+ if (get_u32(&mprobe, *argv, 0))
+ invarg("\"mcast_probes\" value is invalid", *argv);
+
+ rta_addattr32(parms_rta, sizeof(parms_buf),
+ NDTPA_MCAST_PROBES, mprobe);
+ parms_change = 1;
+ } else if (strcmp(*argv, "anycast_delay") == 0) {
+ __u64 anycast_delay;
+
+ NEXT_ARG();
+
+ if (get_u64(&anycast_delay, *argv, 0))
+ invarg("\"anycast_delay\" value is invalid", *argv);
+
+ rta_addattr_l(parms_rta, sizeof(parms_buf),
+ NDTPA_ANYCAST_DELAY,
+ &anycast_delay, sizeof(anycast_delay));
+ parms_change = 1;
+ } else if (strcmp(*argv, "proxy_delay") == 0) {
+ __u64 proxy_delay;
+
+ NEXT_ARG();
+
+ if (get_u64(&proxy_delay, *argv, 0))
+ invarg("\"proxy_delay\" value is invalid", *argv);
+
+ rta_addattr_l(parms_rta, sizeof(parms_buf),
+ NDTPA_PROXY_DELAY,
+ &proxy_delay, sizeof(proxy_delay));
+ parms_change = 1;
+ } else if (strcmp(*argv, "proxy_queue") == 0) {
+ __u32 pqueue;
+
+ NEXT_ARG();
+
+ if (get_u32(&pqueue, *argv, 0))
+ invarg("\"proxy_queue\" value is invalid", *argv);
+
+ rta_addattr32(parms_rta, sizeof(parms_buf),
+ NDTPA_PROXY_QLEN, pqueue);
+ parms_change = 1;
+ } else if (strcmp(*argv, "locktime") == 0) {
+ __u64 locktime;
+
+ NEXT_ARG();
+
+ if (get_u64(&locktime, *argv, 0))
+ invarg("\"locktime\" value is invalid", *argv);
+
+ rta_addattr_l(parms_rta, sizeof(parms_buf),
+ NDTPA_LOCKTIME,
+ &locktime, sizeof(locktime));
+ parms_change = 1;
+ } else {
+ invarg("unknown", *argv);
+ }
+
+ argc--; argv++;
+ }
+
+ if (!namep)
+ missarg("NAME");
+ if (!threshsp && !gc_intp && !parms_change) {
+ fprintf(stderr, "Not enough information: changeable attributes required.\n");
+ exit(-1);
+ }
+
+ if (parms_rta->rta_len > RTA_LENGTH(0)) {
+ addattr_l(&req.n, sizeof(req), NDTA_PARMS, RTA_DATA(parms_rta),
+ RTA_PAYLOAD(parms_rta));
+ }
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ exit(2);
+
+ return 0;
+}
+
+static const char *ntable_strtime_delta(__u32 msec)
+{
+ static char str[32];
+ struct timeval now = {};
+ time_t t;
+ struct tm *tp;
+
+ if (msec == 0)
+ goto error;
+
+ if (gettimeofday(&now, NULL) < 0) {
+ perror("gettimeofday");
+ goto error;
+ }
+
+ t = now.tv_sec - (msec / 1000);
+ tp = localtime(&t);
+ if (!tp)
+ goto error;
+
+ strftime(str, sizeof(str), "%Y-%m-%d %T", tp);
+
+ return str;
+ error:
+ strcpy(str, "(error)");
+ return str;
+}
+
+static void print_ndtconfig(const struct ndt_config *ndtc)
+{
+
+ print_uint(PRINT_ANY, "key_length",
+ " config key_len %u ", ndtc->ndtc_key_len);
+ print_uint(PRINT_ANY, "entry_size",
+ "entry_size %u ", ndtc->ndtc_entry_size);
+ print_uint(PRINT_ANY, "entries", "entries %u ", ndtc->ndtc_entries);
+
+ print_nl();
+
+ print_string(PRINT_ANY, "last_flush",
+ " last_flush %s ",
+ ntable_strtime_delta(ndtc->ndtc_last_flush));
+ print_string(PRINT_ANY, "last_rand",
+ "last_rand %s ",
+ ntable_strtime_delta(ndtc->ndtc_last_rand));
+
+ print_nl();
+
+ print_uint(PRINT_ANY, "hash_rnd",
+ " hash_rnd %u ", ndtc->ndtc_hash_rnd);
+ print_0xhex(PRINT_ANY, "hash_mask",
+ "hash_mask %08llx ", ndtc->ndtc_hash_mask);
+
+ print_uint(PRINT_ANY, "hash_chain_gc",
+ "hash_chain_gc %u ", ndtc->ndtc_hash_chain_gc);
+ print_uint(PRINT_ANY, "proxy_qlen",
+ "proxy_qlen %u ", ndtc->ndtc_proxy_qlen);
+
+ print_nl();
+}
+
+static void print_ndtparams(struct rtattr *tpb[])
+{
+
+ if (tpb[NDTPA_IFINDEX]) {
+ __u32 ifindex = rta_getattr_u32(tpb[NDTPA_IFINDEX]);
+
+ print_string(PRINT_FP, NULL, " dev ", NULL);
+ print_color_string(PRINT_ANY, COLOR_IFNAME,
+ "dev", "%s ", ll_index_to_name(ifindex));
+ print_nl();
+ }
+
+ print_string(PRINT_FP, NULL, " ", NULL);
+ if (tpb[NDTPA_REFCNT]) {
+ __u32 refcnt = rta_getattr_u32(tpb[NDTPA_REFCNT]);
+
+ print_uint(PRINT_ANY, "refcnt", "refcnt %u ", refcnt);
+ }
+
+ if (tpb[NDTPA_REACHABLE_TIME]) {
+ __u64 reachable = rta_getattr_u64(tpb[NDTPA_REACHABLE_TIME]);
+
+ print_u64(PRINT_ANY, "reachable",
+ "reachable %llu ", reachable);
+ }
+
+ if (tpb[NDTPA_BASE_REACHABLE_TIME]) {
+ __u64 breachable
+ = rta_getattr_u64(tpb[NDTPA_BASE_REACHABLE_TIME]);
+
+ print_u64(PRINT_ANY, "base_reachable",
+ "base_reachable %llu ", breachable);
+ }
+
+ if (tpb[NDTPA_RETRANS_TIME]) {
+ __u64 retrans = rta_getattr_u64(tpb[NDTPA_RETRANS_TIME]);
+
+ print_u64(PRINT_ANY, "retrans", "retrans %llu ", retrans);
+ }
+
+ print_string(PRINT_FP, NULL, "%s ", _SL_);
+
+ if (tpb[NDTPA_GC_STALETIME]) {
+ __u64 gc_stale = rta_getattr_u64(tpb[NDTPA_GC_STALETIME]);
+
+ print_u64(PRINT_ANY, "gc_stale", "gc_stale %llu ", gc_stale);
+ }
+
+ if (tpb[NDTPA_DELAY_PROBE_TIME]) {
+ __u64 delay_probe
+ = rta_getattr_u64(tpb[NDTPA_DELAY_PROBE_TIME]);
+
+ print_u64(PRINT_ANY, "delay_probe",
+ "delay_probe %llu ", delay_probe);
+ }
+
+ if (tpb[NDTPA_QUEUE_LEN]) {
+ __u32 queue = rta_getattr_u32(tpb[NDTPA_QUEUE_LEN]);
+
+ print_uint(PRINT_ANY, "queue", "queue %u ", queue);
+ }
+
+ print_string(PRINT_FP, NULL, "%s ", _SL_);
+
+ if (tpb[NDTPA_APP_PROBES]) {
+ __u32 aprobe = rta_getattr_u32(tpb[NDTPA_APP_PROBES]);
+
+ print_uint(PRINT_ANY, "app_probes", "app_probes %u ", aprobe);
+ }
+
+ if (tpb[NDTPA_UCAST_PROBES]) {
+ __u32 uprobe = rta_getattr_u32(tpb[NDTPA_UCAST_PROBES]);
+
+ print_uint(PRINT_ANY, "ucast_probes",
+ "ucast_probes %u ", uprobe);
+ }
+
+ if (tpb[NDTPA_MCAST_PROBES]) {
+ __u32 mprobe = rta_getattr_u32(tpb[NDTPA_MCAST_PROBES]);
+
+ print_uint(PRINT_ANY, "mcast_probes",
+ "mcast_probes %u ", mprobe);
+ }
+
+ print_string(PRINT_FP, NULL, "%s ", _SL_);
+
+ if (tpb[NDTPA_ANYCAST_DELAY]) {
+ __u64 anycast_delay = rta_getattr_u64(tpb[NDTPA_ANYCAST_DELAY]);
+
+ print_u64(PRINT_ANY, "anycast_delay",
+ "anycast_delay %llu ", anycast_delay);
+ }
+
+ if (tpb[NDTPA_PROXY_DELAY]) {
+ __u64 proxy_delay = rta_getattr_u64(tpb[NDTPA_PROXY_DELAY]);
+
+ print_u64(PRINT_ANY, "proxy_delay",
+ "proxy_delay %llu ", proxy_delay);
+ }
+
+ if (tpb[NDTPA_PROXY_QLEN]) {
+ __u32 pqueue = rta_getattr_u32(tpb[NDTPA_PROXY_QLEN]);
+
+ print_uint(PRINT_ANY, "proxy_queue", "proxy_queue %u ", pqueue);
+ }
+
+ if (tpb[NDTPA_LOCKTIME]) {
+ __u64 locktime = rta_getattr_u64(tpb[NDTPA_LOCKTIME]);
+
+ print_u64(PRINT_ANY, "locktime", "locktime %llu ", locktime);
+ }
+
+ print_nl();
+}
+
+static void print_ndtstats(const struct ndt_stats *ndts)
+{
+
+ print_string(PRINT_FP, NULL, " stats ", NULL);
+
+ print_u64(PRINT_ANY, "allocs", "allocs %llu ", ndts->ndts_allocs);
+ print_u64(PRINT_ANY, "destroys", "destroys %llu ",
+ ndts->ndts_destroys);
+ print_u64(PRINT_ANY, "hash_grows", "hash_grows %llu ",
+ ndts->ndts_hash_grows);
+
+ print_string(PRINT_FP, NULL, "%s ", _SL_);
+
+ print_u64(PRINT_ANY, "res_failed", "res_failed %llu ",
+ ndts->ndts_res_failed);
+ print_u64(PRINT_ANY, "lookups", "lookups %llu ", ndts->ndts_lookups);
+ print_u64(PRINT_ANY, "hits", "hits %llu ", ndts->ndts_hits);
+
+ print_string(PRINT_FP, NULL, "%s ", _SL_);
+
+ print_u64(PRINT_ANY, "rcv_probes_mcast", "rcv_probes_mcast %llu ",
+ ndts->ndts_rcv_probes_mcast);
+ print_u64(PRINT_ANY, "rcv_probes_ucast", "rcv_probes_ucast %llu ",
+ ndts->ndts_rcv_probes_ucast);
+
+ print_string(PRINT_FP, NULL, "%s ", _SL_);
+
+ print_u64(PRINT_ANY, "periodic_gc_runs", "periodic_gc_runs %llu ",
+ ndts->ndts_periodic_gc_runs);
+ print_u64(PRINT_ANY, "forced_gc_runs", "forced_gc_runs %llu ",
+ ndts->ndts_forced_gc_runs);
+
+ print_string(PRINT_FP, NULL, "%s ", _SL_);
+
+ print_u64(PRINT_ANY, "table_fulls", "table_fulls %llu ",
+ ndts->ndts_table_fulls);
+
+ print_nl();
+}
+
+static int print_ntable(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+ struct ndtmsg *ndtm = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *tb[NDTA_MAX+1];
+ struct rtattr *tpb[NDTPA_MAX+1];
+ int ret;
+
+ if (n->nlmsg_type != RTM_NEWNEIGHTBL) {
+ fprintf(stderr, "Not NEIGHTBL: %08x %08x %08x\n",
+ n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+ return 0;
+ }
+ len -= NLMSG_LENGTH(sizeof(*ndtm));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ if (preferred_family && preferred_family != ndtm->ndtm_family)
+ return 0;
+
+ parse_rtattr(tb, NDTA_MAX, NDTA_RTA(ndtm),
+ n->nlmsg_len - NLMSG_LENGTH(sizeof(*ndtm)));
+
+ if (tb[NDTA_NAME]) {
+ const char *name = rta_getattr_str(tb[NDTA_NAME]);
+
+ if (filter.name && strcmp(filter.name, name))
+ return 0;
+ }
+
+ if (tb[NDTA_PARMS]) {
+ parse_rtattr(tpb, NDTPA_MAX, RTA_DATA(tb[NDTA_PARMS]),
+ RTA_PAYLOAD(tb[NDTA_PARMS]));
+
+ if (tpb[NDTPA_IFINDEX]) {
+ __u32 ifindex = rta_getattr_u32(tpb[NDTPA_IFINDEX]);
+
+ if (filter.index && filter.index != ifindex)
+ return 0;
+ } else {
+ if (filter.index && filter.index != NONE_DEV)
+ return 0;
+ }
+ }
+
+ open_json_object(NULL);
+ print_string(PRINT_ANY, "family",
+ "%s ", family_name(ndtm->ndtm_family));
+
+ if (tb[NDTA_NAME]) {
+ const char *name = rta_getattr_str(tb[NDTA_NAME]);
+
+ print_string(PRINT_ANY, "name", "%s ", name);
+ }
+
+ print_nl();
+
+ ret = (tb[NDTA_THRESH1] || tb[NDTA_THRESH2] || tb[NDTA_THRESH3] ||
+ tb[NDTA_GC_INTERVAL]);
+ if (ret)
+ print_string(PRINT_FP, NULL, " ", NULL);
+
+ if (tb[NDTA_THRESH1]) {
+ __u32 thresh1 = rta_getattr_u32(tb[NDTA_THRESH1]);
+
+ print_uint(PRINT_ANY, "thresh1", "thresh1 %u ", thresh1);
+ }
+
+ if (tb[NDTA_THRESH2]) {
+ __u32 thresh2 = rta_getattr_u32(tb[NDTA_THRESH2]);
+
+ print_uint(PRINT_ANY, "thresh2", "thresh2 %u ", thresh2);
+ }
+
+ if (tb[NDTA_THRESH3]) {
+ __u32 thresh3 = rta_getattr_u32(tb[NDTA_THRESH3]);
+
+ print_uint(PRINT_ANY, "thresh3", "thresh3 %u ", thresh3);
+ }
+
+ if (tb[NDTA_GC_INTERVAL]) {
+ __u64 gc_int = rta_getattr_u64(tb[NDTA_GC_INTERVAL]);
+
+ print_u64(PRINT_ANY, "gc_interval", "gc_int %llu ", gc_int);
+ }
+
+ if (ret)
+ print_nl();
+
+ if (tb[NDTA_CONFIG] && show_stats)
+ print_ndtconfig(RTA_DATA(tb[NDTA_CONFIG]));
+
+ if (tb[NDTA_PARMS])
+ print_ndtparams(tpb);
+
+ if (tb[NDTA_STATS] && show_stats)
+ print_ndtstats(RTA_DATA(tb[NDTA_STATS]));
+
+ print_string(PRINT_FP, NULL, "\n", "");
+ close_json_object();
+ fflush(fp);
+
+ return 0;
+}
+
+static void ipntable_reset_filter(void)
+{
+ memset(&filter, 0, sizeof(filter));
+}
+
+static int ipntable_show(int argc, char **argv)
+{
+ ipntable_reset_filter();
+
+ filter.family = preferred_family;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+
+ if (strcmp("none", *argv) == 0)
+ filter.index = NONE_DEV;
+ else if ((filter.index = ll_name_to_index(*argv)) == 0)
+ invarg("\"DEV\" is invalid", *argv);
+ } else if (strcmp(*argv, "name") == 0) {
+ NEXT_ARG();
+
+ filter.name = *argv;
+ } else
+ invarg("unknown", *argv);
+
+ argc--; argv++;
+ }
+
+ if (rtnl_neightbldump_req(&rth, preferred_family) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&rth, print_ntable, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ delete_json_obj();
+
+ return 0;
+}
+
+int do_ipntable(int argc, char **argv)
+{
+ ll_init_map(&rth);
+
+ if (argc > 0) {
+ if (matches(*argv, "change") == 0 ||
+ matches(*argv, "chg") == 0)
+ return ipntable_modify(RTM_SETNEIGHTBL,
+ NLM_F_REPLACE,
+ argc-1, argv+1);
+ if (matches(*argv, "show") == 0 ||
+ matches(*argv, "lst") == 0 ||
+ matches(*argv, "list") == 0)
+ return ipntable_show(argc-1, argv+1);
+ if (matches(*argv, "help") == 0)
+ usage();
+ } else
+ return ipntable_show(0, NULL);
+
+ fprintf(stderr, "Command \"%s\" is unknown, try \"ip ntable help\".\n", *argv);
+ exit(-1);
+}
diff --git a/ip/ipprefix.c b/ip/ipprefix.c
new file mode 100644
index 0000000..c5704e5
--- /dev/null
+++ b/ip/ipprefix.c
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C)2005 USAGI/WIDE Project
+ *
+ *
+ * based on ip.c, iproute.c
+ *
+ * Authors:
+ * Masahide NAKAMURA @USAGI
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/icmp6.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+/* prefix flags; see kernel's net/ipv6/addrconf.c and include/net/if_inet6.h */
+#define IF_PREFIX_ONLINK 0x01
+#define IF_PREFIX_AUTOCONF 0x02
+
+int print_prefix(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+ struct prefixmsg *prefix = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *tb[RTA_MAX+1];
+ int family = preferred_family;
+
+ if (n->nlmsg_type != RTM_NEWPREFIX) {
+ fprintf(stderr, "Not a prefix: %08x %08x %08x\n",
+ n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+ return 0;
+ }
+
+ len -= NLMSG_LENGTH(sizeof(*prefix));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ if (family == AF_UNSPEC)
+ family = AF_INET6;
+ if (family != AF_INET6)
+ return 0;
+
+ if (prefix->prefix_family != AF_INET6) {
+ fprintf(stderr, "incorrect protocol family: %d\n", prefix->prefix_family);
+ return 0;
+ }
+ if (prefix->prefix_type != ND_OPT_PREFIX_INFORMATION) {
+ fprintf(stderr, "wrong ND type %d\n", prefix->prefix_type);
+ return 0;
+ }
+
+ parse_rtattr(tb, RTA_MAX, RTM_RTA(prefix), len);
+
+ if (tb[PREFIX_ADDRESS]) {
+ fprintf(fp, "prefix %s/%u",
+ rt_addr_n2a_rta(family, tb[PREFIX_ADDRESS]),
+ prefix->prefix_len);
+ }
+ fprintf(fp, "dev %s ", ll_index_to_name(prefix->prefix_ifindex));
+
+ if (prefix->prefix_flags & IF_PREFIX_ONLINK)
+ fprintf(fp, "onlink ");
+ if (prefix->prefix_flags & IF_PREFIX_AUTOCONF)
+ fprintf(fp, "autoconf ");
+
+ if (tb[PREFIX_CACHEINFO]) {
+ const struct prefix_cacheinfo *pc
+ = RTA_DATA(tb[PREFIX_CACHEINFO]);
+
+ fprintf(fp, "valid %u ", pc->valid_time);
+ fprintf(fp, "preferred %u ", pc->preferred_time);
+ }
+
+ fprintf(fp, "\n");
+ fflush(fp);
+
+ return 0;
+}
diff --git a/ip/iproute.c b/ip/iproute.c
new file mode 100644
index 0000000..73dbab4
--- /dev/null
+++ b/ip/iproute.c
@@ -0,0 +1,2409 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iproute.c "ip route".
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <linux/in_route.h>
+#include <linux/icmpv6.h>
+#include <errno.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "nh_common.h"
+
+#ifndef RTAX_RTTVAR
+#define RTAX_RTTVAR RTAX_HOPS
+#endif
+
+enum list_action {
+ IPROUTE_LIST,
+ IPROUTE_FLUSH,
+ IPROUTE_SAVE,
+};
+static const char *mx_names[RTAX_MAX+1] = {
+ [RTAX_MTU] = "mtu",
+ [RTAX_WINDOW] = "window",
+ [RTAX_RTT] = "rtt",
+ [RTAX_RTTVAR] = "rttvar",
+ [RTAX_SSTHRESH] = "ssthresh",
+ [RTAX_CWND] = "cwnd",
+ [RTAX_ADVMSS] = "advmss",
+ [RTAX_REORDERING] = "reordering",
+ [RTAX_HOPLIMIT] = "hoplimit",
+ [RTAX_INITCWND] = "initcwnd",
+ [RTAX_FEATURES] = "features",
+ [RTAX_RTO_MIN] = "rto_min",
+ [RTAX_INITRWND] = "initrwnd",
+ [RTAX_QUICKACK] = "quickack",
+ [RTAX_CC_ALGO] = "congctl",
+ [RTAX_FASTOPEN_NO_COOKIE] = "fastopen_no_cookie"
+};
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip route { list | flush } SELECTOR\n"
+ " ip route save SELECTOR\n"
+ " ip route restore\n"
+ " ip route showdump\n"
+ " ip route get [ ROUTE_GET_FLAGS ] ADDRESS\n"
+ " [ from ADDRESS iif STRING ]\n"
+ " [ oif STRING ] [ tos TOS ]\n"
+ " [ mark NUMBER ] [ vrf NAME ]\n"
+ " [ uid NUMBER ] [ ipproto PROTOCOL ]\n"
+ " [ sport NUMBER ] [ dport NUMBER ]\n"
+ " ip route { add | del | change | append | replace } ROUTE\n"
+ "SELECTOR := [ root PREFIX ] [ match PREFIX ] [ exact PREFIX ]\n"
+ " [ table TABLE_ID ] [ vrf NAME ] [ proto RTPROTO ]\n"
+ " [ type TYPE ] [ scope SCOPE ]\n"
+ "ROUTE := NODE_SPEC [ INFO_SPEC ]\n"
+ "NODE_SPEC := [ TYPE ] PREFIX [ tos TOS ]\n"
+ " [ table TABLE_ID ] [ proto RTPROTO ]\n"
+ " [ scope SCOPE ] [ metric METRIC ]\n"
+ " [ ttl-propagate { enabled | disabled } ]\n"
+ "INFO_SPEC := { NH | nhid ID } OPTIONS FLAGS [ nexthop NH ]...\n"
+ "NH := [ encap ENCAPTYPE ENCAPHDR ] [ via [ FAMILY ] ADDRESS ]\n"
+ " [ dev STRING ] [ weight NUMBER ] NHFLAGS\n"
+ "FAMILY := [ inet | inet6 | mpls | bridge | link ]\n"
+ "OPTIONS := FLAGS [ mtu NUMBER ] [ advmss NUMBER ] [ as [ to ] ADDRESS ]\n"
+ " [ rtt TIME ] [ rttvar TIME ] [ reordering NUMBER ]\n"
+ " [ window NUMBER ] [ cwnd NUMBER ] [ initcwnd NUMBER ]\n"
+ " [ ssthresh NUMBER ] [ realms REALM ] [ src ADDRESS ]\n"
+ " [ rto_min TIME ] [ hoplimit NUMBER ] [ initrwnd NUMBER ]\n"
+ " [ features FEATURES ] [ quickack BOOL ] [ congctl NAME ]\n"
+ " [ pref PREF ] [ expires TIME ] [ fastopen_no_cookie BOOL ]\n"
+ "TYPE := { unicast | local | broadcast | multicast | throw |\n"
+ " unreachable | prohibit | blackhole | nat }\n"
+ "TABLE_ID := [ local | main | default | all | NUMBER ]\n"
+ "SCOPE := [ host | link | global | NUMBER ]\n"
+ "NHFLAGS := [ onlink | pervasive ]\n"
+ "RTPROTO := [ kernel | boot | static | NUMBER ]\n"
+ "PREF := [ low | medium | high ]\n"
+ "TIME := NUMBER[s|ms]\n"
+ "BOOL := [1|0]\n"
+ "FEATURES := ecn\n"
+ "ENCAPTYPE := [ mpls | ip | ip6 | seg6 | seg6local | rpl | ioam6 | xfrm ]\n"
+ "ENCAPHDR := [ MPLSLABEL | SEG6HDR | SEG6LOCAL | IOAM6HDR | XFRMINFO ]\n"
+ "SEG6HDR := [ mode SEGMODE ] segs ADDR1,ADDRi,ADDRn [hmac HMACKEYID] [cleanup]\n"
+ "SEGMODE := [ encap | encap.red | inline | l2encap | l2encap.red ]\n"
+ "SEG6LOCAL := action ACTION [ OPTIONS ] [ count ]\n"
+ "ACTION := { End | End.X | End.T | End.DX2 | End.DX6 | End.DX4 |\n"
+ " End.DT6 | End.DT4 | End.DT46 | End.B6 | End.B6.Encaps |\n"
+ " End.BM | End.S | End.AS | End.AM | End.BPF }\n"
+ "OPTIONS := OPTION [ OPTIONS ]\n"
+ "OPTION := { flavors FLAVORS | srh SEG6HDR | nh4 ADDR | nh6 ADDR | iif DEV | oif DEV |\n"
+ " table TABLEID | vrftable TABLEID | endpoint PROGNAME }\n"
+ "FLAVORS := { FLAVOR[,FLAVOR] }\n"
+ "FLAVOR := { psp | usp | usd | next-csid }\n"
+ "IOAM6HDR := trace prealloc type IOAM6_TRACE_TYPE ns IOAM6_NAMESPACE size IOAM6_TRACE_SIZE\n"
+ "XFRMINFO := if_id IF_ID [ link_dev LINK ]\n"
+ "ROUTE_GET_FLAGS := [ fibmatch ]\n");
+ exit(-1);
+}
+
+
+static struct
+{
+ unsigned int tb;
+ int cloned;
+ int flushed;
+ char *flushb;
+ int flushp;
+ int flushe;
+ int protocol, protocolmask;
+ int scope, scopemask;
+ __u64 typemask;
+ int tos, tosmask;
+ int iif, iifmask;
+ int oif, oifmask;
+ int mark, markmask;
+ int realm, realmmask;
+ __u32 metric, metricmask;
+ inet_prefix rprefsrc;
+ inet_prefix rvia;
+ inet_prefix rdst;
+ inet_prefix mdst;
+ inet_prefix rsrc;
+ inet_prefix msrc;
+} filter;
+
+static int flush_update(void)
+{
+ if (rtnl_send_check(&rth, filter.flushb, filter.flushp) < 0) {
+ perror("Failed to send flush request");
+ return -2;
+ }
+ filter.flushp = 0;
+ return 0;
+}
+
+static int filter_nlmsg(struct nlmsghdr *n, struct rtattr **tb, int host_len)
+{
+ struct rtmsg *r = NLMSG_DATA(n);
+ inet_prefix dst = { .family = r->rtm_family };
+ inet_prefix src = { .family = r->rtm_family };
+ inet_prefix via = { .family = r->rtm_family };
+ inet_prefix prefsrc = { .family = r->rtm_family };
+ __u32 table;
+ static int ip6_multiple_tables;
+
+ table = rtm_get_table(r, tb);
+
+ if (preferred_family != AF_UNSPEC && r->rtm_family != preferred_family)
+ return 0;
+
+ if (r->rtm_family == AF_INET6 && table != RT_TABLE_MAIN)
+ ip6_multiple_tables = 1;
+
+ if (filter.cloned == !(r->rtm_flags & RTM_F_CLONED))
+ return 0;
+
+ if (r->rtm_family == AF_INET6 && !ip6_multiple_tables) {
+ if (filter.tb) {
+ if (filter.tb == RT_TABLE_LOCAL) {
+ if (r->rtm_type != RTN_LOCAL)
+ return 0;
+ } else if (filter.tb == RT_TABLE_MAIN) {
+ if (r->rtm_type == RTN_LOCAL)
+ return 0;
+ } else {
+ return 0;
+ }
+ }
+ } else {
+ if (filter.tb > 0 && filter.tb != table)
+ return 0;
+ }
+ if ((filter.protocol^r->rtm_protocol)&filter.protocolmask)
+ return 0;
+ if ((filter.scope^r->rtm_scope)&filter.scopemask)
+ return 0;
+
+ if (filter.typemask && !(filter.typemask & (1 << r->rtm_type)))
+ return 0;
+ if ((filter.tos^r->rtm_tos)&filter.tosmask)
+ return 0;
+ if (filter.rdst.family) {
+ if (r->rtm_family != filter.rdst.family ||
+ filter.rdst.bitlen > r->rtm_dst_len)
+ return 0;
+ } else if (filter.rdst.flags & PREFIXLEN_SPECIFIED) {
+ if (filter.rdst.bitlen > r->rtm_dst_len)
+ return 0;
+ }
+ if (filter.mdst.family) {
+ if (r->rtm_family != filter.mdst.family ||
+ (filter.mdst.bitlen >= 0 &&
+ filter.mdst.bitlen < r->rtm_dst_len))
+ return 0;
+ } else if (filter.mdst.flags & PREFIXLEN_SPECIFIED) {
+ if (filter.mdst.bitlen >= 0 &&
+ filter.mdst.bitlen < r->rtm_dst_len)
+ return 0;
+ }
+ if (filter.rsrc.family) {
+ if (r->rtm_family != filter.rsrc.family ||
+ filter.rsrc.bitlen > r->rtm_src_len)
+ return 0;
+ } else if (filter.rsrc.flags & PREFIXLEN_SPECIFIED) {
+ if (filter.rsrc.bitlen > r->rtm_src_len)
+ return 0;
+ }
+ if (filter.msrc.family) {
+ if (r->rtm_family != filter.msrc.family ||
+ (filter.msrc.bitlen >= 0 &&
+ filter.msrc.bitlen < r->rtm_src_len))
+ return 0;
+ } else if (filter.msrc.flags & PREFIXLEN_SPECIFIED) {
+ if (filter.msrc.bitlen >= 0 &&
+ filter.msrc.bitlen < r->rtm_src_len)
+ return 0;
+ }
+ if (filter.rvia.family) {
+ int family = r->rtm_family;
+
+ if (tb[RTA_VIA]) {
+ struct rtvia *via = RTA_DATA(tb[RTA_VIA]);
+
+ family = via->rtvia_family;
+ }
+ if (family != filter.rvia.family)
+ return 0;
+ }
+ if (filter.rprefsrc.family && r->rtm_family != filter.rprefsrc.family)
+ return 0;
+
+ if (tb[RTA_DST])
+ memcpy(&dst.data, RTA_DATA(tb[RTA_DST]), (r->rtm_dst_len+7)/8);
+ if (filter.rsrc.family || filter.msrc.family ||
+ filter.rsrc.flags & PREFIXLEN_SPECIFIED ||
+ filter.msrc.flags & PREFIXLEN_SPECIFIED) {
+ if (tb[RTA_SRC])
+ memcpy(&src.data, RTA_DATA(tb[RTA_SRC]), (r->rtm_src_len+7)/8);
+ }
+ if (filter.rvia.bitlen > 0) {
+ if (tb[RTA_GATEWAY])
+ memcpy(&via.data, RTA_DATA(tb[RTA_GATEWAY]), host_len/8);
+ if (tb[RTA_VIA]) {
+ size_t len = RTA_PAYLOAD(tb[RTA_VIA]) - 2;
+ struct rtvia *rtvia = RTA_DATA(tb[RTA_VIA]);
+
+ via.family = rtvia->rtvia_family;
+ memcpy(&via.data, rtvia->rtvia_addr, len);
+ }
+ }
+ if (filter.rprefsrc.bitlen > 0) {
+ if (tb[RTA_PREFSRC])
+ memcpy(&prefsrc.data, RTA_DATA(tb[RTA_PREFSRC]), host_len/8);
+ }
+
+ if ((filter.rdst.family || filter.rdst.flags & PREFIXLEN_SPECIFIED) &&
+ inet_addr_match(&dst, &filter.rdst, filter.rdst.bitlen))
+ return 0;
+ if ((filter.mdst.family || filter.mdst.flags & PREFIXLEN_SPECIFIED) &&
+ inet_addr_match(&dst, &filter.mdst, r->rtm_dst_len))
+ return 0;
+
+ if ((filter.rsrc.family || filter.rsrc.flags & PREFIXLEN_SPECIFIED) &&
+ inet_addr_match(&src, &filter.rsrc, filter.rsrc.bitlen))
+ return 0;
+ if ((filter.msrc.family || filter.msrc.flags & PREFIXLEN_SPECIFIED) &&
+ filter.msrc.bitlen >= 0 &&
+ inet_addr_match(&src, &filter.msrc, r->rtm_src_len))
+ return 0;
+
+ if (filter.rvia.family && inet_addr_match(&via, &filter.rvia, filter.rvia.bitlen))
+ return 0;
+ if (filter.rprefsrc.family && inet_addr_match(&prefsrc, &filter.rprefsrc, filter.rprefsrc.bitlen))
+ return 0;
+ if (filter.realmmask) {
+ __u32 realms = 0;
+
+ if (tb[RTA_FLOW])
+ realms = rta_getattr_u32(tb[RTA_FLOW]);
+ if ((realms^filter.realm)&filter.realmmask)
+ return 0;
+ }
+ if (filter.iifmask) {
+ int iif = 0;
+
+ if (tb[RTA_IIF])
+ iif = rta_getattr_u32(tb[RTA_IIF]);
+ if ((iif^filter.iif)&filter.iifmask)
+ return 0;
+ }
+ if (filter.oifmask) {
+ int oif = 0;
+
+ if (tb[RTA_OIF])
+ oif = rta_getattr_u32(tb[RTA_OIF]);
+ if ((oif^filter.oif)&filter.oifmask)
+ return 0;
+ }
+ if (filter.markmask) {
+ int mark = 0;
+
+ if (tb[RTA_MARK])
+ mark = rta_getattr_u32(tb[RTA_MARK]);
+ if ((mark ^ filter.mark) & filter.markmask)
+ return 0;
+ }
+ if (filter.metricmask) {
+ __u32 metric = 0;
+
+ if (tb[RTA_PRIORITY])
+ metric = rta_getattr_u32(tb[RTA_PRIORITY]);
+ if ((metric ^ filter.metric) & filter.metricmask)
+ return 0;
+ }
+ if (filter.flushb &&
+ r->rtm_family == AF_INET6 &&
+ r->rtm_dst_len == 0 &&
+ r->rtm_type == RTN_UNREACHABLE &&
+ tb[RTA_PRIORITY] &&
+ rta_getattr_u32(tb[RTA_PRIORITY]) == -1)
+ return 0;
+
+ return 1;
+}
+
+static void print_rtax_features(FILE *fp, unsigned int features)
+{
+ unsigned int of = features;
+
+ if (features & RTAX_FEATURE_ECN) {
+ print_null(PRINT_ANY, "ecn", "ecn ", NULL);
+ features &= ~RTAX_FEATURE_ECN;
+ }
+
+ if (features & RTAX_FEATURE_TCP_USEC_TS) {
+ print_null(PRINT_ANY, "tcp_usec_ts", "tcp_usec_ts ", NULL);
+ features &= ~RTAX_FEATURE_TCP_USEC_TS;
+ }
+
+ if (features)
+ print_0xhex(PRINT_ANY,
+ "features", "%#llx ", of);
+}
+
+void print_rt_flags(FILE *fp, unsigned int flags)
+{
+ open_json_array(PRINT_JSON,
+ is_json_context() ? "flags" : "");
+
+ if (flags & RTNH_F_DEAD)
+ print_string(PRINT_ANY, NULL, "%s ", "dead");
+ if (flags & RTNH_F_ONLINK)
+ print_string(PRINT_ANY, NULL, "%s ", "onlink");
+ if (flags & RTNH_F_PERVASIVE)
+ print_string(PRINT_ANY, NULL, "%s ", "pervasive");
+ if (flags & RTNH_F_OFFLOAD)
+ print_string(PRINT_ANY, NULL, "%s ", "offload");
+ if (flags & RTNH_F_TRAP)
+ print_string(PRINT_ANY, NULL, "%s ", "trap");
+ if (flags & RTM_F_NOTIFY)
+ print_string(PRINT_ANY, NULL, "%s ", "notify");
+ if (flags & RTNH_F_LINKDOWN)
+ print_string(PRINT_ANY, NULL, "%s ", "linkdown");
+ if (flags & RTNH_F_UNRESOLVED)
+ print_string(PRINT_ANY, NULL, "%s ", "unresolved");
+ if (flags & RTM_F_OFFLOAD)
+ print_string(PRINT_ANY, NULL, "%s ", "rt_offload");
+ if (flags & RTM_F_TRAP)
+ print_string(PRINT_ANY, NULL, "%s ", "rt_trap");
+ if (flags & RTM_F_OFFLOAD_FAILED)
+ print_string(PRINT_ANY, NULL, "%s ", "rt_offload_failed");
+
+ close_json_array(PRINT_JSON, NULL);
+}
+
+static void print_rt_pref(FILE *fp, unsigned int pref)
+{
+
+ switch (pref) {
+ case ICMPV6_ROUTER_PREF_LOW:
+ print_string(PRINT_ANY,
+ "pref", "pref %s", "low");
+ break;
+ case ICMPV6_ROUTER_PREF_MEDIUM:
+ print_string(PRINT_ANY,
+ "pref", "pref %s", "medium");
+ break;
+ case ICMPV6_ROUTER_PREF_HIGH:
+ print_string(PRINT_ANY,
+ "pref", "pref %s", "high");
+ break;
+ default:
+ print_uint(PRINT_ANY,
+ "pref", "%u", pref);
+ }
+}
+
+void print_rta_ifidx(FILE *fp, __u32 ifidx, const char *prefix)
+{
+ const char *ifname = ll_index_to_name(ifidx);
+
+ if (is_json_context()) {
+ print_string(PRINT_JSON, prefix, NULL, ifname);
+ } else {
+ fprintf(fp, "%s ", prefix);
+ color_fprintf(fp, COLOR_IFNAME, "%s ", ifname);
+ }
+}
+
+static void print_cache_flags(FILE *fp, __u32 flags)
+{
+ json_writer_t *jw = get_json_writer();
+ flags &= ~0xFFFF;
+
+ if (jw) {
+ jsonw_name(jw, "cache");
+ jsonw_start_array(jw);
+ } else {
+ fprintf(fp, "%s cache ", _SL_);
+ if (flags == 0)
+ return;
+ putc('<', fp);
+ }
+
+#define PRTFL(fl, flname) \
+ if (flags & RTCF_##fl) { \
+ flags &= ~RTCF_##fl; \
+ if (jw) \
+ jsonw_string(jw, flname); \
+ else \
+ fprintf(fp, "%s%s", flname, flags ? "," : "> "); \
+ }
+
+ PRTFL(LOCAL, "local");
+ PRTFL(REJECT, "reject");
+ PRTFL(MULTICAST, "mc");
+ PRTFL(BROADCAST, "brd");
+ PRTFL(DNAT, "dst-nat");
+ PRTFL(SNAT, "src-nat");
+ PRTFL(MASQ, "masq");
+ PRTFL(DIRECTDST, "dst-direct");
+ PRTFL(DIRECTSRC, "src-direct");
+ PRTFL(REDIRECTED, "redirected");
+ PRTFL(DOREDIRECT, "redirect");
+ PRTFL(FAST, "fastroute");
+ PRTFL(NOTIFY, "notify");
+ PRTFL(TPROXY, "proxy");
+#undef PRTFL
+
+ if (flags)
+ print_hex(PRINT_ANY, "flags", "%x>", flags);
+
+ if (jw)
+ jsonw_end_array(jw);
+}
+
+static void print_rta_cacheinfo(FILE *fp, const struct rta_cacheinfo *ci)
+{
+ static int hz;
+
+ if (!hz)
+ hz = get_user_hz();
+
+ if (ci->rta_expires != 0)
+ print_int(PRINT_ANY, "expires",
+ "expires %dsec ", ci->rta_expires/hz);
+ if (ci->rta_error != 0)
+ print_uint(PRINT_ANY, "error",
+ "error %u ", ci->rta_error);
+
+ if (show_stats) {
+ if (ci->rta_clntref)
+ print_uint(PRINT_ANY, "users",
+ "users %u ", ci->rta_clntref);
+ if (ci->rta_used != 0)
+ print_uint(PRINT_ANY, "used",
+ "used %u ", ci->rta_used);
+ if (ci->rta_lastuse != 0)
+ print_uint(PRINT_ANY, "age",
+ "age %usec ", ci->rta_lastuse/hz);
+ }
+ if (ci->rta_id)
+ print_0xhex(PRINT_ANY, "ipid",
+ "ipid 0x%04llx ", ci->rta_id);
+ if (ci->rta_ts || ci->rta_tsage) {
+ print_0xhex(PRINT_ANY, "ts",
+ "ts 0x%llx", ci->rta_ts);
+ print_uint(PRINT_ANY, "tsage",
+ "tsage %usec ", ci->rta_tsage);
+ }
+}
+
+static void print_rta_flow(FILE *fp, const struct rtattr *rta)
+{
+ __u32 to = rta_getattr_u32(rta);
+ __u32 from = to >> 16;
+ SPRINT_BUF(b1);
+
+ to &= 0xFFFF;
+ if (is_json_context()) {
+ open_json_object("flow");
+
+ if (from)
+ print_string(PRINT_JSON, "from", NULL,
+ rtnl_rtrealm_n2a(from, b1, sizeof(b1)));
+ print_string(PRINT_JSON, "to", NULL,
+ rtnl_rtrealm_n2a(to, b1, sizeof(b1)));
+ close_json_object();
+ } else {
+ fprintf(fp, "realm%s ", from ? "s" : "");
+
+ if (from)
+ print_string(PRINT_FP, NULL, "%s/",
+ rtnl_rtrealm_n2a(from, b1, sizeof(b1)));
+ print_string(PRINT_FP, NULL, "%s ",
+ rtnl_rtrealm_n2a(to, b1, sizeof(b1)));
+ }
+}
+
+static void print_rta_newdst(FILE *fp, const struct rtmsg *r,
+ const struct rtattr *rta)
+{
+ const char *newdst = format_host_rta(r->rtm_family, rta);
+
+ if (is_json_context())
+ print_string(PRINT_JSON, "to", NULL, newdst);
+ else {
+ fprintf(fp, "as to ");
+ print_color_string(PRINT_FP,
+ ifa_family_color(r->rtm_family),
+ NULL, "%s ", newdst);
+ }
+}
+
+void __print_rta_gateway(FILE *fp, unsigned char family, const char *gateway)
+{
+ if (is_json_context()) {
+ print_string(PRINT_JSON, "gateway", NULL, gateway);
+ } else {
+ fprintf(fp, "via ");
+ print_color_string(PRINT_FP,
+ ifa_family_color(family),
+ NULL, "%s ", gateway);
+ }
+}
+
+static void print_rta_gateway(FILE *fp, unsigned char family, const struct rtattr *rta)
+{
+ const char *gateway = format_host_rta(family, rta);
+
+ __print_rta_gateway(fp, family, gateway);
+}
+
+static void print_rta_via(FILE *fp, const struct rtattr *rta)
+{
+ size_t len = RTA_PAYLOAD(rta) - 2;
+ const struct rtvia *via = RTA_DATA(rta);
+
+ if (is_json_context()) {
+ open_json_object("via");
+ print_string(PRINT_JSON, "family", NULL,
+ family_name(via->rtvia_family));
+ print_string(PRINT_JSON, "host", NULL,
+ format_host(via->rtvia_family, len,
+ via->rtvia_addr));
+ close_json_object();
+ } else {
+ print_string(PRINT_FP, NULL, "via %s ",
+ family_name(via->rtvia_family));
+ print_color_string(PRINT_FP,
+ ifa_family_color(via->rtvia_family),
+ NULL, "%s ",
+ format_host(via->rtvia_family,
+ len, via->rtvia_addr));
+ }
+}
+
+static void print_rta_metrics(FILE *fp, const struct rtattr *rta)
+{
+ struct rtattr *mxrta[RTAX_MAX+1];
+ unsigned int mxlock = 0;
+ int i;
+
+ open_json_array(PRINT_JSON, "metrics");
+ open_json_object(NULL);
+
+ parse_rtattr(mxrta, RTAX_MAX, RTA_DATA(rta), RTA_PAYLOAD(rta));
+
+ if (mxrta[RTAX_LOCK])
+ mxlock = rta_getattr_u32(mxrta[RTAX_LOCK]);
+
+ for (i = 2; i <= RTAX_MAX; i++) {
+ __u32 val = 0U;
+
+ if (mxrta[i] == NULL && !(mxlock & (1 << i)))
+ continue;
+
+ if (mxrta[i] != NULL && i != RTAX_CC_ALGO)
+ val = rta_getattr_u32(mxrta[i]);
+
+ if (i == RTAX_HOPLIMIT && (int)val == -1)
+ continue;
+
+ if (!is_json_context()) {
+ if (i < sizeof(mx_names)/sizeof(char *) && mx_names[i])
+ fprintf(fp, "%s ", mx_names[i]);
+ else
+ fprintf(fp, "metric %d ", i);
+
+ if (mxlock & (1<<i))
+ fprintf(fp, "lock ");
+ }
+
+ switch (i) {
+ case RTAX_FEATURES:
+ print_rtax_features(fp, val);
+ break;
+ default:
+ print_uint(PRINT_ANY, mx_names[i], "%u ", val);
+ break;
+
+ case RTAX_RTT:
+ case RTAX_RTTVAR:
+ case RTAX_RTO_MIN:
+ if (i == RTAX_RTT)
+ val /= 8;
+ else if (i == RTAX_RTTVAR)
+ val /= 4;
+
+ if (is_json_context())
+ print_uint(PRINT_JSON, mx_names[i],
+ NULL, val);
+ else {
+ if (val >= 1000)
+ fprintf(fp, "%gs ", val/1e3);
+ else
+ fprintf(fp, "%ums ", val);
+ }
+ break;
+ case RTAX_CC_ALGO:
+ print_string(PRINT_ANY, "congestion",
+ "%s ", rta_getattr_str(mxrta[i]));
+ break;
+ }
+ }
+
+ close_json_object();
+ close_json_array(PRINT_JSON, NULL);
+}
+
+static void print_rta_multipath(FILE *fp, const struct rtmsg *r,
+ struct rtattr *rta)
+{
+ const struct rtnexthop *nh = RTA_DATA(rta);
+ int len = RTA_PAYLOAD(rta);
+ int first = 1;
+
+ open_json_array(PRINT_JSON, "nexthops");
+
+ while (len >= sizeof(*nh)) {
+ struct rtattr *tb[RTA_MAX + 1];
+
+ if (nh->rtnh_len > len)
+ break;
+
+ open_json_object(NULL);
+
+ if ((r->rtm_flags & RTM_F_CLONED) &&
+ r->rtm_type == RTN_MULTICAST) {
+ if (first) {
+ print_string(PRINT_FP, NULL, "Oifs: ", NULL);
+ first = 0;
+ } else {
+ print_string(PRINT_FP, NULL, " ", NULL);
+ }
+ } else
+ print_string(PRINT_FP, NULL, "%s\tnexthop ", _SL_);
+
+ if (nh->rtnh_len > sizeof(*nh)) {
+ parse_rtattr(tb, RTA_MAX, RTNH_DATA(nh),
+ nh->rtnh_len - sizeof(*nh));
+
+ if (tb[RTA_ENCAP])
+ lwt_print_encap(fp,
+ tb[RTA_ENCAP_TYPE],
+ tb[RTA_ENCAP]);
+ if (tb[RTA_NEWDST])
+ print_rta_newdst(fp, r, tb[RTA_NEWDST]);
+ if (tb[RTA_GATEWAY])
+ print_rta_gateway(fp, r->rtm_family,
+ tb[RTA_GATEWAY]);
+ if (tb[RTA_VIA])
+ print_rta_via(fp, tb[RTA_VIA]);
+ if (tb[RTA_FLOW])
+ print_rta_flow(fp, tb[RTA_FLOW]);
+ }
+
+ if ((r->rtm_flags & RTM_F_CLONED) &&
+ r->rtm_type == RTN_MULTICAST) {
+ print_string(PRINT_ANY, "dev",
+ "%s", ll_index_to_name(nh->rtnh_ifindex));
+
+ if (nh->rtnh_hops != 1)
+ print_int(PRINT_ANY, "ttl", "(ttl>%d)", nh->rtnh_hops);
+
+ print_string(PRINT_FP, NULL, " ", NULL);
+ } else {
+ print_string(PRINT_ANY, "dev",
+ "dev %s ", ll_index_to_name(nh->rtnh_ifindex));
+
+ if (r->rtm_family != AF_MPLS)
+ print_int(PRINT_ANY, "weight",
+ "weight %d ", nh->rtnh_hops + 1);
+ }
+
+ print_rt_flags(fp, nh->rtnh_flags);
+
+ len -= NLMSG_ALIGN(nh->rtnh_len);
+ nh = RTNH_NEXT(nh);
+
+ close_json_object();
+ }
+ close_json_array(PRINT_JSON, NULL);
+}
+
+int print_route(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+ struct rtmsg *r = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *tb[RTA_MAX+1];
+ int family, color, host_len;
+ __u32 table;
+ int ret;
+
+ SPRINT_BUF(b1);
+ SPRINT_BUF(b2);
+
+ if (n->nlmsg_type != RTM_NEWROUTE && n->nlmsg_type != RTM_DELROUTE) {
+ fprintf(stderr, "Not a route: %08x %08x %08x\n",
+ n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+ return -1;
+ }
+ if (filter.flushb && n->nlmsg_type != RTM_NEWROUTE)
+ return 0;
+ len -= NLMSG_LENGTH(sizeof(*r));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ host_len = af_bit_len(r->rtm_family);
+
+ parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
+ table = rtm_get_table(r, tb);
+
+ if (!filter_nlmsg(n, tb, host_len))
+ return 0;
+
+ if (filter.flushb) {
+ struct nlmsghdr *fn;
+
+ if (NLMSG_ALIGN(filter.flushp) + n->nlmsg_len > filter.flushe) {
+ ret = flush_update();
+ if (ret < 0)
+ return ret;
+ }
+ fn = (struct nlmsghdr *)(filter.flushb + NLMSG_ALIGN(filter.flushp));
+ memcpy(fn, n, n->nlmsg_len);
+ fn->nlmsg_type = RTM_DELROUTE;
+ fn->nlmsg_flags = NLM_F_REQUEST;
+ fn->nlmsg_seq = ++rth.seq;
+ filter.flushp = (((char *)fn) + n->nlmsg_len) - filter.flushb;
+ filter.flushed++;
+ if (show_stats < 2)
+ return 0;
+ }
+
+ open_json_object(NULL);
+ if (n->nlmsg_type == RTM_DELROUTE)
+ print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+ if ((r->rtm_type != RTN_UNICAST || show_details > 0) &&
+ (!filter.typemask || (filter.typemask & (1 << r->rtm_type))))
+ print_string(PRINT_ANY, "type", "%s ",
+ rtnl_rtntype_n2a(r->rtm_type, b1, sizeof(b1)));
+
+ color = COLOR_NONE;
+ if (tb[RTA_DST]) {
+ family = get_real_family(r->rtm_type, r->rtm_family);
+ color = ifa_family_color(family);
+
+ if (r->rtm_dst_len != host_len) {
+ snprintf(b1, sizeof(b1),
+ "%s/%u", rt_addr_n2a_rta(family, tb[RTA_DST]),
+ r->rtm_dst_len);
+ } else {
+ const char *hostname = format_host_rta_r(family, tb[RTA_DST],
+ b2, sizeof(b2));
+ if (hostname)
+ strncpy(b1, hostname, sizeof(b1) - 1);
+ }
+ } else if (r->rtm_dst_len) {
+ snprintf(b1, sizeof(b1), "0/%d ", r->rtm_dst_len);
+ } else {
+ strncpy(b1, "default", sizeof(b1));
+ }
+ print_color_string(PRINT_ANY, color,
+ "dst", "%s ", b1);
+
+ if (tb[RTA_SRC]) {
+ family = get_real_family(r->rtm_type, r->rtm_family);
+ color = ifa_family_color(family);
+
+ if (r->rtm_src_len != host_len) {
+ snprintf(b1, sizeof(b1),
+ "%s/%u",
+ rt_addr_n2a_rta(family, tb[RTA_SRC]),
+ r->rtm_src_len);
+ } else {
+ const char *hostname = format_host_rta_r(family, tb[RTA_SRC],
+ b2, sizeof(b2));
+ if (hostname)
+ strncpy(b1, hostname, sizeof(b1) - 1);
+ }
+ print_color_string(PRINT_ANY, color,
+ "from", "from %s ", b1);
+ } else if (r->rtm_src_len) {
+ snprintf(b1, sizeof(b1), "0/%u", r->rtm_src_len);
+
+ print_string(PRINT_ANY, "src", "from %s ", b1);
+ }
+
+ if (tb[RTA_NH_ID])
+ print_uint(PRINT_ANY, "nhid", "nhid %u ",
+ rta_getattr_u32(tb[RTA_NH_ID]));
+
+ if (tb[RTA_NEWDST])
+ print_rta_newdst(fp, r, tb[RTA_NEWDST]);
+
+ if (tb[RTA_ENCAP])
+ lwt_print_encap(fp, tb[RTA_ENCAP_TYPE], tb[RTA_ENCAP]);
+
+ if (r->rtm_tos && filter.tosmask != -1) {
+ print_string(PRINT_ANY, "tos", "tos %s ",
+ rtnl_dsfield_n2a(r->rtm_tos, b1, sizeof(b1)));
+ }
+
+ if (tb[RTA_GATEWAY] && filter.rvia.bitlen != host_len)
+ print_rta_gateway(fp, r->rtm_family, tb[RTA_GATEWAY]);
+
+ if (tb[RTA_VIA])
+ print_rta_via(fp, tb[RTA_VIA]);
+
+ if (tb[RTA_OIF] && filter.oifmask != -1)
+ print_rta_ifidx(fp, rta_getattr_u32(tb[RTA_OIF]), "dev");
+
+ if (table && (table != RT_TABLE_MAIN || show_details > 0) && !filter.tb)
+ print_string(PRINT_ANY,
+ "table", "table %s ",
+ rtnl_rttable_n2a(table, b1, sizeof(b1)));
+
+ if (!(r->rtm_flags & RTM_F_CLONED)) {
+ if ((r->rtm_protocol != RTPROT_BOOT || show_details > 0) &&
+ filter.protocolmask != -1)
+ print_string(PRINT_ANY,
+ "protocol", "proto %s ",
+ rtnl_rtprot_n2a(r->rtm_protocol,
+ b1, sizeof(b1)));
+
+ if ((r->rtm_scope != RT_SCOPE_UNIVERSE || show_details > 0) &&
+ filter.scopemask != -1)
+ print_string(PRINT_ANY,
+ "scope", "scope %s ",
+ rtnl_rtscope_n2a(r->rtm_scope,
+ b1, sizeof(b1)));
+ }
+
+ if (tb[RTA_PREFSRC] && filter.rprefsrc.bitlen != host_len) {
+ const char *psrc
+ = rt_addr_n2a_rta(r->rtm_family, tb[RTA_PREFSRC]);
+
+ /* Do not use format_host(). It is our local addr
+ and symbolic name will not be useful.
+ */
+ if (is_json_context())
+ print_string(PRINT_JSON, "prefsrc", NULL, psrc);
+ else {
+ fprintf(fp, "src ");
+ print_color_string(PRINT_FP,
+ ifa_family_color(r->rtm_family),
+ NULL, "%s ", psrc);
+ }
+
+ }
+
+ if (tb[RTA_PRIORITY] && filter.metricmask != -1)
+ print_uint(PRINT_ANY, "metric", "metric %u ",
+ rta_getattr_u32(tb[RTA_PRIORITY]));
+
+ print_rt_flags(fp, r->rtm_flags);
+
+ if (tb[RTA_MARK]) {
+ unsigned int mark = rta_getattr_u32(tb[RTA_MARK]);
+
+ if (mark) {
+ if (is_json_context())
+ print_uint(PRINT_JSON, "mark", NULL, mark);
+ else if (mark >= 16)
+ print_0xhex(PRINT_FP, NULL,
+ "mark 0x%llx ", mark);
+ else
+ print_uint(PRINT_FP, NULL,
+ "mark %u ", mark);
+ }
+ }
+
+ if (tb[RTA_FLOW] && filter.realmmask != ~0U)
+ print_rta_flow(fp, tb[RTA_FLOW]);
+
+ if (tb[RTA_UID])
+ print_uint(PRINT_ANY, "uid", "uid %u ",
+ rta_getattr_u32(tb[RTA_UID]));
+
+ if (r->rtm_family == AF_INET) {
+ if (r->rtm_flags & RTM_F_CLONED)
+ print_cache_flags(fp, r->rtm_flags);
+
+ if (tb[RTA_CACHEINFO])
+ print_rta_cacheinfo(fp, RTA_DATA(tb[RTA_CACHEINFO]));
+ } else if (r->rtm_family == AF_INET6) {
+ if (tb[RTA_CACHEINFO])
+ print_rta_cacheinfo(fp, RTA_DATA(tb[RTA_CACHEINFO]));
+ }
+
+ if (tb[RTA_METRICS])
+ print_rta_metrics(fp, tb[RTA_METRICS]);
+
+ if (tb[RTA_IIF] && filter.iifmask != -1)
+ print_rta_ifidx(fp, rta_getattr_u32(tb[RTA_IIF]), "iif");
+
+ if (tb[RTA_PREF])
+ print_rt_pref(fp, rta_getattr_u8(tb[RTA_PREF]));
+
+ if (tb[RTA_TTL_PROPAGATE]) {
+ bool propagate = rta_getattr_u8(tb[RTA_TTL_PROPAGATE]);
+
+ if (is_json_context())
+ print_bool(PRINT_JSON, "ttl-propogate", NULL,
+ propagate);
+ else
+ print_string(PRINT_FP, NULL,
+ "ttl-propogate %s",
+ propagate ? "enabled" : "disabled");
+ }
+
+ if (tb[RTA_NH_ID] && show_details)
+ print_cache_nexthop_id(fp, "\n\tnh_info ", "nh_info",
+ rta_getattr_u32(tb[RTA_NH_ID]));
+
+ if (tb[RTA_MULTIPATH])
+ print_rta_multipath(fp, r, tb[RTA_MULTIPATH]);
+
+ /* If you are adding new route RTA_XXXX then place it above
+ * the RTA_MULTIPATH else it will appear that the last nexthop
+ * in the ECMP has new attributes
+ */
+
+ print_string(PRINT_FP, NULL, "\n", NULL);
+ close_json_object();
+ fflush(fp);
+ return 0;
+}
+
+static int parse_one_nh(struct nlmsghdr *n, struct rtmsg *r,
+ struct rtattr *rta, size_t len, struct rtnexthop *rtnh,
+ int *argcp, char ***argvp)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+
+ while (++argv, --argc > 0) {
+ if (strcmp(*argv, "via") == 0) {
+ inet_prefix addr;
+ int family;
+
+ NEXT_ARG();
+ family = read_family(*argv);
+ if (family == AF_UNSPEC)
+ family = r->rtm_family;
+ else
+ NEXT_ARG();
+ get_addr(&addr, *argv, family);
+ if (r->rtm_family == AF_UNSPEC)
+ r->rtm_family = addr.family;
+ if (addr.family == r->rtm_family) {
+ if (rta_addattr_l(rta, len, RTA_GATEWAY,
+ &addr.data, addr.bytelen))
+ return -1;
+ rtnh->rtnh_len += sizeof(struct rtattr)
+ + addr.bytelen;
+ } else {
+ if (rta_addattr_l(rta, len, RTA_VIA,
+ &addr.family, addr.bytelen + 2))
+ return -1;
+ rtnh->rtnh_len += RTA_SPACE(addr.bytelen + 2);
+ }
+ } else if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ rtnh->rtnh_ifindex = ll_name_to_index(*argv);
+ if (!rtnh->rtnh_ifindex)
+ return nodev(*argv);
+ } else if (strcmp(*argv, "weight") == 0) {
+ unsigned int w;
+
+ NEXT_ARG();
+ if (get_unsigned(&w, *argv, 0) || w == 0 || w > 256)
+ invarg("\"weight\" is invalid\n", *argv);
+ rtnh->rtnh_hops = w - 1;
+ } else if (strcmp(*argv, "onlink") == 0) {
+ rtnh->rtnh_flags |= RTNH_F_ONLINK;
+ } else if (matches(*argv, "realms") == 0) {
+ __u32 realm;
+
+ NEXT_ARG();
+ if (get_rt_realms_or_raw(&realm, *argv))
+ invarg("\"realm\" value is invalid\n", *argv);
+ if (rta_addattr32(rta, len, RTA_FLOW, realm))
+ return -1;
+ rtnh->rtnh_len += sizeof(struct rtattr) + 4;
+ } else if (strcmp(*argv, "encap") == 0) {
+ int old_len = rta->rta_len;
+
+ if (lwt_parse_encap(rta, len, &argc, &argv,
+ RTA_ENCAP, RTA_ENCAP_TYPE))
+ return -1;
+ rtnh->rtnh_len += rta->rta_len - old_len;
+ } else if (strcmp(*argv, "as") == 0) {
+ inet_prefix addr;
+
+ NEXT_ARG();
+ if (strcmp(*argv, "to") == 0)
+ NEXT_ARG();
+ get_addr(&addr, *argv, r->rtm_family);
+ if (rta_addattr_l(rta, len, RTA_NEWDST,
+ &addr.data, addr.bytelen))
+ return -1;
+ rtnh->rtnh_len += sizeof(struct rtattr) + addr.bytelen;
+ } else
+ break;
+ }
+ *argcp = argc;
+ *argvp = argv;
+ return 0;
+}
+
+static int parse_nexthops(struct nlmsghdr *n, struct rtmsg *r,
+ int argc, char **argv)
+{
+ char buf[4096];
+ struct rtattr *rta = (void *)buf;
+ struct rtnexthop *rtnh;
+
+ rta->rta_type = RTA_MULTIPATH;
+ rta->rta_len = RTA_LENGTH(0);
+ rtnh = RTA_DATA(rta);
+
+ while (argc > 0) {
+ if (strcmp(*argv, "nexthop") != 0) {
+ fprintf(stderr, "Error: \"nexthop\" or end of line is expected instead of \"%s\"\n", *argv);
+ exit(-1);
+ }
+ if (argc <= 1) {
+ fprintf(stderr, "Error: unexpected end of line after \"nexthop\"\n");
+ exit(-1);
+ }
+ memset(rtnh, 0, sizeof(*rtnh));
+ rtnh->rtnh_len = sizeof(*rtnh);
+ rta->rta_len += rtnh->rtnh_len;
+ if (parse_one_nh(n, r, rta, 4096, rtnh, &argc, &argv)) {
+ fprintf(stderr, "Error: cannot parse nexthop\n");
+ exit(-1);
+ }
+ rtnh = RTNH_NEXT(rtnh);
+ }
+
+ if (rta->rta_len > RTA_LENGTH(0))
+ return addattr_l(n, 4096, RTA_MULTIPATH,
+ RTA_DATA(rta), RTA_PAYLOAD(rta));
+ return 0;
+}
+
+static int iproute_modify(int cmd, unsigned int flags, int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct rtmsg r;
+ char buf[4096];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST | flags,
+ .n.nlmsg_type = cmd,
+ .r.rtm_family = preferred_family,
+ .r.rtm_table = RT_TABLE_MAIN,
+ .r.rtm_scope = RT_SCOPE_NOWHERE,
+ };
+ char mxbuf[256];
+ struct rtattr *mxrta = (void *)mxbuf;
+ unsigned int mxlock = 0;
+ char *d = NULL;
+ int gw_ok = 0;
+ int dst_ok = 0;
+ int nhs_ok = 0;
+ int scope_ok = 0;
+ int table_ok = 0;
+ int raw = 0;
+ int type_ok = 0;
+ __u32 nhid = 0;
+ int ret;
+
+ if (cmd != RTM_DELROUTE) {
+ req.r.rtm_protocol = RTPROT_BOOT;
+ req.r.rtm_scope = RT_SCOPE_UNIVERSE;
+ req.r.rtm_type = RTN_UNICAST;
+ }
+
+ mxrta->rta_type = RTA_METRICS;
+ mxrta->rta_len = RTA_LENGTH(0);
+
+ while (argc > 0) {
+ if (strcmp(*argv, "src") == 0) {
+ inet_prefix addr;
+
+ NEXT_ARG();
+ get_addr(&addr, *argv, req.r.rtm_family);
+ if (req.r.rtm_family == AF_UNSPEC)
+ req.r.rtm_family = addr.family;
+ addattr_l(&req.n, sizeof(req),
+ RTA_PREFSRC, &addr.data, addr.bytelen);
+ } else if (strcmp(*argv, "as") == 0) {
+ inet_prefix addr;
+
+ NEXT_ARG();
+ if (strcmp(*argv, "to") == 0) {
+ NEXT_ARG();
+ }
+ get_addr(&addr, *argv, req.r.rtm_family);
+ if (req.r.rtm_family == AF_UNSPEC)
+ req.r.rtm_family = addr.family;
+ addattr_l(&req.n, sizeof(req),
+ RTA_NEWDST, &addr.data, addr.bytelen);
+ } else if (strcmp(*argv, "via") == 0) {
+ inet_prefix addr;
+ int family;
+
+ if (gw_ok) {
+ invarg("use nexthop syntax to specify multiple via\n",
+ *argv);
+ }
+ gw_ok = 1;
+ NEXT_ARG();
+ family = read_family(*argv);
+ if (family == AF_UNSPEC)
+ family = req.r.rtm_family;
+ else
+ NEXT_ARG();
+ get_addr(&addr, *argv, family);
+ if (req.r.rtm_family == AF_UNSPEC)
+ req.r.rtm_family = addr.family;
+ if (addr.family == req.r.rtm_family)
+ addattr_l(&req.n, sizeof(req), RTA_GATEWAY,
+ &addr.data, addr.bytelen);
+ else
+ addattr_l(&req.n, sizeof(req), RTA_VIA,
+ &addr.family, addr.bytelen+2);
+ } else if (strcmp(*argv, "from") == 0) {
+ inet_prefix addr;
+
+ NEXT_ARG();
+ get_prefix(&addr, *argv, req.r.rtm_family);
+ if (req.r.rtm_family == AF_UNSPEC)
+ req.r.rtm_family = addr.family;
+ if (addr.bytelen)
+ addattr_l(&req.n, sizeof(req), RTA_SRC, &addr.data, addr.bytelen);
+ req.r.rtm_src_len = addr.bitlen;
+ } else if (strcmp(*argv, "tos") == 0 ||
+ matches(*argv, "dsfield") == 0) {
+ __u32 tos;
+
+ NEXT_ARG();
+ if (rtnl_dsfield_a2n(&tos, *argv))
+ invarg("\"tos\" value is invalid\n", *argv);
+ req.r.rtm_tos = tos;
+ } else if (strcmp(*argv, "expires") == 0) {
+ __u32 expires;
+
+ NEXT_ARG();
+ if (get_u32(&expires, *argv, 0))
+ invarg("\"expires\" value is invalid\n", *argv);
+ addattr32(&req.n, sizeof(req), RTA_EXPIRES, expires);
+ } else if (matches(*argv, "metric") == 0 ||
+ matches(*argv, "priority") == 0 ||
+ strcmp(*argv, "preference") == 0) {
+ __u32 metric;
+
+ NEXT_ARG();
+ if (get_u32(&metric, *argv, 0))
+ invarg("\"metric\" value is invalid\n", *argv);
+ addattr32(&req.n, sizeof(req), RTA_PRIORITY, metric);
+ } else if (strcmp(*argv, "scope") == 0) {
+ __u32 scope = 0;
+
+ NEXT_ARG();
+ if (rtnl_rtscope_a2n(&scope, *argv))
+ invarg("invalid \"scope\" value\n", *argv);
+ req.r.rtm_scope = scope;
+ scope_ok = 1;
+ } else if (strcmp(*argv, "mtu") == 0) {
+ unsigned int mtu;
+
+ NEXT_ARG();
+ if (strcmp(*argv, "lock") == 0) {
+ mxlock |= (1<<RTAX_MTU);
+ NEXT_ARG();
+ }
+ if (get_unsigned(&mtu, *argv, 0))
+ invarg("\"mtu\" value is invalid\n", *argv);
+ rta_addattr32(mxrta, sizeof(mxbuf), RTAX_MTU, mtu);
+ } else if (strcmp(*argv, "hoplimit") == 0) {
+ unsigned int hoplimit;
+
+ NEXT_ARG();
+ if (strcmp(*argv, "lock") == 0) {
+ mxlock |= (1<<RTAX_HOPLIMIT);
+ NEXT_ARG();
+ }
+ if (get_unsigned(&hoplimit, *argv, 0) || hoplimit > 255)
+ invarg("\"hoplimit\" value is invalid\n", *argv);
+ rta_addattr32(mxrta, sizeof(mxbuf), RTAX_HOPLIMIT, hoplimit);
+ } else if (strcmp(*argv, "advmss") == 0) {
+ unsigned int mss;
+
+ NEXT_ARG();
+ if (strcmp(*argv, "lock") == 0) {
+ mxlock |= (1<<RTAX_ADVMSS);
+ NEXT_ARG();
+ }
+ if (get_unsigned(&mss, *argv, 0))
+ invarg("\"mss\" value is invalid\n", *argv);
+ rta_addattr32(mxrta, sizeof(mxbuf), RTAX_ADVMSS, mss);
+ } else if (matches(*argv, "reordering") == 0) {
+ unsigned int reord;
+
+ NEXT_ARG();
+ if (strcmp(*argv, "lock") == 0) {
+ mxlock |= (1<<RTAX_REORDERING);
+ NEXT_ARG();
+ }
+ if (get_unsigned(&reord, *argv, 0))
+ invarg("\"reordering\" value is invalid\n", *argv);
+ rta_addattr32(mxrta, sizeof(mxbuf), RTAX_REORDERING, reord);
+ } else if (strcmp(*argv, "rtt") == 0) {
+ unsigned int rtt;
+
+ NEXT_ARG();
+ if (strcmp(*argv, "lock") == 0) {
+ mxlock |= (1<<RTAX_RTT);
+ NEXT_ARG();
+ }
+ if (get_time_rtt(&rtt, *argv, &raw))
+ invarg("\"rtt\" value is invalid\n", *argv);
+ rta_addattr32(mxrta, sizeof(mxbuf), RTAX_RTT,
+ (raw) ? rtt : rtt * 8);
+ } else if (strcmp(*argv, "rto_min") == 0) {
+ unsigned int rto_min;
+
+ NEXT_ARG();
+ mxlock |= (1<<RTAX_RTO_MIN);
+ if (get_time_rtt(&rto_min, *argv, &raw))
+ invarg("\"rto_min\" value is invalid\n",
+ *argv);
+ rta_addattr32(mxrta, sizeof(mxbuf), RTAX_RTO_MIN,
+ rto_min);
+ } else if (matches(*argv, "window") == 0) {
+ unsigned int win;
+
+ NEXT_ARG();
+ if (strcmp(*argv, "lock") == 0) {
+ mxlock |= (1<<RTAX_WINDOW);
+ NEXT_ARG();
+ }
+ if (get_unsigned(&win, *argv, 0))
+ invarg("\"window\" value is invalid\n", *argv);
+ rta_addattr32(mxrta, sizeof(mxbuf), RTAX_WINDOW, win);
+ } else if (matches(*argv, "cwnd") == 0) {
+ unsigned int win;
+
+ NEXT_ARG();
+ if (strcmp(*argv, "lock") == 0) {
+ mxlock |= (1<<RTAX_CWND);
+ NEXT_ARG();
+ }
+ if (get_unsigned(&win, *argv, 0))
+ invarg("\"cwnd\" value is invalid\n", *argv);
+ rta_addattr32(mxrta, sizeof(mxbuf), RTAX_CWND, win);
+ } else if (matches(*argv, "initcwnd") == 0) {
+ unsigned int win;
+
+ NEXT_ARG();
+ if (strcmp(*argv, "lock") == 0) {
+ mxlock |= (1<<RTAX_INITCWND);
+ NEXT_ARG();
+ }
+ if (get_unsigned(&win, *argv, 0))
+ invarg("\"initcwnd\" value is invalid\n", *argv);
+ rta_addattr32(mxrta, sizeof(mxbuf),
+ RTAX_INITCWND, win);
+ } else if (matches(*argv, "initrwnd") == 0) {
+ unsigned int win;
+
+ NEXT_ARG();
+ if (strcmp(*argv, "lock") == 0) {
+ mxlock |= (1<<RTAX_INITRWND);
+ NEXT_ARG();
+ }
+ if (get_unsigned(&win, *argv, 0))
+ invarg("\"initrwnd\" value is invalid\n", *argv);
+ rta_addattr32(mxrta, sizeof(mxbuf),
+ RTAX_INITRWND, win);
+ } else if (matches(*argv, "features") == 0) {
+ unsigned int features = 0;
+
+ while (argc > 0) {
+ NEXT_ARG();
+
+ if (strcmp(*argv, "ecn") == 0)
+ features |= RTAX_FEATURE_ECN;
+ else if (strcmp(*argv, "tcp_usec_ts") == 0)
+ features |= RTAX_FEATURE_TCP_USEC_TS;
+ else
+ invarg("\"features\" value not valid\n", *argv);
+ break;
+ }
+
+ rta_addattr32(mxrta, sizeof(mxbuf),
+ RTAX_FEATURES, features);
+ } else if (matches(*argv, "quickack") == 0) {
+ unsigned int quickack;
+
+ NEXT_ARG();
+ if (get_unsigned(&quickack, *argv, 0))
+ invarg("\"quickack\" value is invalid\n", *argv);
+ if (quickack != 1 && quickack != 0)
+ invarg("\"quickack\" value should be 0 or 1\n", *argv);
+ rta_addattr32(mxrta, sizeof(mxbuf),
+ RTAX_QUICKACK, quickack);
+ } else if (matches(*argv, "congctl") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "lock") == 0) {
+ mxlock |= 1 << RTAX_CC_ALGO;
+ NEXT_ARG();
+ }
+ rta_addattr_l(mxrta, sizeof(mxbuf), RTAX_CC_ALGO, *argv,
+ strlen(*argv));
+ } else if (matches(*argv, "rttvar") == 0) {
+ unsigned int win;
+
+ NEXT_ARG();
+ if (strcmp(*argv, "lock") == 0) {
+ mxlock |= (1<<RTAX_RTTVAR);
+ NEXT_ARG();
+ }
+ if (get_time_rtt(&win, *argv, &raw))
+ invarg("\"rttvar\" value is invalid\n", *argv);
+ rta_addattr32(mxrta, sizeof(mxbuf), RTAX_RTTVAR,
+ (raw) ? win : win * 4);
+ } else if (matches(*argv, "ssthresh") == 0) {
+ unsigned int win;
+
+ NEXT_ARG();
+ if (strcmp(*argv, "lock") == 0) {
+ mxlock |= (1<<RTAX_SSTHRESH);
+ NEXT_ARG();
+ }
+ if (get_unsigned(&win, *argv, 0))
+ invarg("\"ssthresh\" value is invalid\n", *argv);
+ rta_addattr32(mxrta, sizeof(mxbuf), RTAX_SSTHRESH, win);
+ } else if (matches(*argv, "realms") == 0) {
+ __u32 realm;
+
+ NEXT_ARG();
+ if (get_rt_realms_or_raw(&realm, *argv))
+ invarg("\"realm\" value is invalid\n", *argv);
+ addattr32(&req.n, sizeof(req), RTA_FLOW, realm);
+ } else if (strcmp(*argv, "onlink") == 0) {
+ req.r.rtm_flags |= RTNH_F_ONLINK;
+ } else if (strcmp(*argv, "nexthop") == 0) {
+ nhs_ok = 1;
+ break;
+ } else if (!strcmp(*argv, "nhid")) {
+ NEXT_ARG();
+ if (get_u32(&nhid, *argv, 0))
+ invarg("\"id\" value is invalid\n", *argv);
+ addattr32(&req.n, sizeof(req), RTA_NH_ID, nhid);
+ } else if (matches(*argv, "protocol") == 0) {
+ __u32 prot;
+
+ NEXT_ARG();
+ if (rtnl_rtprot_a2n(&prot, *argv))
+ invarg("\"protocol\" value is invalid\n", *argv);
+ req.r.rtm_protocol = prot;
+ } else if (matches(*argv, "table") == 0) {
+ __u32 tid;
+
+ NEXT_ARG();
+ if (rtnl_rttable_a2n(&tid, *argv))
+ invarg("\"table\" value is invalid\n", *argv);
+ if (tid < 256)
+ req.r.rtm_table = tid;
+ else {
+ req.r.rtm_table = RT_TABLE_UNSPEC;
+ addattr32(&req.n, sizeof(req), RTA_TABLE, tid);
+ }
+ table_ok = 1;
+ } else if (matches(*argv, "vrf") == 0) {
+ __u32 tid;
+
+ NEXT_ARG();
+ tid = ipvrf_get_table(*argv);
+ if (tid == 0)
+ invarg("Invalid VRF\n", *argv);
+ if (tid < 256)
+ req.r.rtm_table = tid;
+ else {
+ req.r.rtm_table = RT_TABLE_UNSPEC;
+ addattr32(&req.n, sizeof(req), RTA_TABLE, tid);
+ }
+ table_ok = 1;
+ } else if (strcmp(*argv, "dev") == 0 ||
+ strcmp(*argv, "oif") == 0) {
+ NEXT_ARG();
+ d = *argv;
+ } else if (matches(*argv, "pref") == 0) {
+ __u8 pref;
+
+ NEXT_ARG();
+ if (strcmp(*argv, "low") == 0)
+ pref = ICMPV6_ROUTER_PREF_LOW;
+ else if (strcmp(*argv, "medium") == 0)
+ pref = ICMPV6_ROUTER_PREF_MEDIUM;
+ else if (strcmp(*argv, "high") == 0)
+ pref = ICMPV6_ROUTER_PREF_HIGH;
+ else if (get_u8(&pref, *argv, 0))
+ invarg("\"pref\" value is invalid\n", *argv);
+ addattr8(&req.n, sizeof(req), RTA_PREF, pref);
+ } else if (strcmp(*argv, "encap") == 0) {
+ char buf[1024];
+ struct rtattr *rta = (void *)buf;
+
+ rta->rta_type = RTA_ENCAP;
+ rta->rta_len = RTA_LENGTH(0);
+
+ lwt_parse_encap(rta, sizeof(buf), &argc, &argv,
+ RTA_ENCAP, RTA_ENCAP_TYPE);
+
+ if (rta->rta_len > RTA_LENGTH(0))
+ addraw_l(&req.n, 1024
+ , RTA_DATA(rta), RTA_PAYLOAD(rta));
+ } else if (strcmp(*argv, "ttl-propagate") == 0) {
+ __u8 ttl_prop;
+
+ NEXT_ARG();
+ if (matches(*argv, "enabled") == 0)
+ ttl_prop = 1;
+ else if (matches(*argv, "disabled") == 0)
+ ttl_prop = 0;
+ else
+ invarg("\"ttl-propagate\" value is invalid\n",
+ *argv);
+
+ addattr8(&req.n, sizeof(req), RTA_TTL_PROPAGATE,
+ ttl_prop);
+ } else if (matches(*argv, "fastopen_no_cookie") == 0) {
+ unsigned int fastopen_no_cookie;
+
+ NEXT_ARG();
+ if (get_unsigned(&fastopen_no_cookie, *argv, 0))
+ invarg("\"fastopen_no_cookie\" value is invalid\n", *argv);
+ if (fastopen_no_cookie != 1 && fastopen_no_cookie != 0)
+ invarg("\"fastopen_no_cookie\" value should be 0 or 1\n", *argv);
+ rta_addattr32(mxrta, sizeof(mxbuf), RTAX_FASTOPEN_NO_COOKIE, fastopen_no_cookie);
+ } else {
+ int type;
+ inet_prefix dst;
+
+ if (strcmp(*argv, "to") == 0) {
+ NEXT_ARG();
+ }
+ if ((**argv < '0' || **argv > '9') &&
+ rtnl_rtntype_a2n(&type, *argv) == 0) {
+ NEXT_ARG();
+ req.r.rtm_type = type;
+ type_ok = 1;
+ }
+
+ if (matches(*argv, "help") == 0)
+ usage();
+ if (dst_ok)
+ duparg2("to", *argv);
+ get_prefix(&dst, *argv, req.r.rtm_family);
+ if (req.r.rtm_family == AF_UNSPEC)
+ req.r.rtm_family = dst.family;
+ req.r.rtm_dst_len = dst.bitlen;
+ dst_ok = 1;
+ if (dst.bytelen)
+ addattr_l(&req.n, sizeof(req),
+ RTA_DST, &dst.data, dst.bytelen);
+ }
+ argc--; argv++;
+ }
+
+ if (!dst_ok)
+ usage();
+
+ if (d) {
+ int idx = ll_name_to_index(d);
+
+ if (!idx)
+ return nodev(d);
+ addattr32(&req.n, sizeof(req), RTA_OIF, idx);
+ }
+
+ if (mxrta->rta_len > RTA_LENGTH(0)) {
+ if (mxlock)
+ rta_addattr32(mxrta, sizeof(mxbuf), RTAX_LOCK, mxlock);
+ addattr_l(&req.n, sizeof(req), RTA_METRICS, RTA_DATA(mxrta), RTA_PAYLOAD(mxrta));
+ }
+
+ if (nhs_ok && parse_nexthops(&req.n, &req.r, argc, argv))
+ return -1;
+
+ if (req.r.rtm_family == AF_UNSPEC)
+ req.r.rtm_family = AF_INET;
+
+ if (!table_ok) {
+ if (req.r.rtm_type == RTN_LOCAL ||
+ req.r.rtm_type == RTN_BROADCAST ||
+ req.r.rtm_type == RTN_NAT ||
+ req.r.rtm_type == RTN_ANYCAST)
+ req.r.rtm_table = RT_TABLE_LOCAL;
+ }
+ if (!scope_ok) {
+ if (req.r.rtm_family == AF_INET6 ||
+ req.r.rtm_family == AF_MPLS)
+ req.r.rtm_scope = RT_SCOPE_UNIVERSE;
+ else if (req.r.rtm_type == RTN_LOCAL ||
+ req.r.rtm_type == RTN_NAT)
+ req.r.rtm_scope = RT_SCOPE_HOST;
+ else if (req.r.rtm_type == RTN_BROADCAST ||
+ req.r.rtm_type == RTN_MULTICAST ||
+ req.r.rtm_type == RTN_ANYCAST)
+ req.r.rtm_scope = RT_SCOPE_LINK;
+ else if (req.r.rtm_type == RTN_UNICAST ||
+ req.r.rtm_type == RTN_UNSPEC) {
+ if (cmd == RTM_DELROUTE)
+ req.r.rtm_scope = RT_SCOPE_NOWHERE;
+ else if (!gw_ok && !nhs_ok && !nhid)
+ req.r.rtm_scope = RT_SCOPE_LINK;
+ }
+ }
+
+ if (!type_ok && req.r.rtm_family == AF_MPLS)
+ req.r.rtm_type = RTN_UNICAST;
+
+ if (echo_request)
+ ret = rtnl_echo_talk(&rth, &req.n, json, print_route);
+ else
+ ret = rtnl_talk(&rth, &req.n, NULL);
+
+ if (ret)
+ return -2;
+
+ return 0;
+}
+
+static int iproute_flush_cache(void)
+{
+#define ROUTE_FLUSH_PATH "/proc/sys/net/ipv4/route/flush"
+
+ int len;
+ int flush_fd = open(ROUTE_FLUSH_PATH, O_WRONLY);
+ char *buffer = "-1";
+
+ if (flush_fd < 0) {
+ fprintf(stderr, "Cannot open \"%s\": %s\n",
+ ROUTE_FLUSH_PATH, strerror(errno));
+ return -1;
+ }
+
+ len = strlen(buffer);
+
+ if ((write(flush_fd, (void *)buffer, len)) < len) {
+ fprintf(stderr, "Cannot flush routing cache\n");
+ close(flush_fd);
+ return -1;
+ }
+ close(flush_fd);
+ return 0;
+}
+
+static __u32 route_dump_magic = 0x45311224;
+
+static int save_route(struct nlmsghdr *n, void *arg)
+{
+ int ret;
+ int len = n->nlmsg_len;
+ struct rtmsg *r = NLMSG_DATA(n);
+ struct rtattr *tb[RTA_MAX+1];
+ int host_len;
+
+ host_len = af_bit_len(r->rtm_family);
+ len -= NLMSG_LENGTH(sizeof(*r));
+ parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
+
+ if (!filter_nlmsg(n, tb, host_len))
+ return 0;
+
+ ret = write(STDOUT_FILENO, n, n->nlmsg_len);
+ if ((ret > 0) && (ret != n->nlmsg_len)) {
+ fprintf(stderr, "Short write while saving nlmsg\n");
+ ret = -EIO;
+ }
+
+ return ret == n->nlmsg_len ? 0 : ret;
+}
+
+static int save_route_prep(void)
+{
+ int ret;
+
+ if (isatty(STDOUT_FILENO)) {
+ fprintf(stderr, "Not sending a binary stream to stdout\n");
+ return -1;
+ }
+
+ ret = write(STDOUT_FILENO, &route_dump_magic, sizeof(route_dump_magic));
+ if (ret != sizeof(route_dump_magic)) {
+ fprintf(stderr, "Can't write magic to dump file\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int iproute_dump_filter(struct nlmsghdr *nlh, int reqlen)
+{
+ struct rtmsg *rtm = NLMSG_DATA(nlh);
+ int err;
+
+ rtm->rtm_protocol = filter.protocol;
+ if (filter.cloned)
+ rtm->rtm_flags |= RTM_F_CLONED;
+
+ if (filter.tb) {
+ err = addattr32(nlh, reqlen, RTA_TABLE, filter.tb);
+ if (err)
+ return err;
+ }
+
+ if (filter.oif) {
+ err = addattr32(nlh, reqlen, RTA_OIF, filter.oif);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int iproute_flush(int family, rtnl_filter_t filter_fn)
+{
+ time_t start = time(0);
+ char flushb[4096-512];
+ int round = 0;
+ int ret;
+
+ if (filter.cloned) {
+ if (family != AF_INET6) {
+ iproute_flush_cache();
+ if (show_stats)
+ printf("*** IPv4 routing cache is flushed.\n");
+ }
+ if (family == AF_INET)
+ return 0;
+ }
+
+ filter.flushb = flushb;
+ filter.flushp = 0;
+ filter.flushe = sizeof(flushb);
+
+ for (;;) {
+ if (rtnl_routedump_req(&rth, family, iproute_dump_filter) < 0) {
+ perror("Cannot send dump request");
+ return -2;
+ }
+ filter.flushed = 0;
+ if (rtnl_dump_filter(&rth, filter_fn, stdout) < 0) {
+ fprintf(stderr, "Flush terminated\n");
+ return -2;
+ }
+ if (filter.flushed == 0) {
+ if (show_stats) {
+ if (round == 0 &&
+ (!filter.cloned || family == AF_INET6))
+ printf("Nothing to flush.\n");
+ else
+ printf("*** Flush is complete after %d round%s ***\n",
+ round, round > 1 ? "s" : "");
+ }
+ fflush(stdout);
+ return 0;
+ }
+ round++;
+ ret = flush_update();
+ if (ret < 0)
+ return ret;
+
+ if (time(0) - start > 30) {
+ printf("\n*** Flush not completed after %ld seconds, %d entries remain ***\n",
+ (long)(time(0) - start), filter.flushed);
+ return -1;
+ }
+
+ if (show_stats) {
+ printf("\n*** Round %d, deleting %d entries ***\n",
+ round, filter.flushed);
+ fflush(stdout);
+ }
+ }
+}
+
+static int save_route_errhndlr(struct nlmsghdr *n, void *arg)
+{
+ int err = -*(int *)NLMSG_DATA(n);
+
+ if (n->nlmsg_type == NLMSG_DONE &&
+ filter.tb == RT_TABLE_MAIN &&
+ err == ENOENT)
+ return RTNL_SUPPRESS_NLMSG_DONE_NLERR;
+
+ return RTNL_LET_NLERR;
+}
+
+static int iproute_list_flush_or_save(int argc, char **argv, int action)
+{
+ int dump_family = preferred_family;
+ char *id = NULL;
+ char *od = NULL;
+ unsigned int mark = 0;
+ rtnl_filter_t filter_fn;
+
+ if (action == IPROUTE_SAVE) {
+ if (save_route_prep())
+ return -1;
+
+ filter_fn = save_route;
+ } else
+ filter_fn = print_route;
+
+ iproute_reset_filter(0);
+ filter.tb = RT_TABLE_MAIN;
+
+ if ((action == IPROUTE_FLUSH) && argc <= 0) {
+ fprintf(stderr, "\"ip route flush\" requires arguments.\n");
+ return -1;
+ }
+
+ while (argc > 0) {
+ if (matches(*argv, "table") == 0) {
+ __u32 tid;
+
+ NEXT_ARG();
+ if (rtnl_rttable_a2n(&tid, *argv)) {
+ if (strcmp(*argv, "all") == 0) {
+ filter.tb = 0;
+ } else if (strcmp(*argv, "cache") == 0) {
+ filter.cloned = 1;
+ } else if (strcmp(*argv, "help") == 0) {
+ usage();
+ } else {
+ invarg("table id value is invalid\n", *argv);
+ }
+ } else
+ filter.tb = tid;
+ } else if (matches(*argv, "vrf") == 0) {
+ __u32 tid;
+
+ NEXT_ARG();
+ tid = ipvrf_get_table(*argv);
+ if (tid == 0)
+ invarg("Invalid VRF\n", *argv);
+ filter.tb = tid;
+ filter.typemask = ~(1 << RTN_LOCAL | 1<<RTN_BROADCAST);
+ } else if (matches(*argv, "cached") == 0 ||
+ matches(*argv, "cloned") == 0) {
+ filter.cloned = 1;
+ } else if (strcmp(*argv, "tos") == 0 ||
+ matches(*argv, "dsfield") == 0) {
+ __u32 tos;
+
+ NEXT_ARG();
+ if (rtnl_dsfield_a2n(&tos, *argv))
+ invarg("TOS value is invalid\n", *argv);
+ filter.tos = tos;
+ filter.tosmask = -1;
+ } else if (matches(*argv, "protocol") == 0) {
+ __u32 prot = 0;
+
+ NEXT_ARG();
+ filter.protocolmask = -1;
+ if (rtnl_rtprot_a2n(&prot, *argv)) {
+ if (strcmp(*argv, "all") != 0)
+ invarg("invalid \"protocol\"\n", *argv);
+ prot = 0;
+ filter.protocolmask = 0;
+ }
+ filter.protocol = prot;
+ } else if (matches(*argv, "scope") == 0) {
+ __u32 scope = 0;
+
+ NEXT_ARG();
+ filter.scopemask = -1;
+ if (rtnl_rtscope_a2n(&scope, *argv)) {
+ if (strcmp(*argv, "all") != 0)
+ invarg("invalid \"scope\"\n", *argv);
+ scope = RT_SCOPE_NOWHERE;
+ filter.scopemask = 0;
+ }
+ filter.scope = scope;
+ } else if (matches(*argv, "type") == 0) {
+ int type;
+
+ NEXT_ARG();
+ if (rtnl_rtntype_a2n(&type, *argv))
+ invarg("node type value is invalid\n", *argv);
+ filter.typemask = (1<<type);
+ } else if (strcmp(*argv, "dev") == 0 ||
+ strcmp(*argv, "oif") == 0) {
+ NEXT_ARG();
+ od = *argv;
+ } else if (strcmp(*argv, "iif") == 0) {
+ NEXT_ARG();
+ id = *argv;
+ } else if (strcmp(*argv, "mark") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&mark, *argv, 0))
+ invarg("invalid mark value", *argv);
+ filter.markmask = -1;
+ } else if (matches(*argv, "metric") == 0 ||
+ matches(*argv, "priority") == 0 ||
+ strcmp(*argv, "preference") == 0) {
+ __u32 metric;
+
+ NEXT_ARG();
+ if (get_u32(&metric, *argv, 0))
+ invarg("\"metric\" value is invalid\n", *argv);
+ filter.metric = metric;
+ filter.metricmask = -1;
+ } else if (strcmp(*argv, "via") == 0) {
+ int family;
+
+ NEXT_ARG();
+ family = read_family(*argv);
+ if (family == AF_UNSPEC)
+ family = dump_family;
+ else
+ NEXT_ARG();
+ get_prefix(&filter.rvia, *argv, family);
+ } else if (strcmp(*argv, "src") == 0) {
+ NEXT_ARG();
+ get_prefix(&filter.rprefsrc, *argv, dump_family);
+ } else if (matches(*argv, "realms") == 0) {
+ __u32 realm;
+
+ NEXT_ARG();
+ if (get_rt_realms_or_raw(&realm, *argv))
+ invarg("invalid realms\n", *argv);
+ filter.realm = realm;
+ filter.realmmask = ~0U;
+ if ((filter.realm&0xFFFF) == 0 &&
+ (*argv)[strlen(*argv) - 1] == '/')
+ filter.realmmask &= ~0xFFFF;
+ if ((filter.realm&0xFFFF0000U) == 0 &&
+ (strchr(*argv, '/') == NULL ||
+ (*argv)[0] == '/'))
+ filter.realmmask &= ~0xFFFF0000U;
+ } else if (matches(*argv, "from") == 0) {
+ NEXT_ARG();
+ if (matches(*argv, "root") == 0) {
+ NEXT_ARG();
+ get_prefix(&filter.rsrc, *argv, dump_family);
+ } else if (matches(*argv, "match") == 0) {
+ NEXT_ARG();
+ get_prefix(&filter.msrc, *argv, dump_family);
+ } else {
+ if (matches(*argv, "exact") == 0) {
+ NEXT_ARG();
+ }
+ get_prefix(&filter.msrc, *argv, dump_family);
+ filter.rsrc = filter.msrc;
+ }
+ } else {
+ if (matches(*argv, "to") == 0) {
+ NEXT_ARG();
+ }
+ if (matches(*argv, "root") == 0) {
+ NEXT_ARG();
+ get_prefix(&filter.rdst, *argv, dump_family);
+ } else if (matches(*argv, "match") == 0) {
+ NEXT_ARG();
+ get_prefix(&filter.mdst, *argv, dump_family);
+ } else {
+ if (matches(*argv, "exact") == 0) {
+ NEXT_ARG();
+ }
+ get_prefix(&filter.mdst, *argv, dump_family);
+ filter.rdst = filter.mdst;
+ }
+ }
+ argc--; argv++;
+ }
+
+ if (dump_family == AF_UNSPEC && filter.tb)
+ dump_family = AF_INET;
+
+ if (id || od) {
+ int idx;
+
+ if (id) {
+ idx = ll_name_to_index(id);
+ if (!idx)
+ return nodev(id);
+ filter.iif = idx;
+ filter.iifmask = -1;
+ }
+ if (od) {
+ idx = ll_name_to_index(od);
+ if (!idx)
+ return nodev(od);
+ filter.oif = idx;
+ filter.oifmask = -1;
+ }
+ }
+ filter.mark = mark;
+
+ if (action == IPROUTE_FLUSH)
+ return iproute_flush(dump_family, filter_fn);
+
+ if (rtnl_routedump_req(&rth, dump_family, iproute_dump_filter) < 0) {
+ perror("Cannot send dump request");
+ return -2;
+ }
+
+ new_json_obj(json);
+
+ if (rtnl_dump_filter_errhndlr(&rth, filter_fn, stdout,
+ save_route_errhndlr, NULL) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ delete_json_obj();
+ return -2;
+ }
+
+ delete_json_obj();
+ fflush(stdout);
+ return 0;
+}
+
+
+static int iproute_get(int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct rtmsg r;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_GETROUTE,
+ .r.rtm_family = preferred_family,
+ };
+ char *idev = NULL;
+ char *odev = NULL;
+ struct nlmsghdr *answer;
+ int connected = 0;
+ int fib_match = 0;
+ int from_ok = 0;
+ unsigned int mark = 0;
+ bool address_found = false;
+
+ iproute_reset_filter(0);
+ filter.cloned = 2;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "tos") == 0 ||
+ matches(*argv, "dsfield") == 0) {
+ __u32 tos;
+
+ NEXT_ARG();
+ if (rtnl_dsfield_a2n(&tos, *argv))
+ invarg("TOS value is invalid\n", *argv);
+ req.r.rtm_tos = tos;
+ } else if (matches(*argv, "from") == 0) {
+ inet_prefix addr;
+
+ NEXT_ARG();
+ if (matches(*argv, "help") == 0)
+ usage();
+ from_ok = 1;
+ get_prefix(&addr, *argv, req.r.rtm_family);
+ if (req.r.rtm_family == AF_UNSPEC)
+ req.r.rtm_family = addr.family;
+ if (addr.bytelen)
+ addattr_l(&req.n, sizeof(req), RTA_SRC,
+ &addr.data, addr.bytelen);
+ req.r.rtm_src_len = addr.bitlen;
+ } else if (matches(*argv, "iif") == 0) {
+ NEXT_ARG();
+ idev = *argv;
+ } else if (matches(*argv, "mark") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&mark, *argv, 0))
+ invarg("invalid mark value", *argv);
+ } else if (matches(*argv, "oif") == 0 ||
+ strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ odev = *argv;
+ } else if (matches(*argv, "notify") == 0) {
+ req.r.rtm_flags |= RTM_F_NOTIFY;
+ } else if (matches(*argv, "connected") == 0) {
+ connected = 1;
+ } else if (matches(*argv, "vrf") == 0) {
+ NEXT_ARG();
+ if (!name_is_vrf(*argv))
+ invarg("Invalid VRF\n", *argv);
+ odev = *argv;
+ } else if (matches(*argv, "uid") == 0) {
+ uid_t uid;
+
+ NEXT_ARG();
+ if (get_unsigned(&uid, *argv, 0))
+ invarg("invalid UID\n", *argv);
+ addattr32(&req.n, sizeof(req), RTA_UID, uid);
+ } else if (matches(*argv, "fibmatch") == 0) {
+ fib_match = 1;
+ } else if (strcmp(*argv, "as") == 0) {
+ inet_prefix addr;
+
+ NEXT_ARG();
+ if (strcmp(*argv, "to") == 0)
+ NEXT_ARG();
+ get_addr(&addr, *argv, req.r.rtm_family);
+ if (req.r.rtm_family == AF_UNSPEC)
+ req.r.rtm_family = addr.family;
+ addattr_l(&req.n, sizeof(req), RTA_NEWDST,
+ &addr.data, addr.bytelen);
+ } else if (matches(*argv, "sport") == 0) {
+ __be16 sport;
+
+ NEXT_ARG();
+ if (get_be16(&sport, *argv, 0))
+ invarg("invalid sport\n", *argv);
+ addattr16(&req.n, sizeof(req), RTA_SPORT, sport);
+ } else if (matches(*argv, "dport") == 0) {
+ __be16 dport;
+
+ NEXT_ARG();
+ if (get_be16(&dport, *argv, 0))
+ invarg("invalid dport\n", *argv);
+ addattr16(&req.n, sizeof(req), RTA_DPORT, dport);
+ } else if (matches(*argv, "ipproto") == 0) {
+ int ipproto;
+
+ NEXT_ARG();
+ ipproto = inet_proto_a2n(*argv);
+ if (ipproto < 0)
+ invarg("Invalid \"ipproto\" value\n",
+ *argv);
+ addattr8(&req.n, sizeof(req), RTA_IP_PROTO, ipproto);
+ } else {
+ inet_prefix addr;
+
+ if (strcmp(*argv, "to") == 0) {
+ NEXT_ARG();
+ }
+ if (matches(*argv, "help") == 0)
+ usage();
+ get_prefix(&addr, *argv, req.r.rtm_family);
+ if (req.r.rtm_family == AF_UNSPEC)
+ req.r.rtm_family = addr.family;
+ if (addr.bytelen)
+ addattr_l(&req.n, sizeof(req),
+ RTA_DST, &addr.data, addr.bytelen);
+ if (req.r.rtm_family == AF_INET && addr.bitlen != 32) {
+ fprintf(stderr,
+ "Warning: /%u as prefix is invalid, only /32 (or none) is supported.\n",
+ addr.bitlen);
+ req.r.rtm_dst_len = 32;
+ } else if (req.r.rtm_family == AF_INET6 && addr.bitlen != 128) {
+ fprintf(stderr,
+ "Warning: /%u as prefix is invalid, only /128 (or none) is supported.\n",
+ addr.bitlen);
+ req.r.rtm_dst_len = 128;
+ } else
+ req.r.rtm_dst_len = addr.bitlen;
+ address_found = true;
+ }
+ argc--; argv++;
+ }
+
+ if (!address_found) {
+ fprintf(stderr, "need at least a destination address\n");
+ return -1;
+ }
+
+ if (idev || odev) {
+ int idx;
+
+ if (idev) {
+ idx = ll_name_to_index(idev);
+ if (!idx)
+ return nodev(idev);
+ addattr32(&req.n, sizeof(req), RTA_IIF, idx);
+ }
+ if (odev) {
+ idx = ll_name_to_index(odev);
+ if (!idx)
+ return nodev(odev);
+ addattr32(&req.n, sizeof(req), RTA_OIF, idx);
+ }
+ }
+ if (mark)
+ addattr32(&req.n, sizeof(req), RTA_MARK, mark);
+
+ if (req.r.rtm_family == AF_UNSPEC)
+ req.r.rtm_family = AF_INET;
+
+ /* Only IPv4 supports the RTM_F_LOOKUP_TABLE flag */
+ if (req.r.rtm_family == AF_INET)
+ req.r.rtm_flags |= RTM_F_LOOKUP_TABLE;
+ if (fib_match)
+ req.r.rtm_flags |= RTM_F_FIB_MATCH;
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0)
+ return -2;
+
+ new_json_obj(json);
+
+ if (connected && !from_ok) {
+ struct rtmsg *r = NLMSG_DATA(answer);
+ int len = answer->nlmsg_len;
+ struct rtattr *tb[RTA_MAX+1];
+
+ if (print_route(answer, (void *)stdout) < 0) {
+ fprintf(stderr, "An error :-)\n");
+ delete_json_obj();
+ free(answer);
+ return -1;
+ }
+
+ if (answer->nlmsg_type != RTM_NEWROUTE) {
+ fprintf(stderr, "Not a route?\n");
+ delete_json_obj();
+ free(answer);
+ return -1;
+ }
+ len -= NLMSG_LENGTH(sizeof(*r));
+ if (len < 0) {
+ fprintf(stderr, "Wrong len %d\n", len);
+ delete_json_obj();
+ free(answer);
+ return -1;
+ }
+
+ parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
+
+ if (tb[RTA_PREFSRC]) {
+ tb[RTA_PREFSRC]->rta_type = RTA_SRC;
+ r->rtm_src_len = 8*RTA_PAYLOAD(tb[RTA_PREFSRC]);
+ } else if (!tb[RTA_SRC]) {
+ fprintf(stderr, "Failed to connect the route\n");
+ delete_json_obj();
+ free(answer);
+ return -1;
+ }
+ if (!odev && tb[RTA_OIF])
+ tb[RTA_OIF]->rta_type = 0;
+ if (tb[RTA_GATEWAY])
+ tb[RTA_GATEWAY]->rta_type = 0;
+ if (tb[RTA_VIA])
+ tb[RTA_VIA]->rta_type = 0;
+ if (!idev && tb[RTA_IIF])
+ tb[RTA_IIF]->rta_type = 0;
+ req.n.nlmsg_flags = NLM_F_REQUEST;
+ req.n.nlmsg_type = RTM_GETROUTE;
+
+ delete_json_obj();
+ free(answer);
+ if (rtnl_talk(&rth, &req.n, &answer) < 0)
+ return -2;
+ }
+
+ if (print_route(answer, (void *)stdout) < 0) {
+ fprintf(stderr, "An error :-)\n");
+ delete_json_obj();
+ free(answer);
+ return -1;
+ }
+
+ delete_json_obj();
+ free(answer);
+ return 0;
+}
+
+static int rtattr_cmp(const struct rtattr *rta1, const struct rtattr *rta2)
+{
+ if (!rta1 || !rta2 || rta1->rta_len != rta2->rta_len)
+ return 1;
+
+ return memcmp(RTA_DATA(rta1), RTA_DATA(rta2), RTA_PAYLOAD(rta1));
+}
+
+static int restore_handler(struct rtnl_ctrl_data *ctrl,
+ struct nlmsghdr *n, void *arg)
+{
+ struct rtmsg *r = NLMSG_DATA(n);
+ struct rtattr *tb[RTA_MAX+1];
+ int len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*r));
+ int ret, prio = *(int *)arg;
+
+ parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
+
+ /* Restore routes in correct order:
+ * 0. ones for local addresses,
+ * 1. ones for local networks,
+ * 2. others (remote networks/hosts).
+ */
+ if (!prio && !tb[RTA_GATEWAY] && (!tb[RTA_PREFSRC] ||
+ !rtattr_cmp(tb[RTA_PREFSRC], tb[RTA_DST])))
+ goto restore;
+ else if (prio == 1 && !tb[RTA_GATEWAY] && tb[RTA_PREFSRC] &&
+ rtattr_cmp(tb[RTA_PREFSRC], tb[RTA_DST]))
+ goto restore;
+ else if (prio == 2 && tb[RTA_GATEWAY])
+ goto restore;
+
+ return 0;
+
+restore:
+ n->nlmsg_flags |= NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK;
+
+ ll_init_map(&rth);
+
+ ret = rtnl_talk(&rth, n, NULL);
+ if ((ret < 0) && (errno == EEXIST))
+ ret = 0;
+
+ return ret;
+}
+
+static int route_dump_check_magic(void)
+{
+ int ret;
+ __u32 magic = 0;
+
+ if (isatty(STDIN_FILENO)) {
+ fprintf(stderr, "Can't restore route dump from a terminal\n");
+ return -1;
+ }
+
+ ret = fread(&magic, sizeof(magic), 1, stdin);
+ if (magic != route_dump_magic) {
+ fprintf(stderr, "Magic mismatch (%d elems, %x magic)\n", ret, magic);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int iproute_restore(void)
+{
+ int pos, prio;
+
+ if (route_dump_check_magic())
+ return -1;
+
+ pos = ftell(stdin);
+ if (pos == -1) {
+ perror("Failed to restore: ftell");
+ return -1;
+ }
+
+ for (prio = 0; prio < 3; prio++) {
+ int err;
+
+ err = rtnl_from_file(stdin, &restore_handler, &prio);
+ if (err)
+ return -2;
+
+ if (fseek(stdin, pos, SEEK_SET) == -1) {
+ perror("Failed to restore: fseek");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int show_handler(struct rtnl_ctrl_data *ctrl,
+ struct nlmsghdr *n, void *arg)
+{
+ print_route(n, stdout);
+ return 0;
+}
+
+static int iproute_showdump(void)
+{
+ if (route_dump_check_magic())
+ return -1;
+
+ if (rtnl_from_file(stdin, &show_handler, NULL))
+ return -2;
+
+ return 0;
+}
+
+void iproute_reset_filter(int ifindex)
+{
+ memset(&filter, 0, sizeof(filter));
+ filter.mdst.bitlen = -1;
+ filter.msrc.bitlen = -1;
+ filter.oif = ifindex;
+ if (filter.oif > 0)
+ filter.oifmask = -1;
+}
+
+int do_iproute(int argc, char **argv)
+{
+ if (argc < 1)
+ return iproute_list_flush_or_save(0, NULL, IPROUTE_LIST);
+
+ if (matches(*argv, "add") == 0)
+ return iproute_modify(RTM_NEWROUTE, NLM_F_CREATE|NLM_F_EXCL,
+ argc-1, argv+1);
+ if (matches(*argv, "change") == 0 || strcmp(*argv, "chg") == 0)
+ return iproute_modify(RTM_NEWROUTE, NLM_F_REPLACE,
+ argc-1, argv+1);
+ if (matches(*argv, "replace") == 0)
+ return iproute_modify(RTM_NEWROUTE, NLM_F_CREATE|NLM_F_REPLACE,
+ argc-1, argv+1);
+ if (matches(*argv, "prepend") == 0)
+ return iproute_modify(RTM_NEWROUTE, NLM_F_CREATE,
+ argc-1, argv+1);
+ if (matches(*argv, "append") == 0)
+ return iproute_modify(RTM_NEWROUTE, NLM_F_CREATE|NLM_F_APPEND,
+ argc-1, argv+1);
+ if (matches(*argv, "test") == 0)
+ return iproute_modify(RTM_NEWROUTE, NLM_F_EXCL,
+ argc-1, argv+1);
+ if (matches(*argv, "delete") == 0)
+ return iproute_modify(RTM_DELROUTE, 0,
+ argc-1, argv+1);
+ if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
+ || matches(*argv, "lst") == 0)
+ return iproute_list_flush_or_save(argc-1, argv+1, IPROUTE_LIST);
+ if (matches(*argv, "get") == 0)
+ return iproute_get(argc-1, argv+1);
+ if (matches(*argv, "flush") == 0)
+ return iproute_list_flush_or_save(argc-1, argv+1, IPROUTE_FLUSH);
+ if (matches(*argv, "save") == 0)
+ return iproute_list_flush_or_save(argc-1, argv+1, IPROUTE_SAVE);
+ if (matches(*argv, "restore") == 0)
+ return iproute_restore();
+ if (matches(*argv, "showdump") == 0)
+ return iproute_showdump();
+ if (matches(*argv, "help") == 0)
+ usage();
+
+ fprintf(stderr,
+ "Command \"%s\" is unknown, try \"ip route help\".\n", *argv);
+ exit(-1);
+}
diff --git a/ip/iproute_lwtunnel.c b/ip/iproute_lwtunnel.c
new file mode 100644
index 0000000..9498597
--- /dev/null
+++ b/ip/iproute_lwtunnel.c
@@ -0,0 +1,2285 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iproute_lwtunnel.c
+ *
+ * Authors: Roopa Prabhu, <roopa@cumulusnetworks.com>
+ * Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <linux/ila.h>
+#include <linux/lwtunnel.h>
+#include <linux/mpls_iptunnel.h>
+#include <errno.h>
+
+#include "rt_names.h"
+#include "bpf_util.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "ila_common.h"
+
+#include <linux/seg6.h>
+#include <linux/seg6_iptunnel.h>
+#include <linux/rpl.h>
+#include <linux/rpl_iptunnel.h>
+#include <linux/seg6_hmac.h>
+#include <linux/seg6_local.h>
+#include <linux/if_tunnel.h>
+#include <linux/ioam6.h>
+#include <linux/ioam6_iptunnel.h>
+
+static const char *format_encap_type(uint16_t type)
+{
+ switch (type) {
+ case LWTUNNEL_ENCAP_MPLS:
+ return "mpls";
+ case LWTUNNEL_ENCAP_IP:
+ return "ip";
+ case LWTUNNEL_ENCAP_IP6:
+ return "ip6";
+ case LWTUNNEL_ENCAP_ILA:
+ return "ila";
+ case LWTUNNEL_ENCAP_BPF:
+ return "bpf";
+ case LWTUNNEL_ENCAP_SEG6:
+ return "seg6";
+ case LWTUNNEL_ENCAP_SEG6_LOCAL:
+ return "seg6local";
+ case LWTUNNEL_ENCAP_RPL:
+ return "rpl";
+ case LWTUNNEL_ENCAP_IOAM6:
+ return "ioam6";
+ case LWTUNNEL_ENCAP_XFRM:
+ return "xfrm";
+ default:
+ return "unknown";
+ }
+}
+
+static void encap_type_usage(void)
+{
+ uint16_t i;
+
+ fprintf(stderr, "Usage: ip route ... encap TYPE [ OPTIONS ] [...]\n");
+
+ for (i = 1; i <= LWTUNNEL_ENCAP_MAX; i++)
+ fprintf(stderr, "%s %s\n", format_encap_type(i),
+ i == 1 ? "TYPE := " : " ");
+
+ exit(-1);
+}
+
+static uint16_t read_encap_type(const char *name)
+{
+ if (strcmp(name, "mpls") == 0)
+ return LWTUNNEL_ENCAP_MPLS;
+ else if (strcmp(name, "ip") == 0)
+ return LWTUNNEL_ENCAP_IP;
+ else if (strcmp(name, "ip6") == 0)
+ return LWTUNNEL_ENCAP_IP6;
+ else if (strcmp(name, "ila") == 0)
+ return LWTUNNEL_ENCAP_ILA;
+ else if (strcmp(name, "bpf") == 0)
+ return LWTUNNEL_ENCAP_BPF;
+ else if (strcmp(name, "seg6") == 0)
+ return LWTUNNEL_ENCAP_SEG6;
+ else if (strcmp(name, "seg6local") == 0)
+ return LWTUNNEL_ENCAP_SEG6_LOCAL;
+ else if (strcmp(name, "rpl") == 0)
+ return LWTUNNEL_ENCAP_RPL;
+ else if (strcmp(name, "ioam6") == 0)
+ return LWTUNNEL_ENCAP_IOAM6;
+ else if (strcmp(name, "xfrm") == 0)
+ return LWTUNNEL_ENCAP_XFRM;
+ else if (strcmp(name, "help") == 0)
+ encap_type_usage();
+
+ return LWTUNNEL_ENCAP_NONE;
+}
+
+static void print_srh(FILE *fp, struct ipv6_sr_hdr *srh)
+{
+ int i;
+
+ if (is_json_context())
+ open_json_array(PRINT_JSON, "segs");
+ else
+ fprintf(fp, "segs %d [ ", srh->first_segment + 1);
+
+ for (i = srh->first_segment; i >= 0; i--)
+ print_color_string(PRINT_ANY, COLOR_INET6,
+ NULL, "%s ",
+ rt_addr_n2a(AF_INET6, 16, &srh->segments[i]));
+
+ if (is_json_context())
+ close_json_array(PRINT_JSON, NULL);
+ else
+ fprintf(fp, "] ");
+
+ if (sr_has_hmac(srh)) {
+ unsigned int offset = ((srh->hdrlen + 1) << 3) - 40;
+ struct sr6_tlv_hmac *tlv;
+
+ tlv = (struct sr6_tlv_hmac *)((char *)srh + offset);
+ print_0xhex(PRINT_ANY, "hmac",
+ "hmac %llX ", ntohl(tlv->hmackeyid));
+ }
+}
+
+static const char *seg6_mode_types[] = {
+ [SEG6_IPTUN_MODE_INLINE] = "inline",
+ [SEG6_IPTUN_MODE_ENCAP] = "encap",
+ [SEG6_IPTUN_MODE_L2ENCAP] = "l2encap",
+ [SEG6_IPTUN_MODE_ENCAP_RED] = "encap.red",
+ [SEG6_IPTUN_MODE_L2ENCAP_RED] = "l2encap.red",
+};
+
+static const char *format_seg6mode_type(int mode)
+{
+ if (mode < 0 || mode >= ARRAY_SIZE(seg6_mode_types))
+ return "<unknown>";
+
+ return seg6_mode_types[mode];
+}
+
+static int read_seg6mode_type(const char *mode)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(seg6_mode_types); i++) {
+ if (strcmp(mode, seg6_mode_types[i]) == 0)
+ return i;
+ }
+
+ return -1;
+}
+
+static const char *seg6_flavor_names[SEG6_LOCAL_FLV_OP_MAX + 1] = {
+ [SEG6_LOCAL_FLV_OP_PSP] = "psp",
+ [SEG6_LOCAL_FLV_OP_USP] = "usp",
+ [SEG6_LOCAL_FLV_OP_USD] = "usd",
+ [SEG6_LOCAL_FLV_OP_NEXT_CSID] = "next-csid"
+};
+
+static int read_seg6_local_flv_type(const char *name)
+{
+ int i;
+
+ for (i = 1; i < SEG6_LOCAL_FLV_OP_MAX + 1; ++i) {
+ if (!seg6_flavor_names[i])
+ continue;
+
+ if (strcasecmp(seg6_flavor_names[i], name) == 0)
+ return i;
+ }
+
+ return -1;
+}
+
+static int parse_seg6local_flavors(const char *buf, __u32 *flv_mask)
+{
+ unsigned char flavor_ok[SEG6_LOCAL_FLV_OP_MAX + 1] = { 0, };
+ char *wbuf;
+ __u32 mask = 0;
+ int index;
+ char *s;
+
+ /* strtok changes first parameter, so we need to make a local copy */
+ wbuf = strdupa(buf);
+
+ if (strlen(wbuf) == 0)
+ return -1;
+
+ for (s = strtok(wbuf, ","); s; s = strtok(NULL, ",")) {
+ index = read_seg6_local_flv_type(s);
+ if (index < 0 || index > SEG6_LOCAL_FLV_OP_MAX)
+ return -1;
+ /* we check for duplicates */
+ if (flavor_ok[index]++)
+ return -1;
+
+ mask |= (1 << index);
+ }
+
+ *flv_mask = mask;
+ return 0;
+}
+
+static void print_flavors(FILE *fp, __u32 flavors)
+{
+ int i, fnumber = 0;
+ char *flv_name;
+
+ if (is_json_context())
+ open_json_array(PRINT_JSON, "flavors");
+ else
+ print_string(PRINT_FP, NULL, "flavors ", NULL);
+
+ for (i = 0; i < SEG6_LOCAL_FLV_OP_MAX + 1; ++i) {
+ if (flavors & (1 << i)) {
+ flv_name = (char *) seg6_flavor_names[i];
+ if (!flv_name)
+ continue;
+
+ if (is_json_context())
+ print_string(PRINT_JSON, NULL, NULL, flv_name);
+ else {
+ if (fnumber++ == 0)
+ print_string(PRINT_FP, NULL, "%s", flv_name);
+ else
+ print_string(PRINT_FP, NULL, ",%s", flv_name);
+ }
+ }
+ }
+
+ if (is_json_context())
+ close_json_array(PRINT_JSON, NULL);
+ else
+ print_string(PRINT_FP, NULL, " ", NULL);
+}
+
+static void print_flavors_attr(FILE *fp, const char *key, __u32 value)
+{
+ if (is_json_context()) {
+ print_u64(PRINT_JSON, key, NULL, value);
+ } else {
+ print_string(PRINT_FP, NULL, "%s ", key);
+ print_num(fp, 1, value);
+ }
+}
+
+static void print_encap_seg6(FILE *fp, struct rtattr *encap)
+{
+ struct rtattr *tb[SEG6_IPTUNNEL_MAX+1];
+ struct seg6_iptunnel_encap *tuninfo;
+
+ parse_rtattr_nested(tb, SEG6_IPTUNNEL_MAX, encap);
+
+ if (!tb[SEG6_IPTUNNEL_SRH])
+ return;
+
+ tuninfo = RTA_DATA(tb[SEG6_IPTUNNEL_SRH]);
+ print_string(PRINT_ANY, "mode",
+ "mode %s ", format_seg6mode_type(tuninfo->mode));
+
+ print_srh(fp, tuninfo->srh);
+}
+
+static void print_rpl_srh(FILE *fp, struct ipv6_rpl_sr_hdr *srh)
+{
+ int i;
+
+ if (is_json_context())
+ open_json_array(PRINT_JSON, "segs");
+ else
+ fprintf(fp, "segs %d [ ", srh->segments_left);
+
+ for (i = srh->segments_left - 1; i >= 0; i--) {
+ print_color_string(PRINT_ANY, COLOR_INET6,
+ NULL, "%s ",
+ rt_addr_n2a(AF_INET6, 16, &srh->rpl_segaddr[i]));
+ }
+
+ if (is_json_context())
+ close_json_array(PRINT_JSON, NULL);
+ else
+ fprintf(fp, "] ");
+}
+
+static void print_encap_rpl(FILE *fp, struct rtattr *encap)
+{
+ struct rtattr *tb[RPL_IPTUNNEL_MAX + 1];
+ struct ipv6_rpl_sr_hdr *srh;
+
+ parse_rtattr_nested(tb, RPL_IPTUNNEL_MAX, encap);
+
+ if (!tb[RPL_IPTUNNEL_SRH])
+ return;
+
+ srh = RTA_DATA(tb[RPL_IPTUNNEL_SRH]);
+
+ print_rpl_srh(fp, srh);
+}
+
+static const char *ioam6_mode_types[IOAM6_IPTUNNEL_MODE_MAX + 1] = {
+ [IOAM6_IPTUNNEL_MODE_INLINE] = "inline",
+ [IOAM6_IPTUNNEL_MODE_ENCAP] = "encap",
+ [IOAM6_IPTUNNEL_MODE_AUTO] = "auto",
+};
+
+static const char *format_ioam6mode_type(int mode)
+{
+ if (mode < IOAM6_IPTUNNEL_MODE_MIN ||
+ mode > IOAM6_IPTUNNEL_MODE_MAX ||
+ !ioam6_mode_types[mode])
+ return "<unknown>";
+
+ return ioam6_mode_types[mode];
+}
+
+static __u8 read_ioam6mode_type(const char *mode)
+{
+ __u8 i;
+
+ for (i = IOAM6_IPTUNNEL_MODE_MIN; i <= IOAM6_IPTUNNEL_MODE_MAX; i++) {
+ if (ioam6_mode_types[i] && !strcmp(mode, ioam6_mode_types[i]))
+ return i;
+ }
+
+ return 0;
+}
+
+static void print_encap_ioam6(FILE *fp, struct rtattr *encap)
+{
+ struct rtattr *tb[IOAM6_IPTUNNEL_MAX + 1];
+ struct ioam6_trace_hdr *trace;
+ __u32 freq_k, freq_n;
+ __u8 mode;
+
+ parse_rtattr_nested(tb, IOAM6_IPTUNNEL_MAX, encap);
+ if (!tb[IOAM6_IPTUNNEL_MODE] || !tb[IOAM6_IPTUNNEL_TRACE] ||
+ !tb[IOAM6_IPTUNNEL_FREQ_K] || !tb[IOAM6_IPTUNNEL_FREQ_N])
+ return;
+
+ freq_k = rta_getattr_u32(tb[IOAM6_IPTUNNEL_FREQ_K]);
+ freq_n = rta_getattr_u32(tb[IOAM6_IPTUNNEL_FREQ_N]);
+
+ print_uint(PRINT_ANY, "freqk", "freq %u", freq_k);
+ print_uint(PRINT_ANY, "freqn", "/%u ", freq_n);
+
+ mode = rta_getattr_u8(tb[IOAM6_IPTUNNEL_MODE]);
+ if (!tb[IOAM6_IPTUNNEL_DST] && mode != IOAM6_IPTUNNEL_MODE_INLINE)
+ return;
+
+ print_string(PRINT_ANY, "mode", "mode %s ", format_ioam6mode_type(mode));
+
+ if (mode != IOAM6_IPTUNNEL_MODE_INLINE)
+ print_string(PRINT_ANY, "tundst", "tundst %s ",
+ rt_addr_n2a_rta(AF_INET6, tb[IOAM6_IPTUNNEL_DST]));
+
+ trace = RTA_DATA(tb[IOAM6_IPTUNNEL_TRACE]);
+
+ print_null(PRINT_ANY, "trace", "trace ", NULL);
+ print_null(PRINT_ANY, "prealloc", "prealloc ", NULL);
+ print_hex(PRINT_ANY, "type", "type %#08x ", ntohl(trace->type_be32) >> 8);
+ print_uint(PRINT_ANY, "ns", "ns %u ", ntohs(trace->namespace_id));
+ print_uint(PRINT_ANY, "size", "size %u ", trace->remlen * 4);
+}
+
+static const char *seg6_action_names[SEG6_LOCAL_ACTION_MAX + 1] = {
+ [SEG6_LOCAL_ACTION_END] = "End",
+ [SEG6_LOCAL_ACTION_END_X] = "End.X",
+ [SEG6_LOCAL_ACTION_END_T] = "End.T",
+ [SEG6_LOCAL_ACTION_END_DX2] = "End.DX2",
+ [SEG6_LOCAL_ACTION_END_DX6] = "End.DX6",
+ [SEG6_LOCAL_ACTION_END_DX4] = "End.DX4",
+ [SEG6_LOCAL_ACTION_END_DT6] = "End.DT6",
+ [SEG6_LOCAL_ACTION_END_DT4] = "End.DT4",
+ [SEG6_LOCAL_ACTION_END_B6] = "End.B6",
+ [SEG6_LOCAL_ACTION_END_B6_ENCAP] = "End.B6.Encaps",
+ [SEG6_LOCAL_ACTION_END_BM] = "End.BM",
+ [SEG6_LOCAL_ACTION_END_S] = "End.S",
+ [SEG6_LOCAL_ACTION_END_AS] = "End.AS",
+ [SEG6_LOCAL_ACTION_END_AM] = "End.AM",
+ [SEG6_LOCAL_ACTION_END_BPF] = "End.BPF",
+ [SEG6_LOCAL_ACTION_END_DT46] = "End.DT46",
+};
+
+static const char *format_action_type(int action)
+{
+ if (action < 0 || action > SEG6_LOCAL_ACTION_MAX)
+ return "<invalid>";
+
+ return seg6_action_names[action] ?: "<unknown>";
+}
+
+static int read_action_type(const char *name)
+{
+ int i;
+
+ for (i = 0; i < SEG6_LOCAL_ACTION_MAX + 1; i++) {
+ if (!seg6_action_names[i])
+ continue;
+
+ if (strcmp(seg6_action_names[i], name) == 0)
+ return i;
+ }
+
+ return SEG6_LOCAL_ACTION_UNSPEC;
+}
+
+static void print_encap_bpf_prog(FILE *fp, struct rtattr *encap,
+ const char *str)
+{
+ struct rtattr *tb[LWT_BPF_PROG_MAX+1];
+ const char *progname = NULL;
+
+ parse_rtattr_nested(tb, LWT_BPF_PROG_MAX, encap);
+
+ if (tb[LWT_BPF_PROG_NAME])
+ progname = rta_getattr_str(tb[LWT_BPF_PROG_NAME]);
+
+ if (is_json_context())
+ print_string(PRINT_JSON, str, NULL,
+ progname ? : "<unknown>");
+ else {
+ fprintf(fp, "%s ", str);
+ if (progname)
+ fprintf(fp, "%s ", progname);
+ }
+}
+
+static void print_seg6_local_counters(FILE *fp, struct rtattr *encap)
+{
+ struct rtattr *tb[SEG6_LOCAL_CNT_MAX + 1];
+ __u64 packets = 0, bytes = 0, errors = 0;
+
+ parse_rtattr_nested(tb, SEG6_LOCAL_CNT_MAX, encap);
+
+ if (tb[SEG6_LOCAL_CNT_PACKETS])
+ packets = rta_getattr_u64(tb[SEG6_LOCAL_CNT_PACKETS]);
+
+ if (tb[SEG6_LOCAL_CNT_BYTES])
+ bytes = rta_getattr_u64(tb[SEG6_LOCAL_CNT_BYTES]);
+
+ if (tb[SEG6_LOCAL_CNT_ERRORS])
+ errors = rta_getattr_u64(tb[SEG6_LOCAL_CNT_ERRORS]);
+
+ if (is_json_context()) {
+ open_json_object("stats64");
+
+ print_u64(PRINT_JSON, "packets", NULL, packets);
+ print_u64(PRINT_JSON, "bytes", NULL, bytes);
+ print_u64(PRINT_JSON, "errors", NULL, errors);
+
+ close_json_object();
+ } else {
+ print_string(PRINT_FP, NULL, "%s ", "packets");
+ print_num(fp, 1, packets);
+
+ print_string(PRINT_FP, NULL, "%s ", "bytes");
+ print_num(fp, 1, bytes);
+
+ print_string(PRINT_FP, NULL, "%s ", "errors");
+ print_num(fp, 1, errors);
+ }
+}
+
+static void print_seg6_local_flavors(FILE *fp, struct rtattr *encap)
+{
+ struct rtattr *tb[SEG6_LOCAL_FLV_MAX + 1];
+ __u8 lbl = 0, nfl = 0;
+ __u32 flavors = 0;
+
+ parse_rtattr_nested(tb, SEG6_LOCAL_FLV_MAX, encap);
+
+ if (tb[SEG6_LOCAL_FLV_OPERATION]) {
+ flavors = rta_getattr_u32(tb[SEG6_LOCAL_FLV_OPERATION]);
+ print_flavors(fp, flavors);
+ }
+
+ if (tb[SEG6_LOCAL_FLV_LCBLOCK_BITS]) {
+ lbl = rta_getattr_u8(tb[SEG6_LOCAL_FLV_LCBLOCK_BITS]);
+ print_flavors_attr(fp, "lblen", lbl);
+ }
+
+ if (tb[SEG6_LOCAL_FLV_LCNODE_FN_BITS]) {
+ nfl = rta_getattr_u8(tb[SEG6_LOCAL_FLV_LCNODE_FN_BITS]);
+ print_flavors_attr(fp, "nflen", nfl);
+ }
+}
+
+static void print_encap_seg6local(FILE *fp, struct rtattr *encap)
+{
+ struct rtattr *tb[SEG6_LOCAL_MAX + 1];
+ int action;
+
+ SPRINT_BUF(b1);
+
+ parse_rtattr_nested(tb, SEG6_LOCAL_MAX, encap);
+
+ if (!tb[SEG6_LOCAL_ACTION])
+ return;
+
+ action = rta_getattr_u32(tb[SEG6_LOCAL_ACTION]);
+
+ print_string(PRINT_ANY, "action",
+ "action %s ", format_action_type(action));
+
+ if (tb[SEG6_LOCAL_SRH]) {
+ open_json_object("srh");
+ print_srh(fp, RTA_DATA(tb[SEG6_LOCAL_SRH]));
+ close_json_object();
+ }
+
+ if (tb[SEG6_LOCAL_TABLE])
+ print_string(PRINT_ANY, "table", "table %s ",
+ rtnl_rttable_n2a(rta_getattr_u32(tb[SEG6_LOCAL_TABLE]),
+ b1, sizeof(b1)));
+
+ if (tb[SEG6_LOCAL_VRFTABLE])
+ print_string(PRINT_ANY, "vrftable", "vrftable %s ",
+ rtnl_rttable_n2a(rta_getattr_u32(tb[SEG6_LOCAL_VRFTABLE]),
+ b1, sizeof(b1)));
+
+ if (tb[SEG6_LOCAL_NH4]) {
+ print_string(PRINT_ANY, "nh4",
+ "nh4 %s ", rt_addr_n2a_rta(AF_INET, tb[SEG6_LOCAL_NH4]));
+ }
+
+ if (tb[SEG6_LOCAL_NH6]) {
+ print_string(PRINT_ANY, "nh6",
+ "nh6 %s ", rt_addr_n2a_rta(AF_INET6, tb[SEG6_LOCAL_NH6]));
+ }
+
+ if (tb[SEG6_LOCAL_IIF]) {
+ int iif = rta_getattr_u32(tb[SEG6_LOCAL_IIF]);
+
+ print_string(PRINT_ANY, "iif",
+ "iif %s ", ll_index_to_name(iif));
+ }
+
+ if (tb[SEG6_LOCAL_OIF]) {
+ int oif = rta_getattr_u32(tb[SEG6_LOCAL_OIF]);
+
+ print_string(PRINT_ANY, "oif",
+ "oif %s ", ll_index_to_name(oif));
+ }
+
+ if (tb[SEG6_LOCAL_BPF])
+ print_encap_bpf_prog(fp, tb[SEG6_LOCAL_BPF], "endpoint");
+
+ if (tb[SEG6_LOCAL_COUNTERS] && show_stats)
+ print_seg6_local_counters(fp, tb[SEG6_LOCAL_COUNTERS]);
+
+ if (tb[SEG6_LOCAL_FLAVORS])
+ print_seg6_local_flavors(fp, tb[SEG6_LOCAL_FLAVORS]);
+}
+
+static void print_encap_mpls(FILE *fp, struct rtattr *encap)
+{
+ struct rtattr *tb[MPLS_IPTUNNEL_MAX+1];
+
+ parse_rtattr_nested(tb, MPLS_IPTUNNEL_MAX, encap);
+
+ if (tb[MPLS_IPTUNNEL_DST])
+ print_string(PRINT_ANY, "dst", " %s ",
+ format_host_rta(AF_MPLS, tb[MPLS_IPTUNNEL_DST]));
+ if (tb[MPLS_IPTUNNEL_TTL])
+ print_uint(PRINT_ANY, "ttl", "ttl %u ",
+ rta_getattr_u8(tb[MPLS_IPTUNNEL_TTL]));
+}
+
+static void lwtunnel_print_geneve_opts(struct rtattr *attr)
+{
+ struct rtattr *tb[LWTUNNEL_IP_OPT_GENEVE_MAX + 1];
+ struct rtattr *i = RTA_DATA(attr);
+ int rem = RTA_PAYLOAD(attr);
+ char *name = "geneve_opts";
+ int data_len, offset = 0;
+ char data[rem * 2 + 1];
+ __u16 class;
+ __u8 type;
+
+ print_nl();
+ print_string(PRINT_FP, name, "\t%s ", name);
+ open_json_array(PRINT_JSON, name);
+
+ while (rem) {
+ parse_rtattr(tb, LWTUNNEL_IP_OPT_GENEVE_MAX, i, rem);
+ class = rta_getattr_be16(tb[LWTUNNEL_IP_OPT_GENEVE_CLASS]);
+ type = rta_getattr_u8(tb[LWTUNNEL_IP_OPT_GENEVE_TYPE]);
+ data_len = RTA_PAYLOAD(tb[LWTUNNEL_IP_OPT_GENEVE_DATA]);
+ hexstring_n2a(RTA_DATA(tb[LWTUNNEL_IP_OPT_GENEVE_DATA]),
+ data_len, data, sizeof(data));
+ offset += data_len + 20;
+ rem -= data_len + 20;
+ i = RTA_DATA(attr) + offset;
+
+ open_json_object(NULL);
+ print_uint(PRINT_ANY, "class", "%u", class);
+ print_uint(PRINT_ANY, "type", ":%u", type);
+ if (rem)
+ print_string(PRINT_ANY, "data", ":%s,", data);
+ else
+ print_string(PRINT_ANY, "data", ":%s ", data);
+ close_json_object();
+ }
+
+ close_json_array(PRINT_JSON, name);
+}
+
+static void lwtunnel_print_vxlan_opts(struct rtattr *attr)
+{
+ struct rtattr *tb[LWTUNNEL_IP_OPT_VXLAN_MAX + 1];
+ struct rtattr *i = RTA_DATA(attr);
+ int rem = RTA_PAYLOAD(attr);
+ char *name = "vxlan_opts";
+ __u32 gbp;
+
+ parse_rtattr(tb, LWTUNNEL_IP_OPT_VXLAN_MAX, i, rem);
+ gbp = rta_getattr_u32(tb[LWTUNNEL_IP_OPT_VXLAN_GBP]);
+
+ print_nl();
+ print_string(PRINT_FP, name, "\t%s ", name);
+ open_json_array(PRINT_JSON, name);
+ open_json_object(NULL);
+ print_uint(PRINT_ANY, "gbp", "%u ", gbp);
+ close_json_object();
+ close_json_array(PRINT_JSON, name);
+}
+
+static void lwtunnel_print_erspan_opts(struct rtattr *attr)
+{
+ struct rtattr *tb[LWTUNNEL_IP_OPT_ERSPAN_MAX + 1];
+ struct rtattr *i = RTA_DATA(attr);
+ char *name = "erspan_opts";
+ __u8 ver, hwid, dir;
+ __u32 idx;
+
+ parse_rtattr(tb, LWTUNNEL_IP_OPT_ERSPAN_MAX, i, RTA_PAYLOAD(attr));
+ ver = rta_getattr_u8(tb[LWTUNNEL_IP_OPT_ERSPAN_VER]);
+ if (ver == 1) {
+ idx = rta_getattr_be32(tb[LWTUNNEL_IP_OPT_ERSPAN_INDEX]);
+ dir = 0;
+ hwid = 0;
+ } else {
+ idx = 0;
+ dir = rta_getattr_u8(tb[LWTUNNEL_IP_OPT_ERSPAN_DIR]);
+ hwid = rta_getattr_u8(tb[LWTUNNEL_IP_OPT_ERSPAN_HWID]);
+ }
+
+ print_nl();
+ print_string(PRINT_FP, name, "\t%s ", name);
+ open_json_array(PRINT_JSON, name);
+ open_json_object(NULL);
+ print_uint(PRINT_ANY, "ver", "%u", ver);
+ print_uint(PRINT_ANY, "index", ":%u", idx);
+ print_uint(PRINT_ANY, "dir", ":%u", dir);
+ print_uint(PRINT_ANY, "hwid", ":%u ", hwid);
+ close_json_object();
+ close_json_array(PRINT_JSON, name);
+}
+
+static void lwtunnel_print_opts(struct rtattr *attr)
+{
+ struct rtattr *tb_opt[LWTUNNEL_IP_OPTS_MAX + 1];
+
+ parse_rtattr_nested(tb_opt, LWTUNNEL_IP_OPTS_MAX, attr);
+ if (tb_opt[LWTUNNEL_IP_OPTS_GENEVE])
+ lwtunnel_print_geneve_opts(tb_opt[LWTUNNEL_IP_OPTS_GENEVE]);
+ else if (tb_opt[LWTUNNEL_IP_OPTS_VXLAN])
+ lwtunnel_print_vxlan_opts(tb_opt[LWTUNNEL_IP_OPTS_VXLAN]);
+ else if (tb_opt[LWTUNNEL_IP_OPTS_ERSPAN])
+ lwtunnel_print_erspan_opts(tb_opt[LWTUNNEL_IP_OPTS_ERSPAN]);
+}
+
+static void print_encap_ip(FILE *fp, struct rtattr *encap)
+{
+ struct rtattr *tb[LWTUNNEL_IP_MAX+1];
+ __u16 flags;
+
+ parse_rtattr_nested(tb, LWTUNNEL_IP_MAX, encap);
+
+ if (tb[LWTUNNEL_IP_ID])
+ print_u64(PRINT_ANY, "id", "id %llu ",
+ ntohll(rta_getattr_u64(tb[LWTUNNEL_IP_ID])));
+
+ if (tb[LWTUNNEL_IP_SRC])
+ print_color_string(PRINT_ANY, COLOR_INET,
+ "src", "src %s ",
+ rt_addr_n2a_rta(AF_INET, tb[LWTUNNEL_IP_SRC]));
+
+ if (tb[LWTUNNEL_IP_DST])
+ print_color_string(PRINT_ANY, COLOR_INET,
+ "dst", "dst %s ",
+ rt_addr_n2a_rta(AF_INET, tb[LWTUNNEL_IP_DST]));
+
+ if (tb[LWTUNNEL_IP_TTL])
+ print_uint(PRINT_ANY, "ttl",
+ "ttl %u ", rta_getattr_u8(tb[LWTUNNEL_IP_TTL]));
+
+ if (tb[LWTUNNEL_IP_TOS])
+ print_uint(PRINT_ANY, "tos",
+ "tos %d ", rta_getattr_u8(tb[LWTUNNEL_IP_TOS]));
+
+ if (tb[LWTUNNEL_IP_FLAGS]) {
+ flags = rta_getattr_u16(tb[LWTUNNEL_IP_FLAGS]);
+ if (flags & TUNNEL_KEY)
+ print_bool(PRINT_ANY, "key", "key ", true);
+ if (flags & TUNNEL_CSUM)
+ print_bool(PRINT_ANY, "csum", "csum ", true);
+ if (flags & TUNNEL_SEQ)
+ print_bool(PRINT_ANY, "seq", "seq ", true);
+ }
+
+ if (tb[LWTUNNEL_IP_OPTS])
+ lwtunnel_print_opts(tb[LWTUNNEL_IP_OPTS]);
+}
+
+static void print_encap_ila(FILE *fp, struct rtattr *encap)
+{
+ struct rtattr *tb[ILA_ATTR_MAX+1];
+
+ parse_rtattr_nested(tb, ILA_ATTR_MAX, encap);
+
+ if (tb[ILA_ATTR_LOCATOR]) {
+ char abuf[ADDR64_BUF_SIZE];
+
+ addr64_n2a(rta_getattr_u64(tb[ILA_ATTR_LOCATOR]),
+ abuf, sizeof(abuf));
+ print_string(PRINT_ANY, "locator",
+ " %s ", abuf);
+ }
+
+ if (tb[ILA_ATTR_CSUM_MODE])
+ print_string(PRINT_ANY, "csum_mode",
+ " csum-mode %s ",
+ ila_csum_mode2name(rta_getattr_u8(tb[ILA_ATTR_CSUM_MODE])));
+
+ if (tb[ILA_ATTR_IDENT_TYPE])
+ print_string(PRINT_ANY, "ident_type",
+ " ident-type %s ",
+ ila_ident_type2name(rta_getattr_u8(tb[ILA_ATTR_IDENT_TYPE])));
+
+ if (tb[ILA_ATTR_HOOK_TYPE])
+ print_string(PRINT_ANY, "hook_type",
+ " hook-type %s ",
+ ila_hook_type2name(rta_getattr_u8(tb[ILA_ATTR_HOOK_TYPE])));
+}
+
+static void print_encap_ip6(FILE *fp, struct rtattr *encap)
+{
+ struct rtattr *tb[LWTUNNEL_IP6_MAX+1];
+ __u16 flags;
+
+ parse_rtattr_nested(tb, LWTUNNEL_IP6_MAX, encap);
+
+ if (tb[LWTUNNEL_IP6_ID])
+ print_u64(PRINT_ANY, "id", "id %llu ",
+ ntohll(rta_getattr_u64(tb[LWTUNNEL_IP6_ID])));
+
+ if (tb[LWTUNNEL_IP6_SRC])
+ print_color_string(PRINT_ANY, COLOR_INET6,
+ "src", "src %s ",
+ rt_addr_n2a_rta(AF_INET6, tb[LWTUNNEL_IP6_SRC]));
+
+ if (tb[LWTUNNEL_IP6_DST])
+ print_color_string(PRINT_ANY, COLOR_INET6,
+ "dst", "dst %s ",
+ rt_addr_n2a_rta(AF_INET6, tb[LWTUNNEL_IP6_DST]));
+
+ if (tb[LWTUNNEL_IP6_HOPLIMIT])
+ print_u64(PRINT_ANY, "hoplimit",
+ "hoplimit %u ",
+ rta_getattr_u8(tb[LWTUNNEL_IP6_HOPLIMIT]));
+
+ if (tb[LWTUNNEL_IP6_TC])
+ print_uint(PRINT_ANY, "tc",
+ "tc %u ", rta_getattr_u8(tb[LWTUNNEL_IP6_TC]));
+
+ if (tb[LWTUNNEL_IP6_FLAGS]) {
+ flags = rta_getattr_u16(tb[LWTUNNEL_IP6_FLAGS]);
+ if (flags & TUNNEL_KEY)
+ print_bool(PRINT_ANY, "key", "key ", true);
+ if (flags & TUNNEL_CSUM)
+ print_bool(PRINT_ANY, "csum", "csum ", true);
+ if (flags & TUNNEL_SEQ)
+ print_bool(PRINT_ANY, "seq", "seq ", true);
+ }
+
+ if (tb[LWTUNNEL_IP6_OPTS])
+ lwtunnel_print_opts(tb[LWTUNNEL_IP6_OPTS]);
+}
+
+static void print_encap_bpf(FILE *fp, struct rtattr *encap)
+{
+ struct rtattr *tb[LWT_BPF_MAX+1];
+
+ parse_rtattr_nested(tb, LWT_BPF_MAX, encap);
+
+ if (tb[LWT_BPF_IN])
+ print_encap_bpf_prog(fp, tb[LWT_BPF_IN], "in");
+ if (tb[LWT_BPF_OUT])
+ print_encap_bpf_prog(fp, tb[LWT_BPF_OUT], "out");
+ if (tb[LWT_BPF_XMIT])
+ print_encap_bpf_prog(fp, tb[LWT_BPF_XMIT], "xmit");
+ if (tb[LWT_BPF_XMIT_HEADROOM])
+ print_uint(PRINT_ANY, "headroom",
+ " %u ", rta_getattr_u32(tb[LWT_BPF_XMIT_HEADROOM]));
+}
+
+static void print_encap_xfrm(FILE *fp, struct rtattr *encap)
+{
+ struct rtattr *tb[LWT_XFRM_MAX+1];
+
+ parse_rtattr_nested(tb, LWT_XFRM_MAX, encap);
+
+ if (tb[LWT_XFRM_IF_ID])
+ print_uint(PRINT_ANY, "if_id", "if_id %lu ",
+ rta_getattr_u32(tb[LWT_XFRM_IF_ID]));
+
+ if (tb[LWT_XFRM_LINK]) {
+ int link = rta_getattr_u32(tb[LWT_XFRM_LINK]);
+
+ print_string(PRINT_ANY, "link_dev", "link_dev %s ",
+ ll_index_to_name(link));
+ }
+}
+
+void lwt_print_encap(FILE *fp, struct rtattr *encap_type,
+ struct rtattr *encap)
+{
+ uint16_t et;
+
+ if (!encap_type)
+ return;
+
+ et = rta_getattr_u16(encap_type);
+ open_json_object("encap");
+ print_string(PRINT_ANY, "encap_type", " encap %s ",
+ format_encap_type(et));
+
+ switch (et) {
+ case LWTUNNEL_ENCAP_MPLS:
+ print_encap_mpls(fp, encap);
+ break;
+ case LWTUNNEL_ENCAP_IP:
+ print_encap_ip(fp, encap);
+ break;
+ case LWTUNNEL_ENCAP_ILA:
+ print_encap_ila(fp, encap);
+ break;
+ case LWTUNNEL_ENCAP_IP6:
+ print_encap_ip6(fp, encap);
+ break;
+ case LWTUNNEL_ENCAP_BPF:
+ print_encap_bpf(fp, encap);
+ break;
+ case LWTUNNEL_ENCAP_SEG6:
+ print_encap_seg6(fp, encap);
+ break;
+ case LWTUNNEL_ENCAP_SEG6_LOCAL:
+ print_encap_seg6local(fp, encap);
+ break;
+ case LWTUNNEL_ENCAP_RPL:
+ print_encap_rpl(fp, encap);
+ break;
+ case LWTUNNEL_ENCAP_IOAM6:
+ print_encap_ioam6(fp, encap);
+ break;
+ case LWTUNNEL_ENCAP_XFRM:
+ print_encap_xfrm(fp, encap);
+ break;
+ }
+ close_json_object();
+}
+
+static struct ipv6_sr_hdr *parse_srh(char *segbuf, int hmac, bool encap)
+{
+ struct ipv6_sr_hdr *srh;
+ int nsegs = 0;
+ int srhlen;
+ char *s;
+ int i;
+
+ s = segbuf;
+ for (i = 0; *s; *s++ == ',' ? i++ : *s);
+ nsegs = i + 1;
+
+ if (!encap)
+ nsegs++;
+
+ srhlen = 8 + 16*nsegs;
+
+ if (hmac)
+ srhlen += 40;
+
+ srh = malloc(srhlen);
+ if (srh == NULL)
+ return NULL;
+
+ memset(srh, 0, srhlen);
+
+ srh->hdrlen = (srhlen >> 3) - 1;
+ srh->type = 4;
+ srh->segments_left = nsegs - 1;
+ srh->first_segment = nsegs - 1;
+
+ if (hmac)
+ srh->flags |= SR6_FLAG1_HMAC;
+
+ i = srh->first_segment;
+ for (s = strtok(segbuf, ","); s; s = strtok(NULL, ",")) {
+ inet_prefix addr;
+
+ get_addr(&addr, s, AF_INET6);
+ memcpy(&srh->segments[i], addr.data, sizeof(struct in6_addr));
+ i--;
+ }
+
+ if (hmac) {
+ struct sr6_tlv_hmac *tlv;
+
+ tlv = (struct sr6_tlv_hmac *)((char *)srh + srhlen - 40);
+ tlv->tlvhdr.type = SR6_TLV_HMAC;
+ tlv->tlvhdr.len = 38;
+ tlv->hmackeyid = htonl(hmac);
+ }
+
+ return srh;
+}
+
+static int parse_encap_seg6(struct rtattr *rta, size_t len, int *argcp,
+ char ***argvp)
+{
+ int mode_ok = 0, segs_ok = 0, hmac_ok = 0;
+ struct seg6_iptunnel_encap *tuninfo = NULL;
+ struct ipv6_sr_hdr *srh;
+ char **argv = *argvp;
+ char segbuf[1024] = "";
+ int argc = *argcp;
+ int encap = -1;
+ __u32 hmac = 0;
+ int ret = -1;
+ int srhlen;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "mode") == 0) {
+ NEXT_ARG();
+ if (mode_ok++)
+ duparg2("mode", *argv);
+ encap = read_seg6mode_type(*argv);
+ if (encap < 0)
+ invarg("\"mode\" value is invalid\n", *argv);
+ } else if (strcmp(*argv, "segs") == 0) {
+ NEXT_ARG();
+ if (segs_ok++)
+ duparg2("segs", *argv);
+ if (encap == -1)
+ invarg("\"segs\" provided before \"mode\"\n",
+ *argv);
+
+ strlcpy(segbuf, *argv, sizeof(segbuf));
+ } else if (strcmp(*argv, "hmac") == 0) {
+ NEXT_ARG();
+ if (hmac_ok++)
+ duparg2("hmac", *argv);
+ get_u32(&hmac, *argv, 0);
+ } else {
+ break;
+ }
+ argc--; argv++;
+ }
+
+ srh = parse_srh(segbuf, hmac, encap);
+ if (srh == NULL)
+ goto out;
+ srhlen = (srh->hdrlen + 1) << 3;
+
+ tuninfo = malloc(sizeof(*tuninfo) + srhlen);
+ if (tuninfo == NULL)
+ goto out;
+ memset(tuninfo, 0, sizeof(*tuninfo) + srhlen);
+
+ tuninfo->mode = encap;
+
+ memcpy(tuninfo->srh, srh, srhlen);
+
+ if (rta_addattr_l(rta, len, SEG6_IPTUNNEL_SRH, tuninfo,
+ sizeof(*tuninfo) + srhlen))
+ goto out;
+
+ *argcp = argc + 1;
+ *argvp = argv - 1;
+ ret = 0;
+
+out:
+ free(tuninfo);
+ free(srh);
+
+ return ret;
+}
+
+static struct ipv6_rpl_sr_hdr *parse_rpl_srh(char *segbuf)
+{
+ struct ipv6_rpl_sr_hdr *srh;
+ int nsegs = 0;
+ int srhlen;
+ char *s;
+ int i;
+
+ s = segbuf;
+ for (i = 0; *s; *s++ == ',' ? i++ : *s);
+ nsegs = i + 1;
+
+ srhlen = 8 + 16 * nsegs;
+
+ srh = calloc(1, srhlen);
+ if (srh == NULL)
+ return NULL;
+
+ srh->hdrlen = (srhlen >> 3) - 1;
+ srh->type = 3;
+ srh->segments_left = nsegs;
+
+ for (s = strtok(segbuf, ","); s; s = strtok(NULL, ",")) {
+ inet_prefix addr;
+
+ get_addr(&addr, s, AF_INET6);
+ memcpy(&srh->rpl_segaddr[i], addr.data, sizeof(struct in6_addr));
+ i--;
+ }
+
+ return srh;
+}
+
+static int parse_encap_rpl(struct rtattr *rta, size_t len, int *argcp,
+ char ***argvp)
+{
+ struct ipv6_rpl_sr_hdr *srh;
+ char **argv = *argvp;
+ char segbuf[1024] = "";
+ int argc = *argcp;
+ int segs_ok = 0;
+ int ret = 0;
+ int srhlen;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "segs") == 0) {
+ NEXT_ARG();
+ if (segs_ok++)
+ duparg2("segs", *argv);
+
+ strlcpy(segbuf, *argv, sizeof(segbuf));
+ } else {
+ break;
+ }
+ argc--; argv++;
+ }
+
+ srh = parse_rpl_srh(segbuf);
+ srhlen = (srh->hdrlen + 1) << 3;
+
+ if (rta_addattr_l(rta, len, RPL_IPTUNNEL_SRH, srh,
+ srhlen)) {
+ ret = -1;
+ goto out;
+ }
+
+ *argcp = argc + 1;
+ *argvp = argv - 1;
+
+out:
+ free(srh);
+
+ return ret;
+}
+
+static int parse_ioam6_freq(char *buf, __u32 *freq_k, __u32 *freq_n)
+{
+ char *s;
+ int i;
+
+ s = buf;
+ for (i = 0; *s; *s++ == '/' ? i++ : *s);
+ if (i != 1)
+ return 1;
+
+ s = strtok(buf, "/");
+ if (!s || get_u32(freq_k, s, 10))
+ return 1;
+
+ s = strtok(NULL, "/");
+ if (!s || get_u32(freq_n, s, 10))
+ return 1;
+
+ s = strtok(NULL, "/");
+ if (s)
+ return 1;
+
+ return 0;
+}
+
+static int parse_encap_ioam6(struct rtattr *rta, size_t len, int *argcp,
+ char ***argvp)
+{
+ int ns_found = 0, argc = *argcp;
+ __u16 trace_ns, trace_size = 0;
+ struct ioam6_trace_hdr *trace;
+ char **argv = *argvp;
+ __u32 trace_type = 0;
+ __u32 freq_k, freq_n;
+ char buf[16] = {0};
+ inet_prefix addr;
+ __u8 mode;
+
+ if (strcmp(*argv, "freq") != 0) {
+ freq_k = IOAM6_IPTUNNEL_FREQ_MIN;
+ freq_n = IOAM6_IPTUNNEL_FREQ_MIN;
+ } else {
+ NEXT_ARG();
+
+ if (strlen(*argv) > sizeof(buf) - 1)
+ invarg("Invalid frequency (too long)", *argv);
+
+ strncpy(buf, *argv, sizeof(buf));
+
+ if (parse_ioam6_freq(buf, &freq_k, &freq_n))
+ invarg("Invalid frequency (malformed)", *argv);
+
+ if (freq_k < IOAM6_IPTUNNEL_FREQ_MIN ||
+ freq_k > IOAM6_IPTUNNEL_FREQ_MAX)
+ invarg("Out of bound \"k\" frequency", *argv);
+
+ if (freq_n < IOAM6_IPTUNNEL_FREQ_MIN ||
+ freq_n > IOAM6_IPTUNNEL_FREQ_MAX)
+ invarg("Out of bound \"n\" frequency", *argv);
+
+ if (freq_k > freq_n)
+ invarg("Frequency with k > n is forbidden", *argv);
+
+ NEXT_ARG();
+ }
+
+ if (strcmp(*argv, "mode") != 0) {
+ mode = IOAM6_IPTUNNEL_MODE_INLINE;
+ } else {
+ NEXT_ARG();
+
+ mode = read_ioam6mode_type(*argv);
+ if (!mode)
+ invarg("Invalid mode", *argv);
+
+ NEXT_ARG();
+ }
+
+ if (strcmp(*argv, "tundst") != 0) {
+ if (mode != IOAM6_IPTUNNEL_MODE_INLINE)
+ missarg("tundst");
+ } else {
+ if (mode == IOAM6_IPTUNNEL_MODE_INLINE)
+ invarg("Inline mode does not need tundst", *argv);
+
+ NEXT_ARG();
+
+ get_addr(&addr, *argv, AF_INET6);
+ if (addr.family != AF_INET6 || addr.bytelen != 16)
+ invarg("Invalid IPv6 address for tundst", *argv);
+
+ NEXT_ARG();
+ }
+
+ if (strcmp(*argv, "trace") != 0)
+ missarg("trace");
+
+ NEXT_ARG();
+
+ if (strcmp(*argv, "prealloc") != 0)
+ missarg("prealloc");
+
+ while (NEXT_ARG_OK()) {
+ NEXT_ARG_FWD();
+
+ if (strcmp(*argv, "type") == 0) {
+ NEXT_ARG();
+
+ if (trace_type)
+ duparg2("type", *argv);
+
+ if (get_u32(&trace_type, *argv, 0) || !trace_type)
+ invarg("Invalid trace type", *argv);
+ } else if (strcmp(*argv, "ns") == 0) {
+ NEXT_ARG();
+
+ if (ns_found++)
+ duparg2("ns", *argv);
+
+ if (get_u16(&trace_ns, *argv, 0))
+ invarg("Invalid namespace ID", *argv);
+ } else if (strcmp(*argv, "size") == 0) {
+ NEXT_ARG();
+
+ if (trace_size)
+ duparg2("size", *argv);
+
+ if (get_u16(&trace_size, *argv, 0) || !trace_size)
+ invarg("Invalid trace size", *argv);
+
+ if (trace_size % 4)
+ invarg("Trace size must be a 4-octet multiple",
+ *argv);
+
+ if (trace_size > IOAM6_TRACE_DATA_SIZE_MAX)
+ invarg("Trace size is too big", *argv);
+ } else {
+ break;
+ }
+ }
+
+ if (!trace_type)
+ missarg("type");
+ if (!ns_found)
+ missarg("ns");
+ if (!trace_size)
+ missarg("size");
+
+ trace = calloc(1, sizeof(*trace));
+ if (!trace)
+ return -1;
+
+ trace->type_be32 = htonl(trace_type << 8);
+ trace->namespace_id = htons(trace_ns);
+ trace->remlen = (__u8)(trace_size / 4);
+
+ if (rta_addattr32(rta, len, IOAM6_IPTUNNEL_FREQ_K, freq_k) ||
+ rta_addattr32(rta, len, IOAM6_IPTUNNEL_FREQ_N, freq_n) ||
+ rta_addattr8(rta, len, IOAM6_IPTUNNEL_MODE, mode) ||
+ (mode != IOAM6_IPTUNNEL_MODE_INLINE &&
+ rta_addattr_l(rta, len, IOAM6_IPTUNNEL_DST, &addr.data, addr.bytelen)) ||
+ rta_addattr_l(rta, len, IOAM6_IPTUNNEL_TRACE, trace, sizeof(*trace))) {
+ free(trace);
+ return -1;
+ }
+
+ *argcp = argc + 1;
+ *argvp = argv - 1;
+
+ free(trace);
+ return 0;
+}
+
+struct lwt_x {
+ struct rtattr *rta;
+ size_t len;
+};
+
+static void bpf_lwt_cb(void *lwt_ptr, int fd, const char *annotation)
+{
+ struct lwt_x *x = lwt_ptr;
+
+ rta_addattr32(x->rta, x->len, LWT_BPF_PROG_FD, fd);
+ rta_addattr_l(x->rta, x->len, LWT_BPF_PROG_NAME, annotation,
+ strlen(annotation) + 1);
+}
+
+static const struct bpf_cfg_ops bpf_cb_ops = {
+ .ebpf_cb = bpf_lwt_cb,
+};
+
+static int lwt_parse_bpf(struct rtattr *rta, size_t len,
+ int *argcp, char ***argvp,
+ int attr, const enum bpf_prog_type bpf_type)
+{
+ struct bpf_cfg_in cfg = {
+ .type = bpf_type,
+ .argc = *argcp,
+ .argv = *argvp,
+ };
+ struct lwt_x x = {
+ .rta = rta,
+ .len = len,
+ };
+ struct rtattr *nest;
+ int err;
+
+ nest = rta_nest(rta, len, attr);
+ err = bpf_parse_and_load_common(&cfg, &bpf_cb_ops, &x);
+ if (err < 0) {
+ fprintf(stderr, "Failed to parse eBPF program: %s\n",
+ strerror(-err));
+ return -1;
+ }
+ rta_nest_end(rta, nest);
+
+ *argcp = cfg.argc;
+ *argvp = cfg.argv;
+
+ return 0;
+}
+
+/* for the moment, counters are always initialized to zero by the kernel; so we
+ * do not expect to parse any argument here.
+ */
+static int seg6local_fill_counters(struct rtattr *rta, size_t len, int attr)
+{
+ struct rtattr *nest;
+ int ret;
+
+ nest = rta_nest(rta, len, attr);
+
+ ret = rta_addattr64(rta, len, SEG6_LOCAL_CNT_PACKETS, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = rta_addattr64(rta, len, SEG6_LOCAL_CNT_BYTES, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = rta_addattr64(rta, len, SEG6_LOCAL_CNT_ERRORS, 0);
+ if (ret < 0)
+ return ret;
+
+ rta_nest_end(rta, nest);
+ return 0;
+}
+
+static int seg6local_parse_flavors(struct rtattr *rta, size_t len,
+ int *argcp, char ***argvp, int attr)
+{
+ int lbl_ok = 0, nfl_ok = 0;
+ __u8 lbl = 0, nfl = 0;
+ struct rtattr *nest;
+ __u32 flavors = 0;
+ int ret;
+
+ char **argv = *argvp;
+ int argc = *argcp;
+
+ nest = rta_nest(rta, len, attr);
+
+ ret = parse_seg6local_flavors(*argv, &flavors);
+ if (ret < 0)
+ return ret;
+
+ ret = rta_addattr32(rta, len, SEG6_LOCAL_FLV_OPERATION, flavors);
+ if (ret < 0)
+ return ret;
+
+ if (flavors & (1 << SEG6_LOCAL_FLV_OP_NEXT_CSID)) {
+ NEXT_ARG_FWD();
+ if (strcmp(*argv, "lblen") == 0){
+ NEXT_ARG();
+ if (lbl_ok++)
+ duparg2("lblen", *argv);
+ if (get_u8(&lbl, *argv, 0))
+ invarg("\"locator-block length\" value is invalid\n", *argv);
+ ret = rta_addattr8(rta, len, SEG6_LOCAL_FLV_LCBLOCK_BITS, lbl);
+ NEXT_ARG_FWD();
+ }
+
+ if (strcmp(*argv, "nflen") == 0){
+ NEXT_ARG();
+ if (nfl_ok++)
+ duparg2("nflen", *argv);
+ if (get_u8(&nfl, *argv, 0))
+ invarg("\"locator-node function length\" value is invalid\n", *argv);
+ ret = rta_addattr8(rta, len, SEG6_LOCAL_FLV_LCNODE_FN_BITS, nfl);
+ NEXT_ARG_FWD();
+ }
+ PREV_ARG();
+ }
+
+ rta_nest_end(rta, nest);
+
+ *argcp = argc;
+ *argvp = argv;
+
+ return 0;
+}
+
+static int parse_encap_seg6local(struct rtattr *rta, size_t len, int *argcp,
+ char ***argvp)
+{
+ int nh4_ok = 0, nh6_ok = 0, iif_ok = 0, oif_ok = 0, flavors_ok = 0;
+ int segs_ok = 0, hmac_ok = 0, table_ok = 0, vrftable_ok = 0;
+ int action_ok = 0, srh_ok = 0, bpf_ok = 0, counters_ok = 0;
+ __u32 action = 0, table, vrftable, iif, oif;
+ struct ipv6_sr_hdr *srh;
+ char **argv = *argvp;
+ int argc = *argcp;
+ char segbuf[1024];
+ inet_prefix addr;
+ __u32 hmac = 0;
+ int ret = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "action") == 0) {
+ NEXT_ARG();
+ if (action_ok++)
+ duparg2("action", *argv);
+ action = read_action_type(*argv);
+ if (!action)
+ invarg("\"action\" value is invalid\n", *argv);
+ ret = rta_addattr32(rta, len, SEG6_LOCAL_ACTION,
+ action);
+ } else if (strcmp(*argv, "table") == 0) {
+ NEXT_ARG();
+ if (table_ok++)
+ duparg2("table", *argv);
+ if (rtnl_rttable_a2n(&table, *argv))
+ invarg("invalid table id\n", *argv);
+ ret = rta_addattr32(rta, len, SEG6_LOCAL_TABLE, table);
+ } else if (strcmp(*argv, "vrftable") == 0) {
+ NEXT_ARG();
+ if (vrftable_ok++)
+ duparg2("vrftable", *argv);
+ if (rtnl_rttable_a2n(&vrftable, *argv))
+ invarg("invalid vrf table id\n", *argv);
+ ret = rta_addattr32(rta, len, SEG6_LOCAL_VRFTABLE,
+ vrftable);
+ } else if (strcmp(*argv, "nh4") == 0) {
+ NEXT_ARG();
+ if (nh4_ok++)
+ duparg2("nh4", *argv);
+ get_addr(&addr, *argv, AF_INET);
+ ret = rta_addattr_l(rta, len, SEG6_LOCAL_NH4,
+ &addr.data, addr.bytelen);
+ } else if (strcmp(*argv, "nh6") == 0) {
+ NEXT_ARG();
+ if (nh6_ok++)
+ duparg2("nh6", *argv);
+ get_addr(&addr, *argv, AF_INET6);
+ ret = rta_addattr_l(rta, len, SEG6_LOCAL_NH6,
+ &addr.data, addr.bytelen);
+ } else if (strcmp(*argv, "iif") == 0) {
+ NEXT_ARG();
+ if (iif_ok++)
+ duparg2("iif", *argv);
+ iif = ll_name_to_index(*argv);
+ if (!iif)
+ exit(nodev(*argv));
+ ret = rta_addattr32(rta, len, SEG6_LOCAL_IIF, iif);
+ } else if (strcmp(*argv, "oif") == 0) {
+ NEXT_ARG();
+ if (oif_ok++)
+ duparg2("oif", *argv);
+ oif = ll_name_to_index(*argv);
+ if (!oif)
+ exit(nodev(*argv));
+ ret = rta_addattr32(rta, len, SEG6_LOCAL_OIF, oif);
+ } else if (strcmp(*argv, "count") == 0) {
+ if (counters_ok++)
+ duparg2("count", *argv);
+ ret = seg6local_fill_counters(rta, len,
+ SEG6_LOCAL_COUNTERS);
+ } else if (strcmp(*argv, "flavors") == 0) {
+ NEXT_ARG();
+ if (flavors_ok++)
+ duparg2("flavors", *argv);
+
+ if (seg6local_parse_flavors(rta, len, &argc, &argv,
+ SEG6_LOCAL_FLAVORS))
+ invarg("invalid \"flavors\" attribute\n",
+ *argv);
+ } else if (strcmp(*argv, "srh") == 0) {
+ NEXT_ARG();
+ if (srh_ok++)
+ duparg2("srh", *argv);
+ if (strcmp(*argv, "segs") != 0)
+ invarg("missing \"segs\" attribute for srh\n",
+ *argv);
+ NEXT_ARG();
+ if (segs_ok++)
+ duparg2("segs", *argv);
+ strlcpy(segbuf, *argv, sizeof(segbuf));
+ if (!NEXT_ARG_OK())
+ break;
+ NEXT_ARG();
+ if (strcmp(*argv, "hmac") == 0) {
+ NEXT_ARG();
+ if (hmac_ok++)
+ duparg2("hmac", *argv);
+ get_u32(&hmac, *argv, 0);
+ } else {
+ continue;
+ }
+ } else if (strcmp(*argv, "endpoint") == 0) {
+ NEXT_ARG();
+ if (bpf_ok++)
+ duparg2("endpoint", *argv);
+
+ if (lwt_parse_bpf(rta, len, &argc, &argv, SEG6_LOCAL_BPF,
+ BPF_PROG_TYPE_LWT_SEG6LOCAL) < 0)
+ exit(-1);
+ } else {
+ break;
+ }
+ if (ret)
+ return ret;
+ argc--; argv++;
+ }
+
+ if (!action) {
+ fprintf(stderr, "Missing action type\n");
+ exit(-1);
+ }
+
+ if (srh_ok) {
+ int srhlen;
+
+ srh = parse_srh(segbuf, hmac,
+ action == SEG6_LOCAL_ACTION_END_B6_ENCAP);
+ srhlen = (srh->hdrlen + 1) << 3;
+ ret = rta_addattr_l(rta, len, SEG6_LOCAL_SRH, srh, srhlen);
+ free(srh);
+ }
+
+ *argcp = argc + 1;
+ *argvp = argv - 1;
+
+ return ret;
+}
+
+static int parse_encap_mpls(struct rtattr *rta, size_t len,
+ int *argcp, char ***argvp)
+{
+ inet_prefix addr;
+ int argc = *argcp;
+ char **argv = *argvp;
+ int ttl_ok = 0;
+
+ if (get_addr(&addr, *argv, AF_MPLS)) {
+ fprintf(stderr,
+ "Error: an inet address is expected rather than \"%s\".\n",
+ *argv);
+ exit(1);
+ }
+
+ if (rta_addattr_l(rta, len, MPLS_IPTUNNEL_DST,
+ &addr.data, addr.bytelen))
+ return -1;
+
+ argc--;
+ argv++;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "ttl") == 0) {
+ __u8 ttl;
+
+ NEXT_ARG();
+ if (ttl_ok++)
+ duparg2("ttl", *argv);
+ if (get_u8(&ttl, *argv, 0))
+ invarg("\"ttl\" value is invalid\n", *argv);
+ if (rta_addattr8(rta, len, MPLS_IPTUNNEL_TTL, ttl))
+ return -1;
+ } else {
+ break;
+ }
+ argc--; argv++;
+ }
+
+ /* argv is currently the first unparsed argument,
+ * but the lwt_parse_encap() caller will move to the next,
+ * so step back
+ */
+ *argcp = argc + 1;
+ *argvp = argv - 1;
+
+ return 0;
+}
+
+static int lwtunnel_parse_geneve_opt(char *str, size_t len, struct rtattr *rta)
+{
+ struct rtattr *nest;
+ char *token;
+ int i, err;
+
+ nest = rta_nest(rta, len, LWTUNNEL_IP_OPTS_GENEVE | NLA_F_NESTED);
+ i = 1;
+ token = strsep(&str, ":");
+ while (token) {
+ switch (i) {
+ case LWTUNNEL_IP_OPT_GENEVE_CLASS:
+ {
+ __be16 opt_class;
+
+ if (!strlen(token))
+ break;
+ err = get_be16(&opt_class, token, 0);
+ if (err)
+ return err;
+
+ rta_addattr16(rta, len, i, opt_class);
+ break;
+ }
+ case LWTUNNEL_IP_OPT_GENEVE_TYPE:
+ {
+ __u8 opt_type;
+
+ if (!strlen(token))
+ break;
+ err = get_u8(&opt_type, token, 0);
+ if (err)
+ return err;
+
+ rta_addattr8(rta, len, i, opt_type);
+ break;
+ }
+ case LWTUNNEL_IP_OPT_GENEVE_DATA:
+ {
+ size_t token_len = strlen(token);
+ __u8 *opts;
+
+ if (!token_len)
+ break;
+ opts = malloc(token_len / 2);
+ if (!opts)
+ return -1;
+ if (hex2mem(token, opts, token_len / 2) < 0) {
+ free(opts);
+ return -1;
+ }
+ rta_addattr_l(rta, len, i, opts, token_len / 2);
+ free(opts);
+
+ break;
+ }
+ default:
+ fprintf(stderr, "Unknown \"geneve_opts\" type\n");
+ return -1;
+ }
+
+ token = strsep(&str, ":");
+ i++;
+ }
+ rta_nest_end(rta, nest);
+
+ return 0;
+}
+
+static int lwtunnel_parse_geneve_opts(char *str, size_t len, struct rtattr *rta)
+{
+ char *token;
+ int err;
+
+ token = strsep(&str, ",");
+ while (token) {
+ err = lwtunnel_parse_geneve_opt(token, len, rta);
+ if (err)
+ return err;
+
+ token = strsep(&str, ",");
+ }
+
+ return 0;
+}
+
+static int lwtunnel_parse_vxlan_opts(char *str, size_t len, struct rtattr *rta)
+{
+ struct rtattr *nest;
+ __u32 gbp;
+ int err;
+
+ nest = rta_nest(rta, len, LWTUNNEL_IP_OPTS_VXLAN | NLA_F_NESTED);
+ err = get_u32(&gbp, str, 0);
+ if (err)
+ return err;
+ rta_addattr32(rta, len, LWTUNNEL_IP_OPT_VXLAN_GBP, gbp);
+
+ rta_nest_end(rta, nest);
+ return 0;
+}
+
+static int lwtunnel_parse_erspan_opts(char *str, size_t len, struct rtattr *rta)
+{
+ struct rtattr *nest;
+ char *token;
+ int i, err;
+
+ nest = rta_nest(rta, len, LWTUNNEL_IP_OPTS_ERSPAN | NLA_F_NESTED);
+ i = 1;
+ token = strsep(&str, ":");
+ while (token) {
+ switch (i) {
+ case LWTUNNEL_IP_OPT_ERSPAN_VER:
+ {
+ __u8 opt_type;
+
+ if (!strlen(token))
+ break;
+ err = get_u8(&opt_type, token, 0);
+ if (err)
+ return err;
+
+ rta_addattr8(rta, len, i, opt_type);
+ break;
+ }
+ case LWTUNNEL_IP_OPT_ERSPAN_INDEX:
+ {
+ __be32 opt_class;
+
+ if (!strlen(token))
+ break;
+ err = get_be32(&opt_class, token, 0);
+ if (err)
+ return err;
+
+ rta_addattr32(rta, len, i, opt_class);
+ break;
+ }
+ case LWTUNNEL_IP_OPT_ERSPAN_DIR:
+ {
+ __u8 opt_type;
+
+ if (!strlen(token))
+ break;
+ err = get_u8(&opt_type, token, 0);
+ if (err)
+ return err;
+
+ rta_addattr8(rta, len, i, opt_type);
+ break;
+ }
+ case LWTUNNEL_IP_OPT_ERSPAN_HWID:
+ {
+ __u8 opt_type;
+
+ if (!strlen(token))
+ break;
+ err = get_u8(&opt_type, token, 0);
+ if (err)
+ return err;
+
+ rta_addattr8(rta, len, i, opt_type);
+ break;
+ }
+ default:
+ fprintf(stderr, "Unknown \"geneve_opts\" type\n");
+ return -1;
+ }
+
+ token = strsep(&str, ":");
+ i++;
+ }
+
+ rta_nest_end(rta, nest);
+ return 0;
+}
+
+static int parse_encap_ip(struct rtattr *rta, size_t len,
+ int *argcp, char ***argvp)
+{
+ int id_ok = 0, dst_ok = 0, src_ok = 0, tos_ok = 0, ttl_ok = 0;
+ int key_ok = 0, csum_ok = 0, seq_ok = 0, opts_ok = 0;
+ char **argv = *argvp;
+ int argc = *argcp;
+ int ret = 0;
+ __u16 flags = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "id") == 0) {
+ __u64 id;
+
+ NEXT_ARG();
+ if (id_ok++)
+ duparg2("id", *argv);
+ if (get_be64(&id, *argv, 0))
+ invarg("\"id\" value is invalid\n", *argv);
+ ret = rta_addattr64(rta, len, LWTUNNEL_IP_ID, id);
+ } else if (strcmp(*argv, "dst") == 0) {
+ inet_prefix addr;
+
+ NEXT_ARG();
+ if (dst_ok++)
+ duparg2("dst", *argv);
+ get_addr(&addr, *argv, AF_INET);
+ ret = rta_addattr_l(rta, len, LWTUNNEL_IP_DST,
+ &addr.data, addr.bytelen);
+ } else if (strcmp(*argv, "src") == 0) {
+ inet_prefix addr;
+
+ NEXT_ARG();
+ if (src_ok++)
+ duparg2("src", *argv);
+ get_addr(&addr, *argv, AF_INET);
+ ret = rta_addattr_l(rta, len, LWTUNNEL_IP_SRC,
+ &addr.data, addr.bytelen);
+ } else if (strcmp(*argv, "tos") == 0) {
+ __u32 tos;
+
+ NEXT_ARG();
+ if (tos_ok++)
+ duparg2("tos", *argv);
+ if (rtnl_dsfield_a2n(&tos, *argv))
+ invarg("\"tos\" value is invalid\n", *argv);
+ ret = rta_addattr8(rta, len, LWTUNNEL_IP_TOS, tos);
+ } else if (strcmp(*argv, "ttl") == 0) {
+ __u8 ttl;
+
+ NEXT_ARG();
+ if (ttl_ok++)
+ duparg2("ttl", *argv);
+ if (get_u8(&ttl, *argv, 0))
+ invarg("\"ttl\" value is invalid\n", *argv);
+ ret = rta_addattr8(rta, len, LWTUNNEL_IP_TTL, ttl);
+ } else if (strcmp(*argv, "geneve_opts") == 0) {
+ struct rtattr *nest;
+
+ if (opts_ok++)
+ duparg2("opts", *argv);
+
+ NEXT_ARG();
+
+ nest = rta_nest(rta, len,
+ LWTUNNEL_IP_OPTS | NLA_F_NESTED);
+ ret = lwtunnel_parse_geneve_opts(*argv, len, rta);
+ if (ret)
+ invarg("\"geneve_opts\" value is invalid\n",
+ *argv);
+ rta_nest_end(rta, nest);
+ } else if (strcmp(*argv, "vxlan_opts") == 0) {
+ struct rtattr *nest;
+
+ if (opts_ok++)
+ duparg2("opts", *argv);
+
+ NEXT_ARG();
+
+ nest = rta_nest(rta, len,
+ LWTUNNEL_IP_OPTS | NLA_F_NESTED);
+ ret = lwtunnel_parse_vxlan_opts(*argv, len, rta);
+ if (ret)
+ invarg("\"vxlan_opts\" value is invalid\n",
+ *argv);
+ rta_nest_end(rta, nest);
+ } else if (strcmp(*argv, "erspan_opts") == 0) {
+ struct rtattr *nest;
+
+ if (opts_ok++)
+ duparg2("opts", *argv);
+
+ NEXT_ARG();
+
+ nest = rta_nest(rta, len,
+ LWTUNNEL_IP_OPTS | NLA_F_NESTED);
+ ret = lwtunnel_parse_erspan_opts(*argv, len, rta);
+ if (ret)
+ invarg("\"erspan_opts\" value is invalid\n",
+ *argv);
+ rta_nest_end(rta, nest);
+ } else if (strcmp(*argv, "key") == 0) {
+ if (key_ok++)
+ duparg2("key", *argv);
+ flags |= TUNNEL_KEY;
+ } else if (strcmp(*argv, "csum") == 0) {
+ if (csum_ok++)
+ duparg2("csum", *argv);
+ flags |= TUNNEL_CSUM;
+ } else if (strcmp(*argv, "seq") == 0) {
+ if (seq_ok++)
+ duparg2("seq", *argv);
+ flags |= TUNNEL_SEQ;
+ } else {
+ break;
+ }
+ if (ret)
+ break;
+ argc--; argv++;
+ }
+
+ if (flags)
+ ret = rta_addattr16(rta, len, LWTUNNEL_IP_FLAGS, flags);
+
+ /* argv is currently the first unparsed argument,
+ * but the lwt_parse_encap() caller will move to the next,
+ * so step back
+ */
+ *argcp = argc + 1;
+ *argvp = argv - 1;
+
+ return ret;
+}
+
+static int parse_encap_ila(struct rtattr *rta, size_t len,
+ int *argcp, char ***argvp)
+{
+ __u64 locator;
+ int argc = *argcp;
+ char **argv = *argvp;
+ int ret = 0;
+
+ if (get_addr64(&locator, *argv) < 0) {
+ fprintf(stderr, "Bad locator: %s\n", *argv);
+ exit(1);
+ }
+
+ argc--; argv++;
+
+ if (rta_addattr64(rta, len, ILA_ATTR_LOCATOR, locator))
+ return -1;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "csum-mode") == 0) {
+ int csum_mode;
+
+ NEXT_ARG();
+
+ csum_mode = ila_csum_name2mode(*argv);
+ if (csum_mode < 0)
+ invarg("\"csum-mode\" value is invalid\n",
+ *argv);
+
+ ret = rta_addattr8(rta, len, ILA_ATTR_CSUM_MODE,
+ (__u8)csum_mode);
+
+ argc--; argv++;
+ } else if (strcmp(*argv, "ident-type") == 0) {
+ int ident_type;
+
+ NEXT_ARG();
+
+ ident_type = ila_ident_name2type(*argv);
+ if (ident_type < 0)
+ invarg("\"ident-type\" value is invalid\n",
+ *argv);
+
+ ret = rta_addattr8(rta, len, ILA_ATTR_IDENT_TYPE,
+ (__u8)ident_type);
+
+ argc--; argv++;
+ } else if (strcmp(*argv, "hook-type") == 0) {
+ int hook_type;
+
+ NEXT_ARG();
+
+ hook_type = ila_hook_name2type(*argv);
+ if (hook_type < 0)
+ invarg("\"hook-type\" value is invalid\n",
+ *argv);
+
+ ret = rta_addattr8(rta, len, ILA_ATTR_HOOK_TYPE,
+ (__u8)hook_type);
+
+ argc--; argv++;
+ } else {
+ break;
+ }
+ if (ret)
+ break;
+ }
+
+ /* argv is currently the first unparsed argument,
+ * but the lwt_parse_encap() caller will move to the next,
+ * so step back
+ */
+ *argcp = argc + 1;
+ *argvp = argv - 1;
+
+ return ret;
+}
+
+static int parse_encap_ip6(struct rtattr *rta, size_t len,
+ int *argcp, char ***argvp)
+{
+ int id_ok = 0, dst_ok = 0, src_ok = 0, tos_ok = 0, ttl_ok = 0;
+ int key_ok = 0, csum_ok = 0, seq_ok = 0, opts_ok = 0;
+ char **argv = *argvp;
+ int argc = *argcp;
+ int ret = 0;
+ __u16 flags = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "id") == 0) {
+ __u64 id;
+
+ NEXT_ARG();
+ if (id_ok++)
+ duparg2("id", *argv);
+ if (get_be64(&id, *argv, 0))
+ invarg("\"id\" value is invalid\n", *argv);
+ ret = rta_addattr64(rta, len, LWTUNNEL_IP6_ID, id);
+ } else if (strcmp(*argv, "dst") == 0) {
+ inet_prefix addr;
+
+ NEXT_ARG();
+ if (dst_ok++)
+ duparg2("dst", *argv);
+ get_addr(&addr, *argv, AF_INET6);
+ ret = rta_addattr_l(rta, len, LWTUNNEL_IP6_DST,
+ &addr.data, addr.bytelen);
+ } else if (strcmp(*argv, "src") == 0) {
+ inet_prefix addr;
+
+ NEXT_ARG();
+ if (src_ok++)
+ duparg2("src", *argv);
+ get_addr(&addr, *argv, AF_INET6);
+ ret = rta_addattr_l(rta, len, LWTUNNEL_IP6_SRC,
+ &addr.data, addr.bytelen);
+ } else if (strcmp(*argv, "tc") == 0) {
+ __u32 tc;
+
+ NEXT_ARG();
+ if (tos_ok++)
+ duparg2("tc", *argv);
+ if (rtnl_dsfield_a2n(&tc, *argv))
+ invarg("\"tc\" value is invalid\n", *argv);
+ ret = rta_addattr8(rta, len, LWTUNNEL_IP6_TC, tc);
+ } else if (strcmp(*argv, "hoplimit") == 0) {
+ __u8 hoplimit;
+
+ NEXT_ARG();
+ if (ttl_ok++)
+ duparg2("hoplimit", *argv);
+ if (get_u8(&hoplimit, *argv, 0))
+ invarg("\"hoplimit\" value is invalid\n",
+ *argv);
+ ret = rta_addattr8(rta, len, LWTUNNEL_IP6_HOPLIMIT,
+ hoplimit);
+ } else if (strcmp(*argv, "geneve_opts") == 0) {
+ struct rtattr *nest;
+
+ if (opts_ok++)
+ duparg2("opts", *argv);
+
+ NEXT_ARG();
+
+ nest = rta_nest(rta, len,
+ LWTUNNEL_IP_OPTS | NLA_F_NESTED);
+ ret = lwtunnel_parse_geneve_opts(*argv, len, rta);
+ if (ret)
+ invarg("\"geneve_opts\" value is invalid\n",
+ *argv);
+ rta_nest_end(rta, nest);
+ } else if (strcmp(*argv, "vxlan_opts") == 0) {
+ struct rtattr *nest;
+
+ if (opts_ok++)
+ duparg2("opts", *argv);
+
+ NEXT_ARG();
+
+ nest = rta_nest(rta, len,
+ LWTUNNEL_IP_OPTS | NLA_F_NESTED);
+ ret = lwtunnel_parse_vxlan_opts(*argv, len, rta);
+ if (ret)
+ invarg("\"vxlan_opts\" value is invalid\n",
+ *argv);
+ rta_nest_end(rta, nest);
+ } else if (strcmp(*argv, "erspan_opts") == 0) {
+ struct rtattr *nest;
+
+ if (opts_ok++)
+ duparg2("opts", *argv);
+
+ NEXT_ARG();
+
+ nest = rta_nest(rta, len,
+ LWTUNNEL_IP_OPTS | NLA_F_NESTED);
+ ret = lwtunnel_parse_erspan_opts(*argv, len, rta);
+ if (ret)
+ invarg("\"erspan_opts\" value is invalid\n",
+ *argv);
+ rta_nest_end(rta, nest);
+ } else if (strcmp(*argv, "key") == 0) {
+ if (key_ok++)
+ duparg2("key", *argv);
+ flags |= TUNNEL_KEY;
+ } else if (strcmp(*argv, "csum") == 0) {
+ if (csum_ok++)
+ duparg2("csum", *argv);
+ flags |= TUNNEL_CSUM;
+ } else if (strcmp(*argv, "seq") == 0) {
+ if (seq_ok++)
+ duparg2("seq", *argv);
+ flags |= TUNNEL_SEQ;
+ } else {
+ break;
+ }
+ if (ret)
+ break;
+ argc--; argv++;
+ }
+
+ if (flags)
+ ret = rta_addattr16(rta, len, LWTUNNEL_IP6_FLAGS, flags);
+
+ /* argv is currently the first unparsed argument,
+ * but the lwt_parse_encap() caller will move to the next,
+ * so step back
+ */
+ *argcp = argc + 1;
+ *argvp = argv - 1;
+
+ return ret;
+}
+
+static void lwt_bpf_usage(void)
+{
+ fprintf(stderr, "Usage: ip route ... encap bpf [ in BPF ] [ out BPF ] [ xmit BPF ] [...]\n");
+ fprintf(stderr, "BPF := obj FILE [ section NAME ] [ verbose ]\n");
+ exit(-1);
+}
+
+static int parse_encap_bpf(struct rtattr *rta, size_t len, int *argcp,
+ char ***argvp)
+{
+ char **argv = *argvp;
+ int argc = *argcp;
+ int headroom_set = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "in") == 0) {
+ NEXT_ARG();
+ if (lwt_parse_bpf(rta, len, &argc, &argv, LWT_BPF_IN,
+ BPF_PROG_TYPE_LWT_IN) < 0)
+ return -1;
+ } else if (strcmp(*argv, "out") == 0) {
+ NEXT_ARG();
+ if (lwt_parse_bpf(rta, len, &argc, &argv, LWT_BPF_OUT,
+ BPF_PROG_TYPE_LWT_OUT) < 0)
+ return -1;
+ } else if (strcmp(*argv, "xmit") == 0) {
+ NEXT_ARG();
+ if (lwt_parse_bpf(rta, len, &argc, &argv, LWT_BPF_XMIT,
+ BPF_PROG_TYPE_LWT_XMIT) < 0)
+ return -1;
+ } else if (strcmp(*argv, "headroom") == 0) {
+ unsigned int headroom;
+
+ NEXT_ARG();
+ if (get_unsigned(&headroom, *argv, 0) || headroom == 0)
+ invarg("headroom is invalid\n", *argv);
+ if (!headroom_set)
+ rta_addattr32(rta, len, LWT_BPF_XMIT_HEADROOM,
+ headroom);
+ headroom_set = 1;
+ } else if (strcmp(*argv, "help") == 0) {
+ lwt_bpf_usage();
+ } else {
+ break;
+ }
+ NEXT_ARG_FWD();
+ }
+
+ /* argv is currently the first unparsed argument,
+ * but the lwt_parse_encap() caller will move to the next,
+ * so step back
+ */
+ *argcp = argc + 1;
+ *argvp = argv - 1;
+
+ return 0;
+}
+
+static void lwt_xfrm_usage(void)
+{
+ fprintf(stderr, "Usage: ip route ... encap xfrm if_id IF_ID [ link_dev LINK ]\n");
+ exit(-1);
+}
+
+static int parse_encap_xfrm(struct rtattr *rta, size_t len,
+ int *argcp, char ***argvp)
+{
+ int if_id_ok = 0, link_ok = 0;
+ char **argv = *argvp;
+ int argc = *argcp;
+ int ret = 0;
+
+ while (argc > 0) {
+ if (!strcmp(*argv, "if_id")) {
+ __u32 if_id;
+
+ NEXT_ARG();
+ if (if_id_ok++)
+ duparg2("if_id", *argv);
+ if (get_u32(&if_id, *argv, 0) || if_id == 0)
+ invarg("\"if_id\" value is invalid\n", *argv);
+ ret = rta_addattr32(rta, len, LWT_XFRM_IF_ID, if_id);
+ } else if (!strcmp(*argv, "link_dev")) {
+ int link;
+
+ NEXT_ARG();
+ if (link_ok++)
+ duparg2("link_dev", *argv);
+ link = ll_name_to_index(*argv);
+ if (!link)
+ exit(nodev(*argv));
+ ret = rta_addattr32(rta, len, LWT_XFRM_LINK, link);
+ } else if (!strcmp(*argv, "help")) {
+ lwt_xfrm_usage();
+ }
+ if (ret)
+ break;
+ argc--; argv++;
+ }
+
+ if (!if_id_ok)
+ lwt_xfrm_usage();
+
+ /* argv is currently the first unparsed argument,
+ * but the lwt_parse_encap() caller will move to the next,
+ * so step back
+ */
+ *argcp = argc + 1;
+ *argvp = argv - 1;
+
+ return ret;
+}
+
+int lwt_parse_encap(struct rtattr *rta, size_t len, int *argcp, char ***argvp,
+ int encap_attr, int encap_type_attr)
+{
+ struct rtattr *nest;
+ int argc = *argcp;
+ char **argv = *argvp;
+ __u16 type;
+ int ret = 0;
+
+ NEXT_ARG();
+ type = read_encap_type(*argv);
+ if (!type)
+ invarg("\"encap type\" value is invalid\n", *argv);
+
+ NEXT_ARG();
+ if (argc <= 1) {
+ fprintf(stderr,
+ "Error: unexpected end of line after \"encap\"\n");
+ exit(-1);
+ }
+
+ nest = rta_nest(rta, len, encap_attr);
+ switch (type) {
+ case LWTUNNEL_ENCAP_MPLS:
+ ret = parse_encap_mpls(rta, len, &argc, &argv);
+ break;
+ case LWTUNNEL_ENCAP_IP:
+ ret = parse_encap_ip(rta, len, &argc, &argv);
+ break;
+ case LWTUNNEL_ENCAP_ILA:
+ ret = parse_encap_ila(rta, len, &argc, &argv);
+ break;
+ case LWTUNNEL_ENCAP_IP6:
+ ret = parse_encap_ip6(rta, len, &argc, &argv);
+ break;
+ case LWTUNNEL_ENCAP_BPF:
+ if (parse_encap_bpf(rta, len, &argc, &argv) < 0)
+ exit(-1);
+ break;
+ case LWTUNNEL_ENCAP_SEG6:
+ ret = parse_encap_seg6(rta, len, &argc, &argv);
+ break;
+ case LWTUNNEL_ENCAP_SEG6_LOCAL:
+ ret = parse_encap_seg6local(rta, len, &argc, &argv);
+ break;
+ case LWTUNNEL_ENCAP_RPL:
+ ret = parse_encap_rpl(rta, len, &argc, &argv);
+ break;
+ case LWTUNNEL_ENCAP_IOAM6:
+ ret = parse_encap_ioam6(rta, len, &argc, &argv);
+ break;
+ case LWTUNNEL_ENCAP_XFRM:
+ ret = parse_encap_xfrm(rta, len, &argc, &argv);
+ break;
+ default:
+ fprintf(stderr, "Error: unsupported encap type\n");
+ break;
+ }
+ if (ret)
+ return ret;
+
+ rta_nest_end(rta, nest);
+
+ ret = rta_addattr16(rta, len, encap_type_attr, type);
+
+ *argcp = argc;
+ *argvp = argv;
+
+ return ret;
+}
diff --git a/ip/iprule.c b/ip/iprule.c
new file mode 100644
index 0000000..e503e5c
--- /dev/null
+++ b/ip/iprule.c
@@ -0,0 +1,1074 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iprule.c "ip rule".
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <linux/if.h>
+#include <linux/fib_rules.h>
+#include <errno.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+enum list_action {
+ IPRULE_LIST,
+ IPRULE_FLUSH,
+ IPRULE_SAVE,
+};
+
+extern struct rtnl_handle rth;
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip rule { add | del } SELECTOR ACTION\n"
+ " ip rule { flush | save | restore }\n"
+ " ip rule [ list [ SELECTOR ]]\n"
+ "SELECTOR := [ not ] [ from PREFIX ] [ to PREFIX ] [ tos TOS ]\n"
+ " [ fwmark FWMARK[/MASK] ]\n"
+ " [ iif STRING ] [ oif STRING ] [ pref NUMBER ] [ l3mdev ]\n"
+ " [ uidrange NUMBER-NUMBER ]\n"
+ " [ ipproto PROTOCOL ]\n"
+ " [ sport [ NUMBER | NUMBER-NUMBER ]\n"
+ " [ dport [ NUMBER | NUMBER-NUMBER ] ]\n"
+ "ACTION := [ table TABLE_ID ]\n"
+ " [ protocol PROTO ]\n"
+ " [ nat ADDRESS ]\n"
+ " [ realms [SRCREALM/]DSTREALM ]\n"
+ " [ goto NUMBER ]\n"
+ " SUPPRESSOR\n"
+ "SUPPRESSOR := [ suppress_prefixlength NUMBER ]\n"
+ " [ suppress_ifgroup DEVGROUP ]\n"
+ "TABLE_ID := [ local | main | default | NUMBER ]\n");
+ exit(-1);
+}
+
+static struct
+{
+ int not;
+ int l3mdev;
+ int iifmask, oifmask, uidrange;
+ unsigned int tb;
+ unsigned int tos, tosmask;
+ unsigned int pref, prefmask;
+ unsigned int fwmark, fwmask;
+ uint64_t tun_id;
+ char iif[IFNAMSIZ];
+ char oif[IFNAMSIZ];
+ struct fib_rule_uid_range range;
+ inet_prefix src;
+ inet_prefix dst;
+ int protocol;
+ int protocolmask;
+ struct fib_rule_port_range sport;
+ struct fib_rule_port_range dport;
+ __u8 ipproto;
+} filter;
+
+static inline int frh_get_table(struct fib_rule_hdr *frh, struct rtattr **tb)
+{
+ __u32 table = frh->table;
+ if (tb[RTA_TABLE])
+ table = rta_getattr_u32(tb[RTA_TABLE]);
+ return table;
+}
+
+static bool filter_nlmsg(struct nlmsghdr *n, struct rtattr **tb, int host_len)
+{
+ struct fib_rule_hdr *frh = NLMSG_DATA(n);
+ __u32 table;
+
+ if (preferred_family != AF_UNSPEC && frh->family != preferred_family)
+ return false;
+
+ if (filter.prefmask &&
+ filter.pref ^ (tb[FRA_PRIORITY] ? rta_getattr_u32(tb[FRA_PRIORITY]) : 0))
+ return false;
+ if (filter.not && !(frh->flags & FIB_RULE_INVERT))
+ return false;
+
+ if (filter.src.family) {
+ inet_prefix *f_src = &filter.src;
+
+ if (f_src->family != frh->family ||
+ f_src->bitlen > frh->src_len)
+ return false;
+
+ if (inet_addr_match_rta(f_src, tb[FRA_SRC]))
+ return false;
+ }
+
+ if (filter.dst.family) {
+ inet_prefix *f_dst = &filter.dst;
+
+ if (f_dst->family != frh->family ||
+ f_dst->bitlen > frh->dst_len)
+ return false;
+
+ if (inet_addr_match_rta(f_dst, tb[FRA_DST]))
+ return false;
+ }
+
+ if (filter.tosmask && filter.tos ^ frh->tos)
+ return false;
+
+ if (filter.fwmark) {
+ __u32 mark = 0;
+
+ if (tb[FRA_FWMARK])
+ mark = rta_getattr_u32(tb[FRA_FWMARK]);
+ if (filter.fwmark ^ mark)
+ return false;
+ }
+ if (filter.fwmask) {
+ __u32 mask = 0;
+
+ if (tb[FRA_FWMASK])
+ mask = rta_getattr_u32(tb[FRA_FWMASK]);
+ if (filter.fwmask ^ mask)
+ return false;
+ }
+
+ if (filter.iifmask) {
+ if (tb[FRA_IFNAME]) {
+ if (strcmp(filter.iif, rta_getattr_str(tb[FRA_IFNAME])) != 0)
+ return false;
+ } else {
+ return false;
+ }
+ }
+
+ if (filter.oifmask) {
+ if (tb[FRA_OIFNAME]) {
+ if (strcmp(filter.oif, rta_getattr_str(tb[FRA_OIFNAME])) != 0)
+ return false;
+ } else {
+ return false;
+ }
+ }
+
+ if (filter.l3mdev && !(tb[FRA_L3MDEV] && rta_getattr_u8(tb[FRA_L3MDEV])))
+ return false;
+
+ if (filter.uidrange) {
+ struct fib_rule_uid_range *r = RTA_DATA(tb[FRA_UID_RANGE]);
+
+ if (!tb[FRA_UID_RANGE] ||
+ r->start != filter.range.start ||
+ r->end != filter.range.end)
+ return false;
+ }
+
+ if (filter.ipproto) {
+ __u8 ipproto = 0;
+
+ if (tb[FRA_IP_PROTO])
+ ipproto = rta_getattr_u8(tb[FRA_IP_PROTO]);
+ if (filter.ipproto != ipproto)
+ return false;
+ }
+
+ if (filter.sport.start) {
+ const struct fib_rule_port_range *r;
+
+ if (!tb[FRA_SPORT_RANGE])
+ return false;
+
+ r = RTA_DATA(tb[FRA_SPORT_RANGE]);
+ if (r->start != filter.sport.start ||
+ r->end != filter.sport.end)
+ return false;
+ }
+
+ if (filter.dport.start) {
+ const struct fib_rule_port_range *r;
+
+ if (!tb[FRA_DPORT_RANGE])
+ return false;
+
+ r = RTA_DATA(tb[FRA_DPORT_RANGE]);
+ if (r->start != filter.dport.start ||
+ r->end != filter.dport.end)
+ return false;
+ }
+
+ if (filter.tun_id) {
+ __u64 tun_id = 0;
+
+ if (tb[FRA_TUN_ID]) {
+ tun_id = ntohll(rta_getattr_u64(tb[FRA_TUN_ID]));
+ if (filter.tun_id != tun_id)
+ return false;
+ } else {
+ return false;
+ }
+ }
+
+ table = frh_get_table(frh, tb);
+ if (filter.tb > 0 && filter.tb ^ table)
+ return false;
+
+ return true;
+}
+
+int print_rule(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = arg;
+ struct fib_rule_hdr *frh = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ int host_len = -1;
+ __u32 table, prio = 0;
+ struct rtattr *tb[FRA_MAX+1];
+ SPRINT_BUF(b1);
+
+ if (n->nlmsg_type != RTM_NEWRULE && n->nlmsg_type != RTM_DELRULE)
+ return 0;
+
+ len -= NLMSG_LENGTH(sizeof(*frh));
+ if (len < 0)
+ return -1;
+
+ parse_rtattr(tb, FRA_MAX, RTM_RTA(frh), len);
+
+ host_len = af_bit_len(frh->family);
+
+ if (!filter_nlmsg(n, tb, host_len))
+ return 0;
+
+ open_json_object(NULL);
+ if (n->nlmsg_type == RTM_DELRULE)
+ print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+ if (tb[FRA_PRIORITY])
+ prio = rta_getattr_u32(tb[FRA_PRIORITY]);
+
+ print_uint(PRINT_ANY, "priority", "%u:\t", prio);
+
+ if (frh->flags & FIB_RULE_INVERT)
+ print_null(PRINT_ANY, "not", "not ", NULL);
+
+ if (tb[FRA_SRC]) {
+ const char *src = rt_addr_n2a_rta(frh->family, tb[FRA_SRC]);
+
+ print_string(PRINT_FP, NULL, "from ", NULL);
+ print_color_string(PRINT_ANY, ifa_family_color(frh->family),
+ "src", "%s", src);
+ if (frh->src_len != host_len)
+ print_uint(PRINT_ANY, "srclen", "/%u", frh->src_len);
+ } else if (frh->src_len) {
+ print_string(PRINT_ANY, "src", "from %s", "0");
+ print_uint(PRINT_ANY, "srclen", "/%u", frh->src_len);
+ } else {
+ print_string(PRINT_ANY, "src", "from %s", "all");
+ }
+
+ if (tb[FRA_DST]) {
+ const char *dst = rt_addr_n2a_rta(frh->family, tb[FRA_DST]);
+
+ print_string(PRINT_FP, NULL, " to ", NULL);
+ print_color_string(PRINT_ANY, ifa_family_color(frh->family),
+ "dst", "%s", dst);
+ if (frh->dst_len != host_len)
+ print_uint(PRINT_ANY, "dstlen", "/%u", frh->dst_len);
+ } else if (frh->dst_len) {
+ print_string(PRINT_ANY, "dst", " to %s", "0");
+ print_uint(PRINT_ANY, "dstlen", "/%u", frh->dst_len);
+ }
+
+ if (frh->tos) {
+ print_string(PRINT_ANY, "tos",
+ " tos %s",
+ rtnl_dsfield_n2a(frh->tos, b1, sizeof(b1)));
+ }
+
+ if (tb[FRA_FWMARK] || tb[FRA_FWMASK]) {
+ __u32 mark = 0, mask = 0;
+
+ if (tb[FRA_FWMARK])
+ mark = rta_getattr_u32(tb[FRA_FWMARK]);
+
+ if (tb[FRA_FWMASK] &&
+ (mask = rta_getattr_u32(tb[FRA_FWMASK])) != 0xFFFFFFFF) {
+ print_0xhex(PRINT_ANY, "fwmark", " fwmark %#llx", mark);
+ print_0xhex(PRINT_ANY, "fwmask", "/%#llx", mask);
+ } else {
+ print_0xhex(PRINT_ANY, "fwmark", " fwmark %#llx", mark);
+ }
+ }
+
+ if (tb[FRA_IFNAME]) {
+ if (!is_json_context())
+ fprintf(fp, " iif ");
+ print_color_string(PRINT_ANY, COLOR_IFNAME,
+ "iif", "%s",
+ rta_getattr_str(tb[FRA_IFNAME]));
+
+ if (frh->flags & FIB_RULE_IIF_DETACHED)
+ print_null(PRINT_ANY, "iif_detached", " [detached]",
+ NULL);
+ }
+
+ if (tb[FRA_OIFNAME]) {
+ if (!is_json_context())
+ fprintf(fp, " oif ");
+
+ print_color_string(PRINT_ANY, COLOR_IFNAME, "oif", "%s",
+ rta_getattr_str(tb[FRA_OIFNAME]));
+
+ if (frh->flags & FIB_RULE_OIF_DETACHED)
+ print_null(PRINT_ANY, "oif_detached", " [detached]",
+ NULL);
+ }
+
+ if (tb[FRA_L3MDEV]) {
+ __u8 mdev = rta_getattr_u8(tb[FRA_L3MDEV]);
+
+ if (mdev)
+ print_null(PRINT_ANY, "l3mdev",
+ " lookup [l3mdev-table]", NULL);
+ }
+
+ if (tb[FRA_UID_RANGE]) {
+ struct fib_rule_uid_range *r = RTA_DATA(tb[FRA_UID_RANGE]);
+
+ print_uint(PRINT_ANY, "uid_start", " uidrange %u", r->start);
+ print_uint(PRINT_ANY, "uid_end", "-%u", r->end);
+ }
+
+ if (tb[FRA_IP_PROTO]) {
+ SPRINT_BUF(pbuf);
+ print_string(PRINT_ANY, "ipproto", " ipproto %s",
+ inet_proto_n2a(rta_getattr_u8(tb[FRA_IP_PROTO]),
+ pbuf, sizeof(pbuf)));
+ }
+
+ if (tb[FRA_SPORT_RANGE]) {
+ struct fib_rule_port_range *r = RTA_DATA(tb[FRA_SPORT_RANGE]);
+
+ if (r->start == r->end) {
+ print_uint(PRINT_ANY, "sport", " sport %u", r->start);
+ } else {
+ print_uint(PRINT_ANY, "sport_start", " sport %u",
+ r->start);
+ print_uint(PRINT_ANY, "sport_end", "-%u", r->end);
+ }
+ }
+
+ if (tb[FRA_DPORT_RANGE]) {
+ struct fib_rule_port_range *r = RTA_DATA(tb[FRA_DPORT_RANGE]);
+
+ if (r->start == r->end) {
+ print_uint(PRINT_ANY, "dport", " dport %u", r->start);
+ } else {
+ print_uint(PRINT_ANY, "dport_start", " dport %u",
+ r->start);
+ print_uint(PRINT_ANY, "dport_end", "-%u", r->end);
+ }
+ }
+
+ if (tb[FRA_TUN_ID]) {
+ __u64 tun_id = ntohll(rta_getattr_u64(tb[FRA_TUN_ID]));
+
+ print_u64(PRINT_ANY, "tun_id", " tun_id %llu", tun_id);
+ }
+
+ table = frh_get_table(frh, tb);
+ if (table) {
+ print_string(PRINT_ANY, "table",
+ " lookup %s",
+ rtnl_rttable_n2a(table, b1, sizeof(b1)));
+
+ if (tb[FRA_SUPPRESS_PREFIXLEN]) {
+ int pl = rta_getattr_u32(tb[FRA_SUPPRESS_PREFIXLEN]);
+
+ if (pl != -1)
+ print_int(PRINT_ANY, "suppress_prefixlen",
+ " suppress_prefixlength %d", pl);
+ }
+
+ if (tb[FRA_SUPPRESS_IFGROUP]) {
+ int group = rta_getattr_u32(tb[FRA_SUPPRESS_IFGROUP]);
+
+ if (group != -1) {
+ const char *grname
+ = rtnl_group_n2a(group, b1, sizeof(b1));
+
+ print_string(PRINT_ANY, "suppress_ifgroup",
+ " suppress_ifgroup %s", grname);
+ }
+ }
+ }
+
+ if (tb[FRA_FLOW]) {
+ __u32 to = rta_getattr_u32(tb[FRA_FLOW]);
+ __u32 from = to>>16;
+
+ to &= 0xFFFF;
+ if (from)
+ print_string(PRINT_ANY,
+ "flow_from", " realms %s/",
+ rtnl_rtrealm_n2a(from, b1, sizeof(b1)));
+ else
+ print_string(PRINT_FP, NULL, " realms ", NULL);
+
+ print_string(PRINT_ANY, "flow_to", "%s",
+ rtnl_rtrealm_n2a(to, b1, sizeof(b1)));
+ }
+
+ if (frh->action == RTN_NAT) {
+ if (tb[RTA_GATEWAY]) {
+ const char *gateway;
+
+ gateway = format_host_rta(frh->family, tb[RTA_GATEWAY]);
+
+ print_string(PRINT_ANY, "nat_gateway",
+ " map-to %s", gateway);
+ } else {
+ print_null(PRINT_ANY, "masquerade", " masquerade", NULL);
+ }
+ } else if (frh->action == FR_ACT_GOTO) {
+ if (tb[FRA_GOTO])
+ print_uint(PRINT_ANY, "goto", " goto %u",
+ rta_getattr_u32(tb[FRA_GOTO]));
+ else
+ print_string(PRINT_ANY, "goto", " goto %s", "none");
+
+ if (frh->flags & FIB_RULE_UNRESOLVED)
+ print_null(PRINT_ANY, "unresolved",
+ " [unresolved]", NULL);
+ } else if (frh->action == FR_ACT_NOP) {
+ print_null(PRINT_ANY, "nop", " nop", NULL);
+ } else if (frh->action != FR_ACT_TO_TBL) {
+ print_string(PRINT_ANY, "action", " %s",
+ rtnl_rtntype_n2a(frh->action, b1, sizeof(b1)));
+ }
+
+ if (tb[FRA_PROTOCOL]) {
+ __u8 protocol = rta_getattr_u8(tb[FRA_PROTOCOL]);
+
+ if ((protocol && protocol != RTPROT_KERNEL) || show_details > 0) {
+ print_string(PRINT_ANY, "protocol", " proto %s",
+ rtnl_rtprot_n2a(protocol, b1, sizeof(b1)));
+ }
+ }
+ print_string(PRINT_FP, NULL, "\n", "");
+ close_json_object();
+ fflush(fp);
+ return 0;
+}
+
+static __u32 rule_dump_magic = 0x71706986;
+
+static int save_rule_prep(void)
+{
+ int ret;
+
+ if (isatty(STDOUT_FILENO)) {
+ fprintf(stderr, "Not sending a binary stream to stdout\n");
+ return -1;
+ }
+
+ ret = write(STDOUT_FILENO, &rule_dump_magic, sizeof(rule_dump_magic));
+ if (ret != sizeof(rule_dump_magic)) {
+ fprintf(stderr, "Can't write magic to dump file\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int save_rule(struct nlmsghdr *n, void *arg)
+{
+ int ret;
+
+ ret = write(STDOUT_FILENO, n, n->nlmsg_len);
+ if ((ret > 0) && (ret != n->nlmsg_len)) {
+ fprintf(stderr, "Short write while saving nlmsg\n");
+ ret = -EIO;
+ }
+
+ return ret == n->nlmsg_len ? 0 : ret;
+}
+
+static int flush_rule(struct nlmsghdr *n, void *arg)
+{
+ struct rtnl_handle rth2;
+ struct fib_rule_hdr *frh = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *tb[FRA_MAX+1];
+ int host_len = -1;
+
+ len -= NLMSG_LENGTH(sizeof(*frh));
+ if (len < 0)
+ return -1;
+
+ parse_rtattr(tb, FRA_MAX, RTM_RTA(frh), len);
+
+ host_len = af_bit_len(frh->family);
+ if (!filter_nlmsg(n, tb, host_len))
+ return 0;
+
+ if (tb[FRA_PROTOCOL]) {
+ __u8 protocol = rta_getattr_u8(tb[FRA_PROTOCOL]);
+
+ if ((filter.protocol ^ protocol) & filter.protocolmask)
+ return 0;
+ }
+
+ if (tb[FRA_PRIORITY]) {
+ n->nlmsg_type = RTM_DELRULE;
+ n->nlmsg_flags = NLM_F_REQUEST;
+
+ if (rtnl_open(&rth2, 0) < 0)
+ return -1;
+
+ if (rtnl_talk(&rth2, n, NULL) < 0)
+ return -2;
+
+ rtnl_close(&rth2);
+ }
+
+ return 0;
+}
+
+static int iprule_list_flush_or_save(int argc, char **argv, int action)
+{
+ rtnl_filter_t filter_fn;
+ int af = preferred_family;
+
+ if (af == AF_UNSPEC)
+ af = AF_INET;
+
+ if (action == IPRULE_SAVE && argc > 0) {
+ fprintf(stderr, "\"ip rule save\" does not take any arguments.\n");
+ return -1;
+ }
+
+ switch (action) {
+ case IPRULE_SAVE:
+ if (save_rule_prep())
+ return -1;
+ filter_fn = save_rule;
+ break;
+ case IPRULE_FLUSH:
+ filter_fn = flush_rule;
+ break;
+ default:
+ filter_fn = print_rule;
+ }
+
+ memset(&filter, 0, sizeof(filter));
+
+ while (argc > 0) {
+ if (matches(*argv, "preference") == 0 ||
+ matches(*argv, "order") == 0 ||
+ matches(*argv, "priority") == 0) {
+ __u32 pref;
+
+ NEXT_ARG();
+ if (get_u32(&pref, *argv, 0))
+ invarg("preference value is invalid\n", *argv);
+ filter.pref = pref;
+ filter.prefmask = 1;
+ } else if (strcmp(*argv, "not") == 0) {
+ filter.not = 1;
+ } else if (strcmp(*argv, "tos") == 0 ||
+ strcmp(*argv, "dsfield") == 0) {
+ __u32 tos;
+
+ NEXT_ARG();
+ if (rtnl_dsfield_a2n(&tos, *argv))
+ invarg("TOS value is invalid\n", *argv);
+ filter.tos = tos;
+ filter.tosmask = 1;
+ } else if (strcmp(*argv, "fwmark") == 0) {
+ char *slash;
+ __u32 fwmark, fwmask;
+
+ NEXT_ARG();
+ slash = strchr(*argv, '/');
+ if (slash != NULL)
+ *slash = '\0';
+ if (get_u32(&fwmark, *argv, 0))
+ invarg("fwmark value is invalid\n", *argv);
+ filter.fwmark = fwmark;
+ if (slash) {
+ if (get_u32(&fwmask, slash+1, 0))
+ invarg("fwmask value is invalid\n",
+ slash+1);
+ filter.fwmask = fwmask;
+ }
+ } else if (strcmp(*argv, "dev") == 0 ||
+ strcmp(*argv, "iif") == 0) {
+ NEXT_ARG();
+ if (get_ifname(filter.iif, *argv))
+ invarg("\"iif\"/\"dev\" not a valid ifname", *argv);
+ filter.iifmask = 1;
+ } else if (strcmp(*argv, "oif") == 0) {
+ NEXT_ARG();
+ if (get_ifname(filter.oif, *argv))
+ invarg("\"oif\" not a valid ifname", *argv);
+ filter.oifmask = 1;
+ } else if (strcmp(*argv, "l3mdev") == 0) {
+ filter.l3mdev = 1;
+ } else if (strcmp(*argv, "uidrange") == 0) {
+ NEXT_ARG();
+ filter.uidrange = 1;
+ if (sscanf(*argv, "%u-%u",
+ &filter.range.start,
+ &filter.range.end) != 2)
+ invarg("invalid UID range\n", *argv);
+
+ } else if (matches(*argv, "tun_id") == 0) {
+ __u64 tun_id;
+
+ NEXT_ARG();
+ if (get_u64(&tun_id, *argv, 0))
+ invarg("\"tun_id\" value is invalid\n", *argv);
+ filter.tun_id = tun_id;
+ } else if (matches(*argv, "lookup") == 0 ||
+ matches(*argv, "table") == 0) {
+ __u32 tid;
+
+ NEXT_ARG();
+ if (rtnl_rttable_a2n(&tid, *argv))
+ invarg("table id value is invalid\n", *argv);
+ filter.tb = tid;
+ } else if (matches(*argv, "from") == 0 ||
+ matches(*argv, "src") == 0) {
+ NEXT_ARG();
+ if (get_prefix(&filter.src, *argv, af))
+ invarg("from value is invalid\n", *argv);
+ } else if (matches(*argv, "protocol") == 0) {
+ __u32 prot;
+ NEXT_ARG();
+ filter.protocolmask = -1;
+ if (rtnl_rtprot_a2n(&prot, *argv)) {
+ if (strcmp(*argv, "all") != 0)
+ invarg("invalid \"protocol\"\n", *argv);
+ prot = 0;
+ filter.protocolmask = 0;
+ }
+ filter.protocol = prot;
+ } else if (strcmp(*argv, "ipproto") == 0) {
+ int ipproto;
+
+ NEXT_ARG();
+ ipproto = inet_proto_a2n(*argv);
+ if (ipproto < 0)
+ invarg("Invalid \"ipproto\" value\n", *argv);
+ filter.ipproto = ipproto;
+ } else if (strcmp(*argv, "sport") == 0) {
+ struct fib_rule_port_range r;
+ int ret;
+
+ NEXT_ARG();
+ ret = sscanf(*argv, "%hu-%hu", &r.start, &r.end);
+ if (ret == 1)
+ r.end = r.start;
+ else if (ret != 2)
+ invarg("invalid port range\n", *argv);
+ filter.sport = r;
+ } else if (strcmp(*argv, "dport") == 0) {
+ struct fib_rule_port_range r;
+ int ret;
+
+ NEXT_ARG();
+ ret = sscanf(*argv, "%hu-%hu", &r.start, &r.end);
+ if (ret == 1)
+ r.end = r.start;
+ else if (ret != 2)
+ invarg("invalid dport range\n", *argv);
+ filter.dport = r;
+ } else {
+ if (matches(*argv, "dst") == 0 ||
+ matches(*argv, "to") == 0) {
+ NEXT_ARG();
+ }
+ if (get_prefix(&filter.dst, *argv, af))
+ invarg("to value is invalid\n", *argv);
+ }
+ argc--; argv++;
+ }
+
+ if (rtnl_ruledump_req(&rth, af) < 0) {
+ perror("Cannot send dump request");
+ return 1;
+ }
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&rth, filter_fn, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ delete_json_obj();
+ return 1;
+ }
+ delete_json_obj();
+
+ return 0;
+}
+
+static int rule_dump_check_magic(void)
+{
+ int ret;
+ __u32 magic = 0;
+
+ if (isatty(STDIN_FILENO)) {
+ fprintf(stderr, "Can't restore rule dump from a terminal\n");
+ return -1;
+ }
+
+ ret = fread(&magic, sizeof(magic), 1, stdin);
+ if (magic != rule_dump_magic) {
+ fprintf(stderr, "Magic mismatch (%d elems, %x magic)\n",
+ ret, magic);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int restore_handler(struct rtnl_ctrl_data *ctrl,
+ struct nlmsghdr *n, void *arg)
+{
+ int ret;
+
+ n->nlmsg_flags |= NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK;
+
+ ll_init_map(&rth);
+
+ ret = rtnl_talk(&rth, n, NULL);
+ if ((ret < 0) && (errno == EEXIST))
+ ret = 0;
+
+ return ret;
+}
+
+
+static int iprule_restore(void)
+{
+ if (rule_dump_check_magic())
+ exit(-1);
+
+ exit(rtnl_from_file(stdin, &restore_handler, NULL));
+}
+
+static int iprule_modify(int cmd, int argc, char **argv)
+{
+ int l3mdev_rule = 0;
+ int table_ok = 0;
+ __u32 tid = 0;
+ struct {
+ struct nlmsghdr n;
+ struct fib_rule_hdr frh;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_type = cmd,
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct fib_rule_hdr)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .frh.family = preferred_family,
+ .frh.action = FR_ACT_UNSPEC,
+ };
+ int ret;
+
+ if (cmd == RTM_NEWRULE) {
+ if (argc == 0) {
+ fprintf(stderr,
+ "\"ip rule add\" requires arguments.\n");
+ return -1;
+ }
+ req.n.nlmsg_flags |= NLM_F_CREATE|NLM_F_EXCL;
+ req.frh.action = FR_ACT_TO_TBL;
+ }
+
+ if (cmd == RTM_DELRULE && argc == 0) {
+ fprintf(stderr, "\"ip rule del\" requires arguments.\n");
+ return -1;
+ }
+
+ while (argc > 0) {
+ if (strcmp(*argv, "not") == 0) {
+ req.frh.flags |= FIB_RULE_INVERT;
+ } else if (strcmp(*argv, "from") == 0) {
+ inet_prefix dst;
+
+ NEXT_ARG();
+ get_prefix(&dst, *argv, req.frh.family);
+ req.frh.src_len = dst.bitlen;
+ addattr_l(&req.n, sizeof(req), FRA_SRC,
+ &dst.data, dst.bytelen);
+ } else if (strcmp(*argv, "to") == 0) {
+ inet_prefix dst;
+
+ NEXT_ARG();
+ get_prefix(&dst, *argv, req.frh.family);
+ req.frh.dst_len = dst.bitlen;
+ addattr_l(&req.n, sizeof(req), FRA_DST,
+ &dst.data, dst.bytelen);
+ } else if (matches(*argv, "preference") == 0 ||
+ matches(*argv, "order") == 0 ||
+ matches(*argv, "priority") == 0) {
+ __u32 pref;
+
+ NEXT_ARG();
+ if (get_u32(&pref, *argv, 0))
+ invarg("preference value is invalid\n", *argv);
+ addattr32(&req.n, sizeof(req), FRA_PRIORITY, pref);
+ } else if (strcmp(*argv, "tos") == 0 ||
+ matches(*argv, "dsfield") == 0) {
+ __u32 tos;
+
+ NEXT_ARG();
+ if (rtnl_dsfield_a2n(&tos, *argv))
+ invarg("TOS value is invalid\n", *argv);
+ req.frh.tos = tos;
+ } else if (strcmp(*argv, "fwmark") == 0) {
+ char *slash;
+ __u32 fwmark, fwmask;
+
+ NEXT_ARG();
+
+ slash = strchr(*argv, '/');
+ if (slash != NULL)
+ *slash = '\0';
+ if (get_u32(&fwmark, *argv, 0))
+ invarg("fwmark value is invalid\n", *argv);
+ addattr32(&req.n, sizeof(req), FRA_FWMARK, fwmark);
+ if (slash) {
+ if (get_u32(&fwmask, slash+1, 0))
+ invarg("fwmask value is invalid\n",
+ slash+1);
+ addattr32(&req.n, sizeof(req),
+ FRA_FWMASK, fwmask);
+ }
+ } else if (matches(*argv, "realms") == 0) {
+ __u32 realm;
+
+ NEXT_ARG();
+ if (get_rt_realms_or_raw(&realm, *argv))
+ invarg("invalid realms\n", *argv);
+ addattr32(&req.n, sizeof(req), FRA_FLOW, realm);
+ } else if (matches(*argv, "protocol") == 0) {
+ __u32 proto;
+
+ NEXT_ARG();
+ if (rtnl_rtprot_a2n(&proto, *argv))
+ invarg("\"protocol\" value is invalid\n", *argv);
+ addattr8(&req.n, sizeof(req), FRA_PROTOCOL, proto);
+ } else if (matches(*argv, "tun_id") == 0) {
+ __u64 tun_id;
+
+ NEXT_ARG();
+ if (get_be64(&tun_id, *argv, 0))
+ invarg("\"tun_id\" value is invalid\n", *argv);
+ addattr64(&req.n, sizeof(req), FRA_TUN_ID, tun_id);
+ } else if (matches(*argv, "table") == 0 ||
+ strcmp(*argv, "lookup") == 0) {
+ NEXT_ARG();
+ if (rtnl_rttable_a2n(&tid, *argv))
+ invarg("invalid table ID\n", *argv);
+ if (tid < 256)
+ req.frh.table = tid;
+ else {
+ req.frh.table = RT_TABLE_UNSPEC;
+ addattr32(&req.n, sizeof(req), FRA_TABLE, tid);
+ }
+ table_ok = 1;
+ } else if (matches(*argv, "suppress_prefixlength") == 0 ||
+ strcmp(*argv, "sup_pl") == 0) {
+ int pl;
+
+ NEXT_ARG();
+ if (get_s32(&pl, *argv, 0) || pl < 0)
+ invarg("suppress_prefixlength value is invalid\n",
+ *argv);
+ addattr32(&req.n, sizeof(req),
+ FRA_SUPPRESS_PREFIXLEN, pl);
+ } else if (matches(*argv, "suppress_ifgroup") == 0 ||
+ strcmp(*argv, "sup_group") == 0) {
+ NEXT_ARG();
+ int group;
+
+ if (rtnl_group_a2n(&group, *argv))
+ invarg("Invalid \"suppress_ifgroup\" value\n",
+ *argv);
+ addattr32(&req.n, sizeof(req),
+ FRA_SUPPRESS_IFGROUP, group);
+ } else if (strcmp(*argv, "dev") == 0 ||
+ strcmp(*argv, "iif") == 0) {
+ NEXT_ARG();
+ if (check_ifname(*argv))
+ invarg("\"iif\"/\"dev\" not a valid ifname", *argv);
+ addattr_l(&req.n, sizeof(req), FRA_IFNAME,
+ *argv, strlen(*argv)+1);
+ } else if (strcmp(*argv, "oif") == 0) {
+ NEXT_ARG();
+ if (check_ifname(*argv))
+ invarg("\"oif\" not a valid ifname", *argv);
+ addattr_l(&req.n, sizeof(req), FRA_OIFNAME,
+ *argv, strlen(*argv)+1);
+ } else if (strcmp(*argv, "l3mdev") == 0) {
+ addattr8(&req.n, sizeof(req), FRA_L3MDEV, 1);
+ table_ok = 1;
+ l3mdev_rule = 1;
+ } else if (strcmp(*argv, "uidrange") == 0) {
+ struct fib_rule_uid_range r;
+
+ NEXT_ARG();
+ if (sscanf(*argv, "%u-%u", &r.start, &r.end) != 2)
+ invarg("invalid UID range\n", *argv);
+ addattr_l(&req.n, sizeof(req), FRA_UID_RANGE, &r,
+ sizeof(r));
+ } else if (strcmp(*argv, "nat") == 0 ||
+ matches(*argv, "map-to") == 0) {
+ NEXT_ARG();
+ fprintf(stderr, "Warning: route NAT is deprecated\n");
+ addattr32(&req.n, sizeof(req), RTA_GATEWAY,
+ get_addr32(*argv));
+ req.frh.action = RTN_NAT;
+ } else if (strcmp(*argv, "ipproto") == 0) {
+ int ipproto;
+
+ NEXT_ARG();
+ ipproto = inet_proto_a2n(*argv);
+ if (ipproto < 0)
+ invarg("Invalid \"ipproto\" value\n",
+ *argv);
+ addattr8(&req.n, sizeof(req), FRA_IP_PROTO, ipproto);
+ } else if (strcmp(*argv, "sport") == 0) {
+ struct fib_rule_port_range r;
+ int ret = 0;
+
+ NEXT_ARG();
+ ret = sscanf(*argv, "%hu-%hu", &r.start, &r.end);
+ if (ret == 1)
+ r.end = r.start;
+ else if (ret != 2)
+ invarg("invalid port range\n", *argv);
+ addattr_l(&req.n, sizeof(req), FRA_SPORT_RANGE, &r,
+ sizeof(r));
+ } else if (strcmp(*argv, "dport") == 0) {
+ struct fib_rule_port_range r;
+ int ret = 0;
+
+ NEXT_ARG();
+ ret = sscanf(*argv, "%hu-%hu", &r.start, &r.end);
+ if (ret == 1)
+ r.end = r.start;
+ else if (ret != 2)
+ invarg("invalid dport range\n", *argv);
+ addattr_l(&req.n, sizeof(req), FRA_DPORT_RANGE, &r,
+ sizeof(r));
+ } else {
+ int type;
+
+ if (strcmp(*argv, "type") == 0)
+ NEXT_ARG();
+
+ if (matches(*argv, "help") == 0)
+ usage();
+ else if (matches(*argv, "goto") == 0) {
+ __u32 target;
+
+ type = FR_ACT_GOTO;
+ NEXT_ARG();
+ if (get_u32(&target, *argv, 0))
+ invarg("invalid target\n", *argv);
+ addattr32(&req.n, sizeof(req),
+ FRA_GOTO, target);
+ } else if (matches(*argv, "nop") == 0)
+ type = FR_ACT_NOP;
+ else if (rtnl_rtntype_a2n(&type, *argv))
+ invarg("Failed to parse rule type", *argv);
+ req.frh.action = type;
+ table_ok = 1;
+ }
+ argc--;
+ argv++;
+ }
+
+ if (l3mdev_rule && tid != 0) {
+ fprintf(stderr,
+ "table can not be specified for l3mdev rules\n");
+ return -EINVAL;
+ }
+
+ if (req.frh.family == AF_UNSPEC)
+ req.frh.family = AF_INET;
+
+ if (!table_ok && cmd == RTM_NEWRULE)
+ req.frh.table = RT_TABLE_MAIN;
+
+ if (echo_request)
+ ret = rtnl_echo_talk(&rth, &req.n, json, print_rule);
+ else
+ ret = rtnl_talk(&rth, &req.n, NULL);
+
+ if (ret)
+ return -2;
+
+ return 0;
+}
+
+int do_iprule(int argc, char **argv)
+{
+ if (argc < 1) {
+ return iprule_list_flush_or_save(0, NULL, IPRULE_LIST);
+ } else if (matches(argv[0], "list") == 0 ||
+ matches(argv[0], "lst") == 0 ||
+ matches(argv[0], "show") == 0) {
+ return iprule_list_flush_or_save(argc-1, argv+1, IPRULE_LIST);
+ } else if (matches(argv[0], "save") == 0) {
+ return iprule_list_flush_or_save(argc-1, argv+1, IPRULE_SAVE);
+ } else if (matches(argv[0], "restore") == 0) {
+ return iprule_restore();
+ } else if (matches(argv[0], "add") == 0) {
+ return iprule_modify(RTM_NEWRULE, argc-1, argv+1);
+ } else if (matches(argv[0], "delete") == 0) {
+ return iprule_modify(RTM_DELRULE, argc-1, argv+1);
+ } else if (matches(argv[0], "flush") == 0) {
+ return iprule_list_flush_or_save(argc-1, argv+1, IPRULE_FLUSH);
+ } else if (matches(argv[0], "help") == 0)
+ usage();
+
+ fprintf(stderr,
+ "Command \"%s\" is unknown, try \"ip rule help\".\n", *argv);
+ exit(-1);
+}
+
+int do_multirule(int argc, char **argv)
+{
+ switch (preferred_family) {
+ case AF_UNSPEC:
+ case AF_INET:
+ preferred_family = RTNL_FAMILY_IPMR;
+ break;
+ case AF_INET6:
+ preferred_family = RTNL_FAMILY_IP6MR;
+ break;
+ case RTNL_FAMILY_IPMR:
+ case RTNL_FAMILY_IP6MR:
+ break;
+ default:
+ fprintf(stderr,
+ "Multicast rules are only supported for IPv4/IPv6, was: %i\n",
+ preferred_family);
+ exit(-1);
+ }
+
+ return do_iprule(argc, argv);
+}
diff --git a/ip/ipseg6.c b/ip/ipseg6.c
new file mode 100644
index 0000000..305b896
--- /dev/null
+++ b/ip/ipseg6.c
@@ -0,0 +1,250 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * seg6.c "ip sr/seg6"
+ *
+ * Author: David Lebrun <david.lebrun@uclouvain.be>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <linux/if.h>
+
+#include <linux/genetlink.h>
+#include <linux/seg6_genl.h>
+#include <linux/seg6_hmac.h>
+
+#include "utils.h"
+#include "ip_common.h"
+#include "libgenl.h"
+#include "json_print.h"
+
+#define HMAC_KEY_PROMPT "Enter secret for HMAC key ID (blank to delete): "
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip sr { COMMAND | help }\n"
+ " ip sr hmac show\n"
+ " ip sr hmac set KEYID ALGO\n"
+ " ip sr tunsrc show\n"
+ " ip sr tunsrc set ADDRESS\n"
+ "where ALGO := { sha1 | sha256 }\n");
+ exit(-1);
+}
+
+static struct rtnl_handle grth = { .fd = -1 };
+static int genl_family = -1;
+
+#define SEG6_REQUEST(_req, _bufsiz, _cmd, _flags) \
+ GENL_REQUEST(_req, _bufsiz, genl_family, 0, \
+ SEG6_GENL_VERSION, _cmd, _flags)
+
+static struct {
+ unsigned int cmd;
+ inet_prefix addr;
+ __u32 keyid;
+ const char *pass;
+ __u8 alg_id;
+} opts;
+
+static void print_dumphmac(struct rtattr *attrs[])
+{
+ char secret[64];
+ char *algstr;
+ __u8 slen = rta_getattr_u8(attrs[SEG6_ATTR_SECRETLEN]);
+ __u8 alg_id = rta_getattr_u8(attrs[SEG6_ATTR_ALGID]);
+
+ memset(secret, 0, 64);
+
+ if (slen > 63) {
+ fprintf(stderr, "HMAC secret length %d > 63, truncated\n", slen);
+ slen = 63;
+ }
+
+ memcpy(secret, RTA_DATA(attrs[SEG6_ATTR_SECRET]), slen);
+
+ switch (alg_id) {
+ case SEG6_HMAC_ALGO_SHA1:
+ algstr = "sha1";
+ break;
+ case SEG6_HMAC_ALGO_SHA256:
+ algstr = "sha256";
+ break;
+ default:
+ algstr = "<unknown>";
+ }
+
+ print_uint(PRINT_ANY, "hmac", "hmac %u ",
+ rta_getattr_u32(attrs[SEG6_ATTR_HMACKEYID]));
+ print_string(PRINT_ANY, "algo", "algo %s ", algstr);
+ print_string(PRINT_ANY, "secret", "secret \"%s\"\n", secret);
+}
+
+static void print_tunsrc(struct rtattr *attrs[])
+{
+ const char *dst
+ = rt_addr_n2a(AF_INET6, 16,
+ RTA_DATA(attrs[SEG6_ATTR_DST]));
+
+ print_string(PRINT_ANY, "tunsrc",
+ "tunsrc addr %s\n", dst);
+}
+
+static int process_msg(struct nlmsghdr *n, void *arg)
+{
+ struct rtattr *attrs[SEG6_ATTR_MAX + 1];
+ struct genlmsghdr *ghdr;
+ int len = n->nlmsg_len;
+
+ if (n->nlmsg_type != genl_family)
+ return -1;
+
+ len -= NLMSG_LENGTH(GENL_HDRLEN);
+ if (len < 0)
+ return -1;
+
+ ghdr = NLMSG_DATA(n);
+
+ parse_rtattr(attrs, SEG6_ATTR_MAX, (void *)ghdr + GENL_HDRLEN, len);
+
+ open_json_object(NULL);
+ switch (ghdr->cmd) {
+ case SEG6_CMD_DUMPHMAC:
+ print_dumphmac(attrs);
+ break;
+
+ case SEG6_CMD_GET_TUNSRC:
+ print_tunsrc(attrs);
+ break;
+ }
+ close_json_object();
+
+ return 0;
+}
+
+static int seg6_do_cmd(void)
+{
+ SEG6_REQUEST(req, 1024, opts.cmd, NLM_F_REQUEST);
+ struct nlmsghdr *answer;
+ int repl = 0, dump = 0;
+
+ if (genl_family < 0) {
+ if (rtnl_open_byproto(&grth, 0, NETLINK_GENERIC) < 0) {
+ fprintf(stderr, "Cannot open generic netlink socket\n");
+ exit(1);
+ }
+ genl_family = genl_resolve_family(&grth, SEG6_GENL_NAME);
+ if (genl_family < 0)
+ exit(1);
+ req.n.nlmsg_type = genl_family;
+ }
+
+ switch (opts.cmd) {
+ case SEG6_CMD_SETHMAC:
+ {
+ addattr32(&req.n, sizeof(req), SEG6_ATTR_HMACKEYID, opts.keyid);
+ addattr8(&req.n, sizeof(req), SEG6_ATTR_SECRETLEN,
+ strlen(opts.pass));
+ addattr8(&req.n, sizeof(req), SEG6_ATTR_ALGID, opts.alg_id);
+ if (strlen(opts.pass))
+ addattr_l(&req.n, sizeof(req), SEG6_ATTR_SECRET,
+ opts.pass, strlen(opts.pass));
+ break;
+ }
+ case SEG6_CMD_SET_TUNSRC:
+ addattr_l(&req.n, sizeof(req), SEG6_ATTR_DST, opts.addr.data,
+ sizeof(struct in6_addr));
+ break;
+ case SEG6_CMD_DUMPHMAC:
+ dump = 1;
+ break;
+ case SEG6_CMD_GET_TUNSRC:
+ repl = 1;
+ break;
+ }
+
+ if (!repl && !dump) {
+ if (rtnl_talk(&grth, &req.n, NULL) < 0)
+ return -1;
+ } else if (repl) {
+ if (rtnl_talk(&grth, &req.n, &answer) < 0)
+ return -2;
+ new_json_obj(json);
+ if (process_msg(answer, stdout) < 0) {
+ fprintf(stderr, "Error parsing reply\n");
+ exit(1);
+ }
+ delete_json_obj();
+ free(answer);
+ } else {
+ req.n.nlmsg_flags |= NLM_F_DUMP;
+ req.n.nlmsg_seq = grth.dump = ++grth.seq;
+ if (rtnl_send(&grth, &req, req.n.nlmsg_len) < 0) {
+ perror("Failed to send dump request");
+ exit(1);
+ }
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&grth, process_msg, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ delete_json_obj();
+ fflush(stdout);
+ }
+
+ return 0;
+}
+
+int do_seg6(int argc, char **argv)
+{
+ if (argc < 1 || matches(*argv, "help") == 0)
+ usage();
+
+ memset(&opts, 0, sizeof(opts));
+
+ if (matches(*argv, "hmac") == 0) {
+ NEXT_ARG();
+ if (matches(*argv, "show") == 0) {
+ opts.cmd = SEG6_CMD_DUMPHMAC;
+ } else if (matches(*argv, "set") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opts.keyid, *argv, 0) || opts.keyid == 0)
+ invarg("hmac KEYID value is invalid", *argv);
+ NEXT_ARG();
+ if (strcmp(*argv, "sha1") == 0) {
+ opts.alg_id = SEG6_HMAC_ALGO_SHA1;
+ } else if (strcmp(*argv, "sha256") == 0) {
+ opts.alg_id = SEG6_HMAC_ALGO_SHA256;
+ } else {
+ invarg("hmac ALGO value is invalid", *argv);
+ }
+ opts.cmd = SEG6_CMD_SETHMAC;
+ opts.pass = getpass(HMAC_KEY_PROMPT);
+ } else {
+ invarg("unknown", *argv);
+ }
+ } else if (matches(*argv, "tunsrc") == 0) {
+ NEXT_ARG();
+ if (matches(*argv, "show") == 0) {
+ opts.cmd = SEG6_CMD_GET_TUNSRC;
+ } else if (matches(*argv, "set") == 0) {
+ NEXT_ARG();
+ opts.cmd = SEG6_CMD_SET_TUNSRC;
+ get_addr(&opts.addr, *argv, AF_INET6);
+ } else {
+ invarg("unknown", *argv);
+ }
+ } else {
+ invarg("unknown", *argv);
+ }
+
+ return seg6_do_cmd();
+}
diff --git a/ip/ipstats.c b/ip/ipstats.c
new file mode 100644
index 0000000..3f94ff1
--- /dev/null
+++ b/ip/ipstats.c
@@ -0,0 +1,1361 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <alloca.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+
+#include "list.h"
+#include "utils.h"
+#include "ip_common.h"
+
+struct ipstats_stat_dump_filters {
+ /* mask[0] filters outer attributes. Then individual nests have their
+ * filtering mask at the index of the nested attribute.
+ */
+ __u32 mask[IFLA_STATS_MAX + 1];
+};
+
+static void
+ipstats_stat_desc_enable_bit(struct ipstats_stat_dump_filters *filters,
+ unsigned int group, unsigned int subgroup)
+{
+ filters->mask[0] |= IFLA_STATS_FILTER_BIT(group);
+ if (subgroup)
+ filters->mask[group] |= IFLA_STATS_FILTER_BIT(subgroup);
+}
+
+struct ipstats_stat_show_attrs {
+ struct if_stats_msg *ifsm;
+ int len;
+
+ /* tbs[0] contains top-level attribute table. Then individual nests have
+ * their attribute tables at the index of the nested attribute.
+ */
+ struct rtattr **tbs[IFLA_STATS_MAX + 1];
+};
+
+static const char *const ipstats_levels[] = {
+ "group",
+ "subgroup",
+ "suite",
+};
+
+enum {
+ IPSTATS_LEVELS_COUNT = ARRAY_SIZE(ipstats_levels),
+};
+
+struct ipstats_sel {
+ const char *sel[IPSTATS_LEVELS_COUNT];
+};
+
+struct ipstats_stat_enabled_one {
+ const struct ipstats_stat_desc *desc;
+ struct ipstats_sel sel;
+};
+
+struct ipstats_stat_enabled {
+ struct ipstats_stat_enabled_one *enabled;
+ size_t nenabled;
+};
+
+static const unsigned int ipstats_stat_ifla_max[] = {
+ [0] = IFLA_STATS_MAX,
+ [IFLA_STATS_LINK_XSTATS] = LINK_XSTATS_TYPE_MAX,
+ [IFLA_STATS_LINK_XSTATS_SLAVE] = LINK_XSTATS_TYPE_MAX,
+ [IFLA_STATS_LINK_OFFLOAD_XSTATS] = IFLA_OFFLOAD_XSTATS_MAX,
+ [IFLA_STATS_AF_SPEC] = AF_MAX - 1,
+};
+
+static_assert(ARRAY_SIZE(ipstats_stat_ifla_max) == IFLA_STATS_MAX + 1,
+ "An IFLA_STATS attribute is missing from the ifla_max table");
+
+static int
+ipstats_stat_show_attrs_alloc_tb(struct ipstats_stat_show_attrs *attrs,
+ unsigned int group)
+{
+ unsigned int ifla_max;
+ int err;
+
+ assert(group < ARRAY_SIZE(ipstats_stat_ifla_max));
+ assert(group < ARRAY_SIZE(attrs->tbs));
+ ifla_max = ipstats_stat_ifla_max[group];
+ assert(ifla_max != 0);
+
+ if (attrs->tbs[group])
+ return 0;
+
+ attrs->tbs[group] = calloc(ifla_max + 1, sizeof(*attrs->tbs[group]));
+ if (attrs->tbs[group] == NULL) {
+ fprintf(stderr, "Error parsing netlink answer: %s\n",
+ strerror(errno));
+ return -errno;
+ }
+
+ if (group == 0)
+ err = parse_rtattr(attrs->tbs[group], ifla_max,
+ IFLA_STATS_RTA(attrs->ifsm), attrs->len);
+ else
+ err = parse_rtattr_nested(attrs->tbs[group], ifla_max,
+ attrs->tbs[0][group]);
+
+ if (err != 0) {
+ free(attrs->tbs[group]);
+ attrs->tbs[group] = NULL;
+ }
+ return err;
+}
+
+static const struct rtattr *
+ipstats_stat_show_get_attr(struct ipstats_stat_show_attrs *attrs,
+ int group, int subgroup, int *err)
+{
+ int tmp_err;
+
+ if (err == NULL)
+ err = &tmp_err;
+
+ *err = 0;
+ if (subgroup == 0)
+ return attrs->tbs[0][group];
+
+ if (attrs->tbs[0][group] == NULL)
+ return NULL;
+
+ *err = ipstats_stat_show_attrs_alloc_tb(attrs, group);
+ if (*err != 0)
+ return NULL;
+
+ return attrs->tbs[group][subgroup];
+}
+
+static void
+ipstats_stat_show_attrs_free(struct ipstats_stat_show_attrs *attrs)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(attrs->tbs); i++)
+ free(attrs->tbs[i]);
+}
+
+#define IPSTATS_RTA_PAYLOAD(VAR, AT) \
+ do { \
+ const struct rtattr *__at = (AT); \
+ size_t __at_sz = __at->rta_len - RTA_LENGTH(0); \
+ size_t __var_sz = sizeof(VAR); \
+ typeof(VAR) *__dest = &VAR; \
+ \
+ memset(__dest, 0, __var_sz); \
+ memcpy(__dest, RTA_DATA(__at), MIN(__at_sz, __var_sz)); \
+ } while (0)
+
+static int ipstats_show_64(struct ipstats_stat_show_attrs *attrs,
+ unsigned int group, unsigned int subgroup)
+{
+ struct rtnl_link_stats64 stats;
+ const struct rtattr *at;
+ int err;
+
+ at = ipstats_stat_show_get_attr(attrs, group, subgroup, &err);
+ if (at == NULL)
+ return err;
+
+ IPSTATS_RTA_PAYLOAD(stats, at);
+
+ open_json_object("stats64");
+ print_stats64(stdout, &stats, NULL, NULL);
+ close_json_object();
+ return 0;
+}
+
+static void print_hw_stats64(FILE *fp, struct rtnl_hw_stats64 *s)
+{
+ unsigned int cols[] = {
+ strlen("*X: bytes"),
+ strlen("packets"),
+ strlen("errors"),
+ strlen("dropped"),
+ strlen("overrun"),
+ };
+
+ if (is_json_context()) {
+ /* RX stats */
+ open_json_object("rx");
+ print_u64(PRINT_JSON, "bytes", NULL, s->rx_bytes);
+ print_u64(PRINT_JSON, "packets", NULL, s->rx_packets);
+ print_u64(PRINT_JSON, "errors", NULL, s->rx_errors);
+ print_u64(PRINT_JSON, "dropped", NULL, s->rx_dropped);
+ print_u64(PRINT_JSON, "multicast", NULL, s->multicast);
+ close_json_object();
+
+ /* TX stats */
+ open_json_object("tx");
+ print_u64(PRINT_JSON, "bytes", NULL, s->tx_bytes);
+ print_u64(PRINT_JSON, "packets", NULL, s->tx_packets);
+ print_u64(PRINT_JSON, "errors", NULL, s->tx_errors);
+ print_u64(PRINT_JSON, "dropped", NULL, s->tx_dropped);
+ close_json_object();
+ } else {
+ size_columns(cols, ARRAY_SIZE(cols),
+ s->rx_bytes, s->rx_packets, s->rx_errors,
+ s->rx_dropped, s->multicast);
+ size_columns(cols, ARRAY_SIZE(cols),
+ s->tx_bytes, s->tx_packets, s->tx_errors,
+ s->tx_dropped, 0);
+
+ /* RX stats */
+ fprintf(fp, " RX: %*s %*s %*s %*s %*s%s",
+ cols[0] - 4, "bytes", cols[1], "packets",
+ cols[2], "errors", cols[3], "dropped",
+ cols[4], "mcast", _SL_);
+
+ fprintf(fp, " ");
+ print_num(fp, cols[0], s->rx_bytes);
+ print_num(fp, cols[1], s->rx_packets);
+ print_num(fp, cols[2], s->rx_errors);
+ print_num(fp, cols[3], s->rx_dropped);
+ print_num(fp, cols[4], s->multicast);
+ fprintf(fp, "%s", _SL_);
+
+ /* TX stats */
+ fprintf(fp, " TX: %*s %*s %*s %*s%s",
+ cols[0] - 4, "bytes", cols[1], "packets",
+ cols[2], "errors", cols[3], "dropped", _SL_);
+
+ fprintf(fp, " ");
+ print_num(fp, cols[0], s->tx_bytes);
+ print_num(fp, cols[1], s->tx_packets);
+ print_num(fp, cols[2], s->tx_errors);
+ print_num(fp, cols[3], s->tx_dropped);
+ }
+}
+
+static int ipstats_show_hw64(const struct rtattr *at)
+{
+ struct rtnl_hw_stats64 stats;
+
+ IPSTATS_RTA_PAYLOAD(stats, at);
+ print_hw_stats64(stdout, &stats);
+ return 0;
+}
+
+enum ipstats_maybe_on_off {
+ IPSTATS_MOO_OFF = -1,
+ IPSTATS_MOO_INVALID,
+ IPSTATS_MOO_ON,
+};
+
+static bool ipstats_moo_to_bool(enum ipstats_maybe_on_off moo)
+{
+ assert(moo != IPSTATS_MOO_INVALID);
+ return moo + 1;
+}
+
+static int ipstats_print_moo(enum output_type t, const char *key,
+ const char *fmt, enum ipstats_maybe_on_off moo)
+{
+ if (!moo)
+ return 0;
+ return print_on_off(t, key, fmt, ipstats_moo_to_bool(moo));
+}
+
+struct ipstats_hw_s_info_one {
+ enum ipstats_maybe_on_off request;
+ enum ipstats_maybe_on_off used;
+};
+
+enum ipstats_hw_s_info_idx {
+ IPSTATS_HW_S_INFO_IDX_L3_STATS,
+ IPSTATS_HW_S_INFO_IDX_COUNT
+};
+
+static const char *const ipstats_hw_s_info_name[] = {
+ "l3_stats",
+};
+
+static_assert(ARRAY_SIZE(ipstats_hw_s_info_name) ==
+ IPSTATS_HW_S_INFO_IDX_COUNT,
+ "mismatch: enum ipstats_hw_s_info_idx x ipstats_hw_s_info_name");
+
+struct ipstats_hw_s_info {
+ /* Indexed by enum ipstats_hw_s_info_idx. */
+ struct ipstats_hw_s_info_one *infos[IPSTATS_HW_S_INFO_IDX_COUNT];
+};
+
+static enum ipstats_maybe_on_off ipstats_dissect_01(int value, const char *what)
+{
+ switch (value) {
+ case 0:
+ return IPSTATS_MOO_OFF;
+ case 1:
+ return IPSTATS_MOO_ON;
+ default:
+ fprintf(stderr, "Invalid value for %s: expected 0 or 1, got %d.\n",
+ what, value);
+ return IPSTATS_MOO_INVALID;
+ }
+}
+
+static int ipstats_dissect_hw_s_info_one(const struct rtattr *at,
+ struct ipstats_hw_s_info_one *p_hwsio,
+ const char *what)
+{
+ int attr_id_request = IFLA_OFFLOAD_XSTATS_HW_S_INFO_REQUEST;
+ struct rtattr *tb[IFLA_OFFLOAD_XSTATS_HW_S_INFO_MAX + 1];
+ int attr_id_used = IFLA_OFFLOAD_XSTATS_HW_S_INFO_USED;
+ struct ipstats_hw_s_info_one hwsio = {};
+ int err;
+ int v;
+
+ err = parse_rtattr_nested(tb, IFLA_OFFLOAD_XSTATS_HW_S_INFO_MAX, at);
+ if (err)
+ return err;
+
+ if (tb[attr_id_request]) {
+ v = rta_getattr_u8(tb[attr_id_request]);
+ hwsio.request = ipstats_dissect_01(v, "request");
+
+ /* This has to be present & valid. */
+ if (!hwsio.request)
+ return -EINVAL;
+ }
+
+ if (tb[attr_id_used]) {
+ v = rta_getattr_u8(tb[attr_id_used]);
+ hwsio.used = ipstats_dissect_01(v, "used");
+ }
+
+ *p_hwsio = hwsio;
+ return 0;
+}
+
+static int ipstats_dissect_hw_s_info(const struct rtattr *at,
+ struct ipstats_hw_s_info *hwsi)
+{
+ struct rtattr *tb[IFLA_OFFLOAD_XSTATS_MAX + 1];
+ int attr_id_l3 = IFLA_OFFLOAD_XSTATS_L3_STATS;
+ struct ipstats_hw_s_info_one *hwsio = NULL;
+ int err;
+
+ err = parse_rtattr_nested(tb, IFLA_OFFLOAD_XSTATS_MAX, at);
+ if (err)
+ return err;
+
+ *hwsi = (struct ipstats_hw_s_info){};
+
+ if (tb[attr_id_l3]) {
+ hwsio = malloc(sizeof(*hwsio));
+ if (!hwsio) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ err = ipstats_dissect_hw_s_info_one(tb[attr_id_l3], hwsio, "l3");
+ if (err)
+ goto out;
+
+ hwsi->infos[IPSTATS_HW_S_INFO_IDX_L3_STATS] = hwsio;
+ hwsio = NULL;
+ }
+
+ return 0;
+
+out:
+ free(hwsio);
+ return err;
+}
+
+static void ipstats_fini_hw_s_info(struct ipstats_hw_s_info *hwsi)
+{
+ int i;
+
+ for (i = 0; i < IPSTATS_HW_S_INFO_IDX_COUNT; i++)
+ free(hwsi->infos[i]);
+}
+
+static void
+__ipstats_show_hw_s_info_one(const struct ipstats_hw_s_info_one *hwsio)
+{
+ if (hwsio == NULL)
+ return;
+
+ ipstats_print_moo(PRINT_ANY, "request", " %s", hwsio->request);
+ ipstats_print_moo(PRINT_ANY, "used", " used %s", hwsio->used);
+}
+
+static void
+ipstats_show_hw_s_info_one(const struct ipstats_hw_s_info *hwsi,
+ enum ipstats_hw_s_info_idx idx)
+{
+ const struct ipstats_hw_s_info_one *hwsio = hwsi->infos[idx];
+ const char *name = ipstats_hw_s_info_name[idx];
+
+ if (hwsio == NULL)
+ return;
+
+ print_string(PRINT_FP, NULL, " %s", name);
+ open_json_object(name);
+ __ipstats_show_hw_s_info_one(hwsio);
+ close_json_object();
+}
+
+static int __ipstats_show_hw_s_info(const struct rtattr *at)
+{
+ struct ipstats_hw_s_info hwsi = {};
+ int err;
+
+ err = ipstats_dissect_hw_s_info(at, &hwsi);
+ if (err)
+ return err;
+
+ open_json_object("info");
+ ipstats_show_hw_s_info_one(&hwsi, IPSTATS_HW_S_INFO_IDX_L3_STATS);
+ close_json_object();
+
+ ipstats_fini_hw_s_info(&hwsi);
+ return 0;
+}
+
+static int ipstats_show_hw_s_info(struct ipstats_stat_show_attrs *attrs,
+ unsigned int group, unsigned int subgroup)
+{
+ const struct rtattr *at;
+ int err;
+
+ at = ipstats_stat_show_get_attr(attrs, group, subgroup, &err);
+ if (at == NULL)
+ return err;
+
+ print_nl();
+ return __ipstats_show_hw_s_info(at);
+}
+
+static int __ipstats_show_hw_stats(const struct rtattr *at_hwsi,
+ const struct rtattr *at_stats,
+ enum ipstats_hw_s_info_idx idx)
+{
+ int err = 0;
+
+ if (at_hwsi != NULL) {
+ struct ipstats_hw_s_info hwsi = {};
+
+ err = ipstats_dissect_hw_s_info(at_hwsi, &hwsi);
+ if (err)
+ return err;
+
+ open_json_object("info");
+ __ipstats_show_hw_s_info_one(hwsi.infos[idx]);
+ close_json_object();
+
+ ipstats_fini_hw_s_info(&hwsi);
+ }
+
+ if (at_stats != NULL) {
+ print_nl();
+ open_json_object("stats64");
+ err = ipstats_show_hw64(at_stats);
+ close_json_object();
+ }
+
+ return err;
+}
+
+static int ipstats_show_hw_stats(struct ipstats_stat_show_attrs *attrs,
+ unsigned int group,
+ unsigned int hw_s_info,
+ unsigned int hw_stats,
+ enum ipstats_hw_s_info_idx idx)
+{
+ const struct rtattr *at_stats;
+ const struct rtattr *at_hwsi;
+ int err = 0;
+
+ at_hwsi = ipstats_stat_show_get_attr(attrs, group, hw_s_info, &err);
+ if (at_hwsi == NULL)
+ return err;
+
+ at_stats = ipstats_stat_show_get_attr(attrs, group, hw_stats, &err);
+ if (at_stats == NULL && err != 0)
+ return err;
+
+ return __ipstats_show_hw_stats(at_hwsi, at_stats, idx);
+}
+
+static void
+ipstats_stat_desc_pack_cpu_hit(struct ipstats_stat_dump_filters *filters,
+ const struct ipstats_stat_desc *desc)
+{
+ ipstats_stat_desc_enable_bit(filters,
+ IFLA_STATS_LINK_OFFLOAD_XSTATS,
+ IFLA_OFFLOAD_XSTATS_CPU_HIT);
+}
+
+static int ipstats_stat_desc_show_cpu_hit(struct ipstats_stat_show_attrs *attrs,
+ const struct ipstats_stat_desc *desc)
+{
+ print_nl();
+ return ipstats_show_64(attrs,
+ IFLA_STATS_LINK_OFFLOAD_XSTATS,
+ IFLA_OFFLOAD_XSTATS_CPU_HIT);
+}
+
+static const struct ipstats_stat_desc ipstats_stat_desc_offload_cpu_hit = {
+ .name = "cpu_hit",
+ .kind = IPSTATS_STAT_DESC_KIND_LEAF,
+ .pack = &ipstats_stat_desc_pack_cpu_hit,
+ .show = &ipstats_stat_desc_show_cpu_hit,
+};
+
+static void
+ipstats_stat_desc_pack_hw_stats_info(struct ipstats_stat_dump_filters *filters,
+ const struct ipstats_stat_desc *desc)
+{
+ ipstats_stat_desc_enable_bit(filters,
+ IFLA_STATS_LINK_OFFLOAD_XSTATS,
+ IFLA_OFFLOAD_XSTATS_HW_S_INFO);
+}
+
+static int
+ipstats_stat_desc_show_hw_stats_info(struct ipstats_stat_show_attrs *attrs,
+ const struct ipstats_stat_desc *desc)
+{
+ return ipstats_show_hw_s_info(attrs,
+ IFLA_STATS_LINK_OFFLOAD_XSTATS,
+ IFLA_OFFLOAD_XSTATS_HW_S_INFO);
+}
+
+static const struct ipstats_stat_desc ipstats_stat_desc_offload_hw_s_info = {
+ .name = "hw_stats_info",
+ .kind = IPSTATS_STAT_DESC_KIND_LEAF,
+ .pack = &ipstats_stat_desc_pack_hw_stats_info,
+ .show = &ipstats_stat_desc_show_hw_stats_info,
+};
+
+static void
+ipstats_stat_desc_pack_l3_stats(struct ipstats_stat_dump_filters *filters,
+ const struct ipstats_stat_desc *desc)
+{
+ ipstats_stat_desc_enable_bit(filters,
+ IFLA_STATS_LINK_OFFLOAD_XSTATS,
+ IFLA_OFFLOAD_XSTATS_L3_STATS);
+ ipstats_stat_desc_enable_bit(filters,
+ IFLA_STATS_LINK_OFFLOAD_XSTATS,
+ IFLA_OFFLOAD_XSTATS_HW_S_INFO);
+}
+
+static int
+ipstats_stat_desc_show_l3_stats(struct ipstats_stat_show_attrs *attrs,
+ const struct ipstats_stat_desc *desc)
+{
+ return ipstats_show_hw_stats(attrs,
+ IFLA_STATS_LINK_OFFLOAD_XSTATS,
+ IFLA_OFFLOAD_XSTATS_HW_S_INFO,
+ IFLA_OFFLOAD_XSTATS_L3_STATS,
+ IPSTATS_HW_S_INFO_IDX_L3_STATS);
+}
+
+static const struct ipstats_stat_desc ipstats_stat_desc_offload_l3_stats = {
+ .name = "l3_stats",
+ .kind = IPSTATS_STAT_DESC_KIND_LEAF,
+ .pack = &ipstats_stat_desc_pack_l3_stats,
+ .show = &ipstats_stat_desc_show_l3_stats,
+};
+
+static const struct ipstats_stat_desc *ipstats_stat_desc_offload_subs[] = {
+ &ipstats_stat_desc_offload_cpu_hit,
+ &ipstats_stat_desc_offload_hw_s_info,
+ &ipstats_stat_desc_offload_l3_stats,
+};
+
+static const struct ipstats_stat_desc ipstats_stat_desc_offload_group = {
+ .name = "offload",
+ .kind = IPSTATS_STAT_DESC_KIND_GROUP,
+ .subs = ipstats_stat_desc_offload_subs,
+ .nsubs = ARRAY_SIZE(ipstats_stat_desc_offload_subs),
+};
+
+void ipstats_stat_desc_pack_xstats(struct ipstats_stat_dump_filters *filters,
+ const struct ipstats_stat_desc *desc)
+{
+ struct ipstats_stat_desc_xstats *xdesc;
+
+ xdesc = container_of(desc, struct ipstats_stat_desc_xstats, desc);
+ ipstats_stat_desc_enable_bit(filters, xdesc->xstats_at, 0);
+}
+
+int ipstats_stat_desc_show_xstats(struct ipstats_stat_show_attrs *attrs,
+ const struct ipstats_stat_desc *desc)
+{
+ struct ipstats_stat_desc_xstats *xdesc;
+ const struct rtattr *at;
+ struct rtattr **tb;
+ int err;
+
+ xdesc = container_of(desc, struct ipstats_stat_desc_xstats, desc);
+ at = ipstats_stat_show_get_attr(attrs,
+ xdesc->xstats_at,
+ xdesc->link_type_at, &err);
+ if (at == NULL)
+ return err;
+
+ tb = alloca(sizeof(*tb) * (xdesc->inner_max + 1));
+ err = parse_rtattr_nested(tb, xdesc->inner_max, at);
+ if (err != 0)
+ return err;
+
+ if (tb[xdesc->inner_at] != NULL) {
+ print_nl();
+ xdesc->show_cb(tb[xdesc->inner_at]);
+ }
+ return 0;
+}
+
+static const struct ipstats_stat_desc *ipstats_stat_desc_xstats_subs[] = {
+ &ipstats_stat_desc_xstats_bridge_group,
+ &ipstats_stat_desc_xstats_bond_group,
+};
+
+static const struct ipstats_stat_desc ipstats_stat_desc_xstats_group = {
+ .name = "xstats",
+ .kind = IPSTATS_STAT_DESC_KIND_GROUP,
+ .subs = ipstats_stat_desc_xstats_subs,
+ .nsubs = ARRAY_SIZE(ipstats_stat_desc_xstats_subs),
+};
+
+static const struct ipstats_stat_desc *ipstats_stat_desc_xstats_slave_subs[] = {
+ &ipstats_stat_desc_xstats_slave_bridge_group,
+ &ipstats_stat_desc_xstats_slave_bond_group,
+};
+
+static const struct ipstats_stat_desc ipstats_stat_desc_xstats_slave_group = {
+ .name = "xstats_slave",
+ .kind = IPSTATS_STAT_DESC_KIND_GROUP,
+ .subs = ipstats_stat_desc_xstats_slave_subs,
+ .nsubs = ARRAY_SIZE(ipstats_stat_desc_xstats_slave_subs),
+};
+
+static void
+ipstats_stat_desc_pack_link(struct ipstats_stat_dump_filters *filters,
+ const struct ipstats_stat_desc *desc)
+{
+ ipstats_stat_desc_enable_bit(filters,
+ IFLA_STATS_LINK_64, 0);
+}
+
+static int
+ipstats_stat_desc_show_link(struct ipstats_stat_show_attrs *attrs,
+ const struct ipstats_stat_desc *desc)
+{
+ print_nl();
+ return ipstats_show_64(attrs, IFLA_STATS_LINK_64, 0);
+}
+
+static const struct ipstats_stat_desc ipstats_stat_desc_toplev_link = {
+ .name = "link",
+ .kind = IPSTATS_STAT_DESC_KIND_LEAF,
+ .pack = &ipstats_stat_desc_pack_link,
+ .show = &ipstats_stat_desc_show_link,
+};
+
+static const struct ipstats_stat_desc ipstats_stat_desc_afstats_group;
+
+static void
+ipstats_stat_desc_pack_afstats(struct ipstats_stat_dump_filters *filters,
+ const struct ipstats_stat_desc *desc)
+{
+ ipstats_stat_desc_enable_bit(filters, IFLA_STATS_AF_SPEC, 0);
+}
+
+static int
+ipstats_stat_desc_show_afstats_mpls(struct ipstats_stat_show_attrs *attrs,
+ const struct ipstats_stat_desc *desc)
+{
+ struct rtattr *mrtb[MPLS_STATS_MAX+1];
+ struct mpls_link_stats stats;
+ const struct rtattr *at;
+ int err;
+
+ at = ipstats_stat_show_get_attr(attrs, IFLA_STATS_AF_SPEC,
+ AF_MPLS, &err);
+ if (at == NULL)
+ return err;
+
+ parse_rtattr_nested(mrtb, MPLS_STATS_MAX, at);
+ if (mrtb[MPLS_STATS_LINK] == NULL)
+ return -ENOENT;
+
+ IPSTATS_RTA_PAYLOAD(stats, mrtb[MPLS_STATS_LINK]);
+
+ print_nl();
+ open_json_object("mpls_stats");
+ print_mpls_link_stats(stdout, &stats, " ");
+ close_json_object();
+ return 0;
+}
+
+static const struct ipstats_stat_desc ipstats_stat_desc_afstats_mpls = {
+ .name = "mpls",
+ .kind = IPSTATS_STAT_DESC_KIND_LEAF,
+ .pack = &ipstats_stat_desc_pack_afstats,
+ .show = &ipstats_stat_desc_show_afstats_mpls,
+};
+
+static const struct ipstats_stat_desc *ipstats_stat_desc_afstats_subs[] = {
+ &ipstats_stat_desc_afstats_mpls,
+};
+
+static const struct ipstats_stat_desc ipstats_stat_desc_afstats_group = {
+ .name = "afstats",
+ .kind = IPSTATS_STAT_DESC_KIND_GROUP,
+ .subs = ipstats_stat_desc_afstats_subs,
+ .nsubs = ARRAY_SIZE(ipstats_stat_desc_afstats_subs),
+};
+static const struct ipstats_stat_desc *ipstats_stat_desc_toplev_subs[] = {
+ &ipstats_stat_desc_toplev_link,
+ &ipstats_stat_desc_xstats_group,
+ &ipstats_stat_desc_xstats_slave_group,
+ &ipstats_stat_desc_offload_group,
+ &ipstats_stat_desc_afstats_group,
+};
+
+static const struct ipstats_stat_desc ipstats_stat_desc_toplev_group = {
+ .name = "top-level",
+ .kind = IPSTATS_STAT_DESC_KIND_GROUP,
+ .subs = ipstats_stat_desc_toplev_subs,
+ .nsubs = ARRAY_SIZE(ipstats_stat_desc_toplev_subs),
+};
+
+static void ipstats_show_group(const struct ipstats_sel *sel)
+{
+ int i;
+
+ for (i = 0; i < IPSTATS_LEVELS_COUNT; i++) {
+ if (sel->sel[i] == NULL)
+ break;
+ print_string(PRINT_JSON, ipstats_levels[i], NULL, sel->sel[i]);
+ print_string(PRINT_FP, NULL, " %s ", ipstats_levels[i]);
+ print_string(PRINT_FP, NULL, "%s", sel->sel[i]);
+ }
+}
+
+static int
+ipstats_process_ifsm(struct nlmsghdr *answer,
+ struct ipstats_stat_enabled *enabled)
+{
+ struct ipstats_stat_show_attrs show_attrs = {};
+ const char *dev;
+ int err = 0;
+ int i;
+
+ show_attrs.ifsm = NLMSG_DATA(answer);
+ show_attrs.len = (answer->nlmsg_len -
+ NLMSG_LENGTH(sizeof(*show_attrs.ifsm)));
+ if (show_attrs.len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", show_attrs.len);
+ return -EINVAL;
+ }
+
+ err = ipstats_stat_show_attrs_alloc_tb(&show_attrs, 0);
+ if (err)
+ return err;
+
+ dev = ll_index_to_name(show_attrs.ifsm->ifindex);
+
+ for (i = 0; i < enabled->nenabled; i++) {
+ const struct ipstats_stat_desc *desc = enabled->enabled[i].desc;
+
+ open_json_object(NULL);
+ print_int(PRINT_ANY, "ifindex", "%d:",
+ show_attrs.ifsm->ifindex);
+ print_color_string(PRINT_ANY, COLOR_IFNAME,
+ "ifname", " %s:", dev);
+ ipstats_show_group(&enabled->enabled[i].sel);
+ err = desc->show(&show_attrs, desc);
+ if (err != 0)
+ goto out;
+ close_json_object();
+ print_nl();
+ }
+
+out:
+ ipstats_stat_show_attrs_free(&show_attrs);
+ return err;
+}
+
+static bool
+ipstats_req_should_filter_at(struct ipstats_stat_dump_filters *filters, int at)
+{
+ return filters->mask[at] != 0 &&
+ filters->mask[at] != (1 << ipstats_stat_ifla_max[at]) - 1;
+}
+
+static int
+ipstats_req_add_filters(struct ipstats_req *req, void *data)
+{
+ struct ipstats_stat_dump_filters dump_filters = {};
+ struct ipstats_stat_enabled *enabled = data;
+ bool get_filters = false;
+ int i;
+
+ for (i = 0; i < enabled->nenabled; i++)
+ enabled->enabled[i].desc->pack(&dump_filters,
+ enabled->enabled[i].desc);
+
+ for (i = 1; i < ARRAY_SIZE(dump_filters.mask); i++) {
+ if (ipstats_req_should_filter_at(&dump_filters, i)) {
+ get_filters = true;
+ break;
+ }
+ }
+
+ req->ifsm.filter_mask = dump_filters.mask[0];
+ if (get_filters) {
+ struct rtattr *nest;
+
+ nest = addattr_nest(&req->nlh, sizeof(*req),
+ IFLA_STATS_GET_FILTERS | NLA_F_NESTED);
+
+ for (i = 1; i < ARRAY_SIZE(dump_filters.mask); i++) {
+ if (ipstats_req_should_filter_at(&dump_filters, i))
+ addattr32(&req->nlh, sizeof(*req), i,
+ dump_filters.mask[i]);
+ }
+
+ addattr_nest_end(&req->nlh, nest);
+ }
+
+ return 0;
+}
+
+static int
+ipstats_show_one(int ifindex, struct ipstats_stat_enabled *enabled)
+{
+ struct ipstats_req req = {
+ .nlh.nlmsg_flags = NLM_F_REQUEST,
+ .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct if_stats_msg)),
+ .nlh.nlmsg_type = RTM_GETSTATS,
+ .ifsm.family = PF_UNSPEC,
+ .ifsm.ifindex = ifindex,
+ };
+ struct nlmsghdr *answer;
+ int err = 0;
+
+ ipstats_req_add_filters(&req, enabled);
+ if (rtnl_talk(&rth, &req.nlh, &answer) < 0)
+ return -2;
+ err = ipstats_process_ifsm(answer, enabled);
+ free(answer);
+
+ return err;
+}
+
+static int ipstats_dump_one(struct nlmsghdr *n, void *arg)
+{
+ struct ipstats_stat_enabled *enabled = arg;
+ int rc;
+
+ rc = ipstats_process_ifsm(n, enabled);
+ if (rc)
+ return rc;
+
+ print_nl();
+ return 0;
+}
+
+static int ipstats_dump(struct ipstats_stat_enabled *enabled)
+{
+ int rc = 0;
+
+ if (rtnl_statsdump_req_filter(&rth, PF_UNSPEC, 0,
+ ipstats_req_add_filters,
+ enabled) < 0) {
+ perror("Cannot send dump request");
+ return -2;
+ }
+
+ if (rtnl_dump_filter(&rth, ipstats_dump_one, enabled) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ rc = -2;
+ }
+
+ fflush(stdout);
+ return rc;
+}
+
+static int
+ipstats_show_do(int ifindex, struct ipstats_stat_enabled *enabled)
+{
+ int rc;
+
+ new_json_obj(json);
+ if (ifindex)
+ rc = ipstats_show_one(ifindex, enabled);
+ else
+ rc = ipstats_dump(enabled);
+ delete_json_obj();
+
+ return rc;
+}
+
+static int ipstats_add_enabled(struct ipstats_stat_enabled_one ens[],
+ size_t nens,
+ struct ipstats_stat_enabled *enabled)
+{
+ struct ipstats_stat_enabled_one *new_en;
+
+ new_en = realloc(enabled->enabled,
+ sizeof(*new_en) * (enabled->nenabled + nens));
+ if (new_en == NULL)
+ return -ENOMEM;
+
+ enabled->enabled = new_en;
+ while (nens-- > 0)
+ enabled->enabled[enabled->nenabled++] = *ens++;
+ return 0;
+}
+
+static void ipstats_select_push(struct ipstats_sel *sel, const char *name)
+{
+ int i;
+
+ for (i = 0; i < IPSTATS_LEVELS_COUNT; i++)
+ if (sel->sel[i] == NULL) {
+ sel->sel[i] = name;
+ return;
+ }
+
+ assert(false);
+}
+
+static int
+ipstats_enable_recursively(const struct ipstats_stat_desc *desc,
+ struct ipstats_stat_enabled *enabled,
+ const struct ipstats_sel *sel)
+{
+ bool found = false;
+ size_t i;
+ int err;
+
+ if (desc->kind == IPSTATS_STAT_DESC_KIND_LEAF) {
+ struct ipstats_stat_enabled_one en[] = {{
+ .desc = desc,
+ .sel = *sel,
+ }};
+
+ return ipstats_add_enabled(en, ARRAY_SIZE(en), enabled);
+ }
+
+ for (i = 0; i < desc->nsubs; i++) {
+ struct ipstats_sel subsel = *sel;
+
+ ipstats_select_push(&subsel, desc->subs[i]->name);
+ err = ipstats_enable_recursively(desc->subs[i], enabled,
+ &subsel);
+ if (err == -ENOENT)
+ continue;
+ if (err != 0)
+ return err;
+ found = true;
+ }
+
+ return found ? 0 : -ENOENT;
+}
+
+static int ipstats_comp_enabled(const void *a, const void *b)
+{
+ const struct ipstats_stat_enabled_one *en_a = a;
+ const struct ipstats_stat_enabled_one *en_b = b;
+
+ if (en_a->desc < en_b->desc)
+ return -1;
+ if (en_a->desc > en_b->desc)
+ return 1;
+
+ return 0;
+}
+
+static void ipstats_enabled_free(struct ipstats_stat_enabled *enabled)
+{
+ free(enabled->enabled);
+}
+
+static const struct ipstats_stat_desc *
+ipstats_stat_desc_find(const struct ipstats_stat_desc *desc,
+ const char *name)
+{
+ size_t i;
+
+ assert(desc->kind == IPSTATS_STAT_DESC_KIND_GROUP);
+ for (i = 0; i < desc->nsubs; i++) {
+ const struct ipstats_stat_desc *sub = desc->subs[i];
+
+ if (strcmp(sub->name, name) == 0)
+ return sub;
+ }
+
+ return NULL;
+}
+
+static const struct ipstats_stat_desc *
+ipstats_enable_find_stat_desc(struct ipstats_sel *sel)
+{
+ const struct ipstats_stat_desc *toplev = &ipstats_stat_desc_toplev_group;
+ const struct ipstats_stat_desc *desc = toplev;
+ int i;
+
+ for (i = 0; i < IPSTATS_LEVELS_COUNT; i++) {
+ const struct ipstats_stat_desc *next_desc;
+
+ if (sel->sel[i] == NULL)
+ break;
+ if (desc->kind == IPSTATS_STAT_DESC_KIND_LEAF) {
+ fprintf(stderr, "Error: %s %s requested inside leaf %s %s\n",
+ ipstats_levels[i], sel->sel[i],
+ ipstats_levels[i - 1], desc->name);
+ return NULL;
+ }
+
+ next_desc = ipstats_stat_desc_find(desc, sel->sel[i]);
+ if (next_desc == NULL) {
+ fprintf(stderr, "Error: no %s named %s found inside %s\n",
+ ipstats_levels[i], sel->sel[i], desc->name);
+ return NULL;
+ }
+
+ desc = next_desc;
+ }
+
+ return desc;
+}
+
+static int ipstats_enable(struct ipstats_sel *sel,
+ struct ipstats_stat_enabled *enabled)
+{
+ struct ipstats_stat_enabled new_enabled = {};
+ const struct ipstats_stat_desc *desc;
+ size_t i, j;
+ int err = 0;
+
+ desc = ipstats_enable_find_stat_desc(sel);
+ if (desc == NULL)
+ return -EINVAL;
+
+ err = ipstats_enable_recursively(desc, &new_enabled, sel);
+ if (err != 0)
+ return err;
+
+ err = ipstats_add_enabled(new_enabled.enabled, new_enabled.nenabled,
+ enabled);
+ if (err != 0)
+ goto out;
+
+ qsort(enabled->enabled, enabled->nenabled, sizeof(*enabled->enabled),
+ ipstats_comp_enabled);
+
+ for (i = 1, j = 1; i < enabled->nenabled; i++) {
+ if (enabled->enabled[i].desc != enabled->enabled[j - 1].desc)
+ enabled->enabled[j++] = enabled->enabled[i];
+ }
+ enabled->nenabled = j;
+
+out:
+ ipstats_enabled_free(&new_enabled);
+ return err;
+}
+
+static int ipstats_enable_check(struct ipstats_sel *sel,
+ struct ipstats_stat_enabled *enabled)
+{
+ int err;
+ int i;
+
+ err = ipstats_enable(sel, enabled);
+ if (err == -ENOENT) {
+ fprintf(stderr, "The request for");
+ for (i = 0; i < IPSTATS_LEVELS_COUNT; i++)
+ if (sel->sel[i] != NULL)
+ fprintf(stderr, " %s %s",
+ ipstats_levels[i], sel->sel[i]);
+ else
+ break;
+ fprintf(stderr, " did not match any known stats.\n");
+ }
+
+ return err;
+}
+
+static int do_help(void)
+{
+ const struct ipstats_stat_desc *toplev = &ipstats_stat_desc_toplev_group;
+ int i;
+
+ fprintf(stderr,
+ "Usage: ip stats help\n"
+ " ip stats show [ dev DEV ] [ group GROUP [ subgroup SUBGROUP [ suite SUITE ] ... ] ... ] ...\n"
+ " ip stats set dev DEV l3_stats { on | off }\n"
+ );
+
+ for (i = 0; i < toplev->nsubs; i++) {
+ const struct ipstats_stat_desc *desc = toplev->subs[i];
+
+ if (i == 0)
+ fprintf(stderr, "GROUP := { %s", desc->name);
+ else
+ fprintf(stderr, " | %s", desc->name);
+ }
+ if (i > 0)
+ fprintf(stderr, " }\n");
+
+ for (i = 0; i < toplev->nsubs; i++) {
+ const struct ipstats_stat_desc *desc = toplev->subs[i];
+ bool opened = false;
+ size_t j;
+
+ if (desc->kind != IPSTATS_STAT_DESC_KIND_GROUP)
+ continue;
+
+ for (j = 0; j < desc->nsubs; j++) {
+ size_t k;
+
+ if (j == 0)
+ fprintf(stderr, "%s SUBGROUP := {", desc->name);
+ else
+ fprintf(stderr, " |");
+ fprintf(stderr, " %s", desc->subs[j]->name);
+ opened = true;
+
+ if (desc->subs[j]->kind != IPSTATS_STAT_DESC_KIND_GROUP)
+ continue;
+
+ for (k = 0; k < desc->subs[j]->nsubs; k++)
+ fprintf(stderr, " [ suite %s ]",
+ desc->subs[j]->subs[k]->name);
+ }
+ if (opened)
+ fprintf(stderr, " }\n");
+ }
+
+ return 0;
+}
+
+static int ipstats_select(struct ipstats_sel *old_sel,
+ const char *new_sel, int level,
+ struct ipstats_stat_enabled *enabled)
+{
+ int err;
+ int i;
+
+ for (i = 0; i < level; i++) {
+ if (old_sel->sel[i] == NULL) {
+ fprintf(stderr, "Error: %s %s requested without selecting a %s first\n",
+ ipstats_levels[level], new_sel,
+ ipstats_levels[i]);
+ return -EINVAL;
+ }
+ }
+
+ for (i = level; i < IPSTATS_LEVELS_COUNT; i++) {
+ if (old_sel->sel[i] != NULL) {
+ err = ipstats_enable_check(old_sel, enabled);
+ if (err)
+ return err;
+ break;
+ }
+ }
+
+ old_sel->sel[level] = new_sel;
+ for (i = level + 1; i < IPSTATS_LEVELS_COUNT; i++)
+ old_sel->sel[i] = NULL;
+
+ return 0;
+}
+
+static int ipstats_show(int argc, char **argv)
+{
+ struct ipstats_stat_enabled enabled = {};
+ struct ipstats_sel sel = {};
+ const char *dev = NULL;
+ int ifindex;
+ int err;
+ int i;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (dev != NULL)
+ duparg2("dev", *argv);
+ if (check_ifname(*argv))
+ invarg("\"dev\" not a valid ifname", *argv);
+ dev = *argv;
+ } else if (strcmp(*argv, "help") == 0) {
+ do_help();
+ return 0;
+ } else {
+ bool found_level = false;
+
+ for (i = 0; i < ARRAY_SIZE(ipstats_levels); i++) {
+ if (strcmp(*argv, ipstats_levels[i]) == 0) {
+ NEXT_ARG();
+ err = ipstats_select(&sel, *argv, i,
+ &enabled);
+ if (err)
+ goto err;
+
+ found_level = true;
+ }
+ }
+
+ if (!found_level) {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ do_help();
+ err = -EINVAL;
+ goto err;
+ }
+ }
+
+ NEXT_ARG_FWD();
+ }
+
+ /* Push whatever was given. */
+ err = ipstats_enable_check(&sel, &enabled);
+ if (err)
+ goto err;
+
+ if (dev) {
+ ifindex = ll_name_to_index(dev);
+ if (!ifindex) {
+ err = nodev(dev);
+ goto err;
+ }
+ } else {
+ ifindex = 0;
+ }
+
+
+ err = ipstats_show_do(ifindex, &enabled);
+
+err:
+ ipstats_enabled_free(&enabled);
+ return err;
+}
+
+static int ipstats_set_do(int ifindex, int at, bool enable)
+{
+ struct ipstats_req req = {
+ .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct if_stats_msg)),
+ .nlh.nlmsg_flags = NLM_F_REQUEST,
+ .nlh.nlmsg_type = RTM_SETSTATS,
+ .ifsm.family = PF_UNSPEC,
+ .ifsm.ifindex = ifindex,
+ };
+
+ addattr8(&req.nlh, sizeof(req), at, enable);
+
+ if (rtnl_talk(&rth, &req.nlh, NULL) < 0)
+ return -2;
+ return 0;
+}
+
+static int ipstats_set(int argc, char **argv)
+{
+ const char *dev = NULL;
+ bool enable = false;
+ int ifindex;
+ int at = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (dev)
+ duparg2("dev", *argv);
+ if (check_ifname(*argv))
+ invarg("\"dev\" not a valid ifname", *argv);
+ dev = *argv;
+ } else if (strcmp(*argv, "l3_stats") == 0) {
+ int err;
+
+ NEXT_ARG();
+ if (at) {
+ fprintf(stderr, "A statistics suite to toggle was already given.\n");
+ return -EINVAL;
+ }
+ at = IFLA_STATS_SET_OFFLOAD_XSTATS_L3_STATS;
+ enable = parse_on_off("l3_stats", *argv, &err);
+ if (err)
+ return err;
+ } else if (strcmp(*argv, "help") == 0) {
+ do_help();
+ return 0;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ do_help();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ }
+
+ if (!dev) {
+ fprintf(stderr, "Not enough information: \"dev\" argument is required.\n");
+ exit(-1);
+ }
+
+ if (!at) {
+ fprintf(stderr, "Not enough information: stat type to toggle is required.\n");
+ exit(-1);
+ }
+
+ ifindex = ll_name_to_index(dev);
+ if (!ifindex)
+ return nodev(dev);
+
+ return ipstats_set_do(ifindex, at, enable);
+}
+
+int do_ipstats(int argc, char **argv)
+{
+ int rc;
+
+ if (argc == 0) {
+ rc = ipstats_show(0, NULL);
+ } else if (strcmp(*argv, "help") == 0) {
+ do_help();
+ rc = 0;
+ } else if (strcmp(*argv, "show") == 0) {
+ /* Invoking "stats show" implies one -s. Passing -d adds one
+ * more -s.
+ */
+ show_stats += show_details + 1;
+ rc = ipstats_show(argc-1, argv+1);
+ } else if (strcmp(*argv, "set") == 0) {
+ rc = ipstats_set(argc-1, argv+1);
+ } else {
+ fprintf(stderr, "Command \"%s\" is unknown, try \"ip stats help\".\n",
+ *argv);
+ rc = -1;
+ }
+
+ return rc;
+}
+
+int ipstats_print(struct nlmsghdr *n, void *arg)
+{
+ struct ipstats_stat_enabled_one one = {
+ .desc = &ipstats_stat_desc_offload_hw_s_info,
+ };
+ struct ipstats_stat_enabled enabled = {
+ .enabled = &one,
+ .nenabled = 1,
+ };
+ FILE *fp = arg;
+ int rc;
+
+ rc = ipstats_process_ifsm(n, &enabled);
+ if (rc)
+ return rc;
+
+ fflush(fp);
+ return 0;
+}
diff --git a/ip/iptoken.c b/ip/iptoken.c
new file mode 100644
index 0000000..f25a7c8
--- /dev/null
+++ b/ip/iptoken.c
@@ -0,0 +1,204 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iptoken.c "ip token"
+ *
+ * Authors: Daniel Borkmann, <borkmann@redhat.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <linux/types.h>
+#include <linux/if.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+extern struct rtnl_handle rth;
+
+struct rtnl_dump_args {
+ FILE *fp;
+ int ifindex;
+};
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr, "Usage: ip token [ list | set | del | get ] [ TOKEN ] [ dev DEV ]\n");
+ exit(-1);
+}
+
+static int print_token(struct nlmsghdr *n, void *arg)
+{
+ struct rtnl_dump_args *args = arg;
+ FILE *fp = args->fp;
+ int ifindex = args->ifindex;
+ struct ifinfomsg *ifi = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *tb[IFLA_MAX + 1];
+ struct rtattr *ltb[IFLA_INET6_MAX + 1];
+
+ if (n->nlmsg_type != RTM_NEWLINK)
+ return -1;
+
+ len -= NLMSG_LENGTH(sizeof(*ifi));
+ if (len < 0)
+ return -1;
+
+ if (ifi->ifi_family != AF_INET6)
+ return 0;
+ if (ifi->ifi_index == 0)
+ return 0;
+ if (ifindex > 0 && ifi->ifi_index != ifindex)
+ return 0;
+ if (ifi->ifi_flags & (IFF_LOOPBACK | IFF_NOARP))
+ return 0;
+
+ parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+ if (!tb[IFLA_PROTINFO])
+ return -1;
+
+ parse_rtattr_nested(ltb, IFLA_INET6_MAX, tb[IFLA_PROTINFO]);
+ if (!ltb[IFLA_INET6_TOKEN]) {
+ fprintf(stderr, "Seems there's no support for IPv6 token!\n");
+ return -1;
+ }
+
+ open_json_object(NULL);
+ print_string(PRINT_FP, NULL, "token ", NULL);
+ print_color_string(PRINT_ANY,
+ ifa_family_color(ifi->ifi_family),
+ "token", "%s",
+ format_host_rta(ifi->ifi_family, ltb[IFLA_INET6_TOKEN]));
+ print_string(PRINT_FP, NULL, " dev ", NULL);
+ print_color_string(PRINT_ANY, COLOR_IFNAME,
+ "ifname", "%s\n",
+ ll_index_to_name(ifi->ifi_index));
+ close_json_object();
+ fflush(fp);
+
+ return 0;
+}
+
+static int iptoken_list(int argc, char **argv)
+{
+ int af = AF_INET6;
+ struct rtnl_dump_args da = { .fp = stdout };
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if ((da.ifindex = ll_name_to_index(*argv)) == 0)
+ invarg("dev is invalid\n", *argv);
+ break;
+ }
+ argc--; argv++;
+ }
+
+ if (rtnl_linkdump_req(&rth, af) < 0) {
+ perror("Cannot send dump request");
+ return -1;
+ }
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&rth, print_token, &da) < 0) {
+ delete_json_obj();
+ fprintf(stderr, "Dump terminated\n");
+ return -1;
+ }
+ delete_json_obj();
+
+ return 0;
+}
+
+static int iptoken_set(int argc, char **argv, bool delete)
+{
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg ifi;
+ char buf[512];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_SETLINK,
+ .ifi.ifi_family = AF_INET6,
+ };
+ struct rtattr *afs, *afs6;
+ bool have_token = delete, have_dev = false;
+ inet_prefix addr = { .bytelen = 16, };
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (!have_dev) {
+ if ((req.ifi.ifi_index =
+ ll_name_to_index(*argv)) == 0)
+ invarg("dev is invalid\n", *argv);
+ have_dev = true;
+ }
+ } else {
+ if (matches(*argv, "help") == 0)
+ usage();
+ if (!have_token) {
+ get_prefix(&addr, *argv, req.ifi.ifi_family);
+ have_token = true;
+ }
+ }
+ argc--; argv++;
+ }
+
+ if (!have_token) {
+ fprintf(stderr, "Not enough information: token is required.\n");
+ return -1;
+ }
+ if (!have_dev) {
+ fprintf(stderr, "Not enough information: \"dev\" argument is required.\n");
+ return -1;
+ }
+
+ afs = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC);
+ afs6 = addattr_nest(&req.n, sizeof(req), AF_INET6);
+ addattr_l(&req.n, sizeof(req), IFLA_INET6_TOKEN,
+ &addr.data, addr.bytelen);
+ addattr_nest_end(&req.n, afs6);
+ addattr_nest_end(&req.n, afs);
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ return -2;
+
+ return 0;
+}
+
+int do_iptoken(int argc, char **argv)
+{
+ ll_init_map(&rth);
+
+ if (argc < 1) {
+ return iptoken_list(0, NULL);
+ } else if (matches(argv[0], "list") == 0 ||
+ matches(argv[0], "lst") == 0 ||
+ matches(argv[0], "show") == 0) {
+ return iptoken_list(argc - 1, argv + 1);
+ } else if (matches(argv[0], "set") == 0 ||
+ matches(argv[0], "add") == 0) {
+ return iptoken_set(argc - 1, argv + 1, false);
+ } else if (matches(argv[0], "delete") == 0) {
+ return iptoken_set(argc - 1, argv + 1, true);
+ } else if (matches(argv[0], "get") == 0) {
+ return iptoken_list(argc - 1, argv + 1);
+ } else if (matches(argv[0], "help") == 0)
+ usage();
+
+ fprintf(stderr, "Command \"%s\" is unknown, try \"ip token help\".\n", *argv);
+ exit(-1);
+}
diff --git a/ip/iptunnel.c b/ip/iptunnel.c
new file mode 100644
index 0000000..b6da145
--- /dev/null
+++ b/ip/iptunnel.c
@@ -0,0 +1,602 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iptunnel.c "ip tunnel"
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include <linux/ip6_tunnel.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip tunnel { add | change | del | show | prl | 6rd } [ NAME ]\n"
+ " [ mode { gre | ipip | isatap | sit | vti } ]\n"
+ " [ remote ADDR ] [ local ADDR ]\n"
+ " [ [i|o]seq ] [ [i|o]key KEY ] [ [i|o]csum ]\n"
+ " [ prl-default ADDR ] [ prl-nodefault ADDR ] [ prl-delete ADDR ]\n"
+ " [ 6rd-prefix ADDR ] [ 6rd-relay_prefix ADDR ] [ 6rd-reset ]\n"
+ " [ ttl TTL ] [ tos TOS ] [ [no]pmtudisc ] [ dev PHYS_DEV ]\n"
+ "\n"
+ "Where: NAME := STRING\n"
+ " ADDR := { IP_ADDRESS | any }\n"
+ " TOS := { STRING | 00..ff | inherit | inherit/STRING | inherit/00..ff }\n"
+ " TTL := { 1..255 | inherit }\n"
+ " KEY := { DOTTED_QUAD | NUMBER }\n");
+ exit(-1);
+}
+
+static void set_tunnel_proto(struct ip_tunnel_parm *p, int proto)
+{
+ if (p->iph.protocol && p->iph.protocol != proto) {
+ fprintf(stderr,
+ "You managed to ask for more than one tunnel mode.\n");
+ exit(-1);
+ }
+ p->iph.protocol = proto;
+}
+
+static int parse_args(int argc, char **argv, int cmd, struct ip_tunnel_parm *p)
+{
+ int count = 0;
+ const char *medium = NULL;
+ int isatap = 0;
+
+ memset(p, 0, sizeof(*p));
+ p->iph.version = 4;
+ p->iph.ihl = 5;
+#ifndef IP_DF
+#define IP_DF 0x4000 /* Flag: "Don't Fragment" */
+#endif
+ p->iph.frag_off = htons(IP_DF);
+
+ while (argc > 0) {
+ if (strcmp(*argv, "mode") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "ipip") == 0 ||
+ strcmp(*argv, "ip/ip") == 0) {
+ set_tunnel_proto(p, IPPROTO_IPIP);
+ } else if (strcmp(*argv, "gre") == 0 ||
+ strcmp(*argv, "gre/ip") == 0) {
+ set_tunnel_proto(p, IPPROTO_GRE);
+ } else if (strcmp(*argv, "sit") == 0 ||
+ strcmp(*argv, "ipv6/ip") == 0) {
+ set_tunnel_proto(p, IPPROTO_IPV6);
+ } else if (strcmp(*argv, "isatap") == 0) {
+ set_tunnel_proto(p, IPPROTO_IPV6);
+ isatap++;
+ } else if (strcmp(*argv, "vti") == 0) {
+ set_tunnel_proto(p, IPPROTO_IPIP);
+ p->i_flags |= VTI_ISVTI;
+ } else {
+ fprintf(stderr,
+ "Unknown tunnel mode \"%s\"\n", *argv);
+ exit(-1);
+ }
+ } else if (strcmp(*argv, "key") == 0) {
+ NEXT_ARG();
+ p->i_flags |= GRE_KEY;
+ p->o_flags |= GRE_KEY;
+ p->i_key = p->o_key = tnl_parse_key("key", *argv);
+ } else if (strcmp(*argv, "ikey") == 0) {
+ NEXT_ARG();
+ p->i_flags |= GRE_KEY;
+ p->i_key = tnl_parse_key("ikey", *argv);
+ } else if (strcmp(*argv, "okey") == 0) {
+ NEXT_ARG();
+ p->o_flags |= GRE_KEY;
+ p->o_key = tnl_parse_key("okey", *argv);
+ } else if (strcmp(*argv, "seq") == 0) {
+ p->i_flags |= GRE_SEQ;
+ p->o_flags |= GRE_SEQ;
+ } else if (strcmp(*argv, "iseq") == 0) {
+ p->i_flags |= GRE_SEQ;
+ } else if (strcmp(*argv, "oseq") == 0) {
+ p->o_flags |= GRE_SEQ;
+ } else if (strcmp(*argv, "csum") == 0) {
+ p->i_flags |= GRE_CSUM;
+ p->o_flags |= GRE_CSUM;
+ } else if (strcmp(*argv, "icsum") == 0) {
+ p->i_flags |= GRE_CSUM;
+ } else if (strcmp(*argv, "ocsum") == 0) {
+ p->o_flags |= GRE_CSUM;
+ } else if (strcmp(*argv, "nopmtudisc") == 0) {
+ p->iph.frag_off = 0;
+ } else if (strcmp(*argv, "pmtudisc") == 0) {
+ p->iph.frag_off = htons(IP_DF);
+ } else if (strcmp(*argv, "remote") == 0) {
+ NEXT_ARG();
+ p->iph.daddr = get_addr32(*argv);
+ } else if (strcmp(*argv, "local") == 0) {
+ NEXT_ARG();
+ p->iph.saddr = get_addr32(*argv);
+ } else if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ medium = *argv;
+ } else if (strcmp(*argv, "ttl") == 0 ||
+ strcmp(*argv, "hoplimit") == 0 ||
+ strcmp(*argv, "hlim") == 0) {
+ __u8 uval;
+
+ NEXT_ARG();
+ if (strcmp(*argv, "inherit") != 0) {
+ if (get_u8(&uval, *argv, 0))
+ invarg("invalid TTL\n", *argv);
+ p->iph.ttl = uval;
+ }
+ } else if (strcmp(*argv, "tos") == 0 ||
+ strcmp(*argv, "tclass") == 0 ||
+ matches(*argv, "dsfield") == 0) {
+ char *dsfield;
+ __u32 uval;
+
+ NEXT_ARG();
+ dsfield = *argv;
+ strsep(&dsfield, "/");
+ if (strcmp(*argv, "inherit") != 0) {
+ dsfield = *argv;
+ p->iph.tos = 0;
+ } else
+ p->iph.tos = 1;
+ if (dsfield) {
+ if (rtnl_dsfield_a2n(&uval, dsfield))
+ invarg("bad TOS value", *argv);
+ p->iph.tos |= uval;
+ }
+ } else {
+ if (strcmp(*argv, "name") == 0)
+ NEXT_ARG();
+ else if (matches(*argv, "help") == 0)
+ usage();
+
+ if (p->name[0])
+ duparg2("name", *argv);
+ if (get_ifname(p->name, *argv))
+ invarg("\"name\" not a valid ifname", *argv);
+ if (cmd == SIOCCHGTUNNEL && count == 0) {
+ union {
+ struct ip_tunnel_parm ip_tnl;
+ struct ip6_tnl_parm2 ip6_tnl;
+ } old_p = {};
+
+ if (tnl_get_ioctl(*argv, &old_p))
+ return -1;
+
+ if (old_p.ip_tnl.iph.version != 4 ||
+ old_p.ip_tnl.iph.ihl != 5)
+ invarg("\"name\" is not an ip tunnel",
+ *argv);
+
+ *p = old_p.ip_tnl;
+ }
+ }
+ count++;
+ argc--; argv++;
+ }
+
+
+ if (p->iph.protocol == 0) {
+ if (memcmp(p->name, "gre", 3) == 0)
+ p->iph.protocol = IPPROTO_GRE;
+ else if (memcmp(p->name, "ipip", 4) == 0)
+ p->iph.protocol = IPPROTO_IPIP;
+ else if (memcmp(p->name, "sit", 3) == 0)
+ p->iph.protocol = IPPROTO_IPV6;
+ else if (memcmp(p->name, "isatap", 6) == 0) {
+ p->iph.protocol = IPPROTO_IPV6;
+ isatap++;
+ } else if (memcmp(p->name, "vti", 3) == 0) {
+ p->iph.protocol = IPPROTO_IPIP;
+ p->i_flags |= VTI_ISVTI;
+ }
+ }
+
+ if ((p->i_flags & GRE_KEY) || (p->o_flags & GRE_KEY)) {
+ if (!(p->i_flags & VTI_ISVTI) &&
+ (p->iph.protocol != IPPROTO_GRE)) {
+ fprintf(stderr, "Keys are not allowed with ipip and sit tunnels\n");
+ return -1;
+ }
+ }
+
+ if (medium) {
+ p->link = ll_name_to_index(medium);
+ if (!p->link)
+ return nodev(medium);
+ }
+
+ if (p->i_key == 0 && IN_MULTICAST(ntohl(p->iph.daddr))) {
+ p->i_key = p->iph.daddr;
+ p->i_flags |= GRE_KEY;
+ }
+ if (p->o_key == 0 && IN_MULTICAST(ntohl(p->iph.daddr))) {
+ p->o_key = p->iph.daddr;
+ p->o_flags |= GRE_KEY;
+ }
+ if (IN_MULTICAST(ntohl(p->iph.daddr)) && !p->iph.saddr) {
+ fprintf(stderr, "A broadcast tunnel requires a source address\n");
+ return -1;
+ }
+ if (isatap)
+ p->i_flags |= SIT_ISATAP;
+
+ return 0;
+}
+
+static const char *tnl_defname(const struct ip_tunnel_parm *p)
+{
+ switch (p->iph.protocol) {
+ case IPPROTO_IPIP:
+ if (p->i_flags & VTI_ISVTI)
+ return "ip_vti0";
+ else
+ return "tunl0";
+ case IPPROTO_GRE:
+ return "gre0";
+ case IPPROTO_IPV6:
+ return "sit0";
+ }
+ return NULL;
+}
+
+static int do_add(int cmd, int argc, char **argv)
+{
+ struct ip_tunnel_parm p;
+ const char *basedev;
+
+ if (parse_args(argc, argv, cmd, &p) < 0)
+ return -1;
+
+ if (p.iph.ttl && p.iph.frag_off == 0) {
+ fprintf(stderr, "ttl != 0 and nopmtudisc are incompatible\n");
+ return -1;
+ }
+
+ basedev = tnl_defname(&p);
+ if (!basedev) {
+ fprintf(stderr,
+ "cannot determine tunnel mode (ipip, gre, vti or sit)\n");
+ return -1;
+ }
+
+ return tnl_add_ioctl(cmd, basedev, p.name, &p);
+}
+
+static int do_del(int argc, char **argv)
+{
+ struct ip_tunnel_parm p;
+
+ if (parse_args(argc, argv, SIOCDELTUNNEL, &p) < 0)
+ return -1;
+
+ return tnl_del_ioctl(tnl_defname(&p) ? : p.name, p.name, &p);
+}
+
+static void print_tunnel(const void *t)
+{
+ const struct ip_tunnel_parm *p = t;
+ struct ip_tunnel_6rd ip6rd = {};
+ SPRINT_BUF(b1);
+
+ /* Do not use format_host() for local addr,
+ * symbolic name will not be useful.
+ */
+ open_json_object(NULL);
+ print_color_string(PRINT_ANY, COLOR_IFNAME, "ifname", "%s: ", p->name);
+ snprintf(b1, sizeof(b1), "%s/ip", tnl_strproto(p->iph.protocol));
+ print_string(PRINT_ANY, "mode", "%s ", b1);
+ print_null(PRINT_FP, NULL, "remote ", NULL);
+ print_color_string(PRINT_ANY, COLOR_INET, "remote", "%s ",
+ p->iph.daddr || is_json_context()
+ ? format_host_r(AF_INET, 4, &p->iph.daddr, b1, sizeof(b1))
+ : "any");
+ print_null(PRINT_FP, NULL, "local ", NULL);
+ print_color_string(PRINT_ANY, COLOR_INET, "local", "%s",
+ p->iph.saddr || is_json_context()
+ ? rt_addr_n2a_r(AF_INET, 4, &p->iph.saddr, b1, sizeof(b1))
+ : "any");
+
+ if (p->iph.protocol == IPPROTO_IPV6 && (p->i_flags & SIT_ISATAP)) {
+ struct ip_tunnel_prl prl[16] = {};
+ int i;
+
+ prl[0].datalen = sizeof(prl) - sizeof(prl[0]);
+ prl[0].addr = htonl(INADDR_ANY);
+
+ if (!tnl_prl_ioctl(SIOCGETPRL, p->name, prl)) {
+ for (i = 1; i < ARRAY_SIZE(prl); i++) {
+ if (prl[i].addr == htonl(INADDR_ANY))
+ continue;
+ if (prl[i].flags & PRL_DEFAULT)
+ print_string(PRINT_ANY, "pdr",
+ " pdr %s",
+ format_host(AF_INET, 4, &prl[i].addr));
+ else
+ print_string(PRINT_ANY, "pr", " pr %s",
+ format_host(AF_INET, 4, &prl[i].addr));
+ }
+ }
+ }
+
+ if (p->link) {
+ const char *n = ll_index_to_name(p->link);
+
+ if (n)
+ print_string(PRINT_ANY, "dev", " dev %s", n);
+ }
+
+ if (p->iph.ttl)
+ print_uint(PRINT_ANY, "ttl", " ttl %u", p->iph.ttl);
+ else
+ print_string(PRINT_FP, "ttl", " ttl %s", "inherit");
+
+ if (p->iph.tos) {
+ SPRINT_BUF(b2);
+
+ if (p->iph.tos != 1) {
+ if (!is_json_context() && p->iph.tos & 1)
+ snprintf(b2, sizeof(b2), "%s%s",
+ p->iph.tos & 1 ? "inherit/" : "",
+ rtnl_dsfield_n2a(p->iph.tos & ~1, b1, sizeof(b1)));
+ else
+ snprintf(b2, sizeof(b2), "%s",
+ rtnl_dsfield_n2a(p->iph.tos, b1, sizeof(b1)));
+ print_string(PRINT_ANY, "tos", " tos %s", b2);
+ } else {
+ print_string(PRINT_FP, NULL, " tos %s", "inherit");
+ }
+ }
+
+ if (!(p->iph.frag_off & htons(IP_DF)))
+ print_null(PRINT_ANY, "nopmtudisc", " nopmtudisc", NULL);
+
+ if (p->iph.protocol == IPPROTO_IPV6 && !tnl_ioctl_get_6rd(p->name, &ip6rd) && ip6rd.prefixlen) {
+ print_string(PRINT_ANY, "6rd-prefix", " 6rd-prefix %s",
+ inet_ntop(AF_INET6, &ip6rd.prefix, b1, sizeof(b1)));
+ print_uint(PRINT_ANY, "6rd-prefixlen", "/%u", ip6rd.prefixlen);
+ if (ip6rd.relay_prefix) {
+ print_string(PRINT_ANY, "6rd-relay_prefix",
+ " 6rd-relay_prefix %s",
+ format_host(AF_INET, 4, &ip6rd.relay_prefix));
+ print_uint(PRINT_ANY, "6rd-relay_prefixlen", "/%u",
+ ip6rd.relay_prefixlen);
+ }
+ }
+
+ tnl_print_gre_flags(p->iph.protocol, p->i_flags, p->o_flags,
+ p->i_key, p->o_key);
+
+ close_json_object();
+}
+
+
+static void ip_tunnel_parm_initialize(const struct tnl_print_nlmsg_info *info)
+{
+ struct ip_tunnel_parm *p2 = info->p2;
+
+ memset(p2, 0, sizeof(*p2));
+}
+
+static bool ip_tunnel_parm_match(const struct tnl_print_nlmsg_info *info)
+{
+ const struct ip_tunnel_parm *p1 = info->p1;
+ const struct ip_tunnel_parm *p2 = info->p2;
+
+ return ((!p1->link || p1->link == p2->link) &&
+ (!p1->name[0] || strcmp(p1->name, p2->name) == 0) &&
+ (!p1->iph.daddr || p1->iph.daddr == p2->iph.daddr) &&
+ (!p1->iph.saddr || p1->iph.saddr == p2->iph.saddr) &&
+ (!p1->i_key || p1->i_key == p2->i_key));
+}
+
+static int do_show(int argc, char **argv)
+{
+ struct ip_tunnel_parm p, p1;
+ const char *basedev;
+
+ if (parse_args(argc, argv, SIOCGETTUNNEL, &p) < 0)
+ return -1;
+
+ basedev = tnl_defname(&p);
+ if (!basedev) {
+ struct tnl_print_nlmsg_info info = {
+ .p1 = &p,
+ .p2 = &p1,
+ .init = ip_tunnel_parm_initialize,
+ .match = ip_tunnel_parm_match,
+ .print = print_tunnel,
+ };
+
+ return do_tunnels_list(&info);
+ }
+
+ if (tnl_get_ioctl(p.name[0] ? p.name : basedev, &p))
+ return -1;
+
+ print_tunnel(&p);
+ fputc('\n', stdout);
+ return 0;
+}
+
+static int do_prl(int argc, char **argv)
+{
+ struct ip_tunnel_prl p = {};
+ int count = 0;
+ int cmd = 0;
+ const char *medium = NULL;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "prl-default") == 0) {
+ NEXT_ARG();
+ cmd = SIOCADDPRL;
+ p.addr = get_addr32(*argv);
+ p.flags |= PRL_DEFAULT;
+ count++;
+ } else if (strcmp(*argv, "prl-nodefault") == 0) {
+ NEXT_ARG();
+ cmd = SIOCADDPRL;
+ p.addr = get_addr32(*argv);
+ count++;
+ } else if (strcmp(*argv, "prl-delete") == 0) {
+ NEXT_ARG();
+ cmd = SIOCDELPRL;
+ p.addr = get_addr32(*argv);
+ count++;
+ } else if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (check_ifname(*argv))
+ invarg("\"dev\" not a valid ifname", *argv);
+ medium = *argv;
+ } else {
+ fprintf(stderr,
+ "Invalid PRL parameter \"%s\"\n", *argv);
+ exit(-1);
+ }
+ if (count > 1) {
+ fprintf(stderr,
+ "One PRL entry at a time\n");
+ exit(-1);
+ }
+ argc--; argv++;
+ }
+ if (!medium) {
+ fprintf(stderr, "Must specify device\n");
+ exit(-1);
+ }
+
+ return tnl_prl_ioctl(cmd, medium, &p);
+}
+
+static int do_6rd(int argc, char **argv)
+{
+ struct ip_tunnel_6rd ip6rd = {};
+ int cmd = 0;
+ const char *medium = NULL;
+ inet_prefix prefix;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "6rd-prefix") == 0) {
+ NEXT_ARG();
+ if (get_prefix(&prefix, *argv, AF_INET6))
+ invarg("invalid 6rd_prefix\n", *argv);
+ cmd = SIOCADD6RD;
+ memcpy(&ip6rd.prefix, prefix.data, 16);
+ ip6rd.prefixlen = prefix.bitlen;
+ } else if (strcmp(*argv, "6rd-relay_prefix") == 0) {
+ NEXT_ARG();
+ if (get_prefix(&prefix, *argv, AF_INET))
+ invarg("invalid 6rd-relay_prefix\n", *argv);
+ cmd = SIOCADD6RD;
+ memcpy(&ip6rd.relay_prefix, prefix.data, 4);
+ ip6rd.relay_prefixlen = prefix.bitlen;
+ } else if (strcmp(*argv, "6rd-reset") == 0) {
+ cmd = SIOCDEL6RD;
+ } else if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (check_ifname(*argv))
+ invarg("\"dev\" not a valid ifname", *argv);
+ medium = *argv;
+ } else {
+ fprintf(stderr,
+ "Invalid 6RD parameter \"%s\"\n", *argv);
+ exit(-1);
+ }
+ argc--; argv++;
+ }
+ if (!medium) {
+ fprintf(stderr, "Must specify device\n");
+ exit(-1);
+ }
+
+ return tnl_6rd_ioctl(cmd, medium, &ip6rd);
+}
+
+static int tunnel_mode_is_ipv6(char *tunnel_mode)
+{
+ static const char * const ipv6_modes[] = {
+ "ipv6/ipv6", "ip6ip6",
+ "vti6",
+ "ip/ipv6", "ipv4/ipv6", "ipip6", "ip4ip6",
+ "ip6gre", "gre/ipv6",
+ "any/ipv6", "any"
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ipv6_modes); i++) {
+ if (strcmp(ipv6_modes[i], tunnel_mode) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+int do_iptunnel(int argc, char **argv)
+{
+ int i;
+
+ for (i = 0; i < argc - 1; i++) {
+ if (strcmp(argv[i], "mode") == 0) {
+ if (tunnel_mode_is_ipv6(argv[i + 1]))
+ preferred_family = AF_INET6;
+ break;
+ }
+ }
+ switch (preferred_family) {
+ case AF_UNSPEC:
+ preferred_family = AF_INET;
+ break;
+ case AF_INET:
+ break;
+ /*
+ * This is silly enough but we have no easy way to make it
+ * protocol-independent because of unarranged structure between
+ * IPv4 and IPv6.
+ */
+ case AF_INET6:
+ return do_ip6tunnel(argc, argv);
+ default:
+ fprintf(stderr, "Unsupported protocol family: %d\n", preferred_family);
+ exit(-1);
+ }
+
+ if (argc > 0) {
+ if (matches(*argv, "add") == 0)
+ return do_add(SIOCADDTUNNEL, argc - 1, argv + 1);
+ if (matches(*argv, "change") == 0)
+ return do_add(SIOCCHGTUNNEL, argc - 1, argv + 1);
+ if (matches(*argv, "delete") == 0)
+ return do_del(argc - 1, argv + 1);
+ if (matches(*argv, "show") == 0 ||
+ matches(*argv, "lst") == 0 ||
+ matches(*argv, "list") == 0)
+ return do_show(argc - 1, argv + 1);
+ if (matches(*argv, "prl") == 0)
+ return do_prl(argc - 1, argv + 1);
+ if (matches(*argv, "6rd") == 0)
+ return do_6rd(argc - 1, argv + 1);
+ if (matches(*argv, "help") == 0)
+ usage();
+ } else
+ return do_show(0, NULL);
+
+ fprintf(stderr, "Command \"%s\" is unknown, try \"ip tunnel help\"\n", *argv);
+ exit(-1);
+}
diff --git a/ip/iptuntap.c b/ip/iptuntap.c
new file mode 100644
index 0000000..b7018a6
--- /dev/null
+++ b/ip/iptuntap.c
@@ -0,0 +1,562 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * iptunnel.c "ip tuntap"
+ *
+ * Authors: David Woodhouse <David.Woodhouse@intel.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <linux/if.h>
+#include <linux/if_tun.h>
+#include <linux/if_arp.h>
+#include <pwd.h>
+#include <grp.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include <glob.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static const char drv_name[] = "tun";
+
+#define TUNDEV "/dev/net/tun"
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip tuntap { add | del | show | list | lst | help } [ dev PHYS_DEV ]\n"
+ " [ mode { tun | tap } ] [ user USER ] [ group GROUP ]\n"
+ " [ one_queue ] [ pi ] [ vnet_hdr ] [ multi_queue ] [ name NAME ]\n"
+ "\n"
+ "Where: USER := { STRING | NUMBER }\n"
+ " GROUP := { STRING | NUMBER }\n");
+ exit(-1);
+}
+
+static int tap_add_ioctl(struct ifreq *ifr, uid_t uid, gid_t gid)
+{
+ int fd;
+ int ret = -1;
+
+#ifdef IFF_TUN_EXCL
+ ifr->ifr_flags |= IFF_TUN_EXCL;
+#endif
+
+ fd = open(TUNDEV, O_RDWR);
+ if (fd < 0) {
+ perror("open");
+ return -1;
+ }
+ if (ioctl(fd, TUNSETIFF, ifr)) {
+ perror("ioctl(TUNSETIFF)");
+ goto out;
+ }
+ if (uid != -1 && ioctl(fd, TUNSETOWNER, uid)) {
+ perror("ioctl(TUNSETOWNER)");
+ goto out;
+ }
+ if (gid != -1 && ioctl(fd, TUNSETGROUP, gid)) {
+ perror("ioctl(TUNSETGROUP)");
+ goto out;
+ }
+ if (ioctl(fd, TUNSETPERSIST, 1)) {
+ perror("ioctl(TUNSETPERSIST)");
+ goto out;
+ }
+ ret = 0;
+ out:
+ close(fd);
+ return ret;
+}
+
+static int tap_del_ioctl(struct ifreq *ifr)
+{
+ int fd = open(TUNDEV, O_RDWR);
+ int ret = -1;
+
+ if (fd < 0) {
+ perror("open");
+ return -1;
+ }
+ if (ioctl(fd, TUNSETIFF, ifr)) {
+ perror("ioctl(TUNSETIFF)");
+ goto out;
+ }
+ if (ioctl(fd, TUNSETPERSIST, 0)) {
+ perror("ioctl(TUNSETPERSIST)");
+ goto out;
+ }
+ ret = 0;
+ out:
+ close(fd);
+ return ret;
+
+}
+static int parse_args(int argc, char **argv,
+ struct ifreq *ifr, uid_t *uid, gid_t *gid)
+{
+ memset(ifr, 0, sizeof(*ifr));
+
+ ifr->ifr_flags |= IFF_NO_PI;
+
+ while (argc > 0) {
+ if (matches(*argv, "mode") == 0) {
+ NEXT_ARG();
+ if (matches(*argv, "tun") == 0) {
+ if (ifr->ifr_flags & IFF_TAP) {
+ fprintf(stderr, "You managed to ask for more than one tunnel mode.\n");
+ exit(-1);
+ }
+ ifr->ifr_flags |= IFF_TUN;
+ } else if (matches(*argv, "tap") == 0) {
+ if (ifr->ifr_flags & IFF_TUN) {
+ fprintf(stderr, "You managed to ask for more than one tunnel mode.\n");
+ exit(-1);
+ }
+ ifr->ifr_flags |= IFF_TAP;
+ } else {
+ fprintf(stderr, "Unknown tunnel mode \"%s\"\n", *argv);
+ exit(-1);
+ }
+ } else if (uid && matches(*argv, "user") == 0) {
+ char *end;
+ unsigned long user;
+
+ NEXT_ARG();
+ if (**argv && ((user = strtol(*argv, &end, 10)), !*end))
+ *uid = user;
+ else {
+ struct passwd *pw = getpwnam(*argv);
+
+ if (!pw) {
+ fprintf(stderr, "invalid user \"%s\"\n", *argv);
+ exit(-1);
+ }
+ *uid = pw->pw_uid;
+ }
+ } else if (gid && matches(*argv, "group") == 0) {
+ char *end;
+ unsigned long group;
+
+ NEXT_ARG();
+
+ if (**argv && ((group = strtol(*argv, &end, 10)), !*end))
+ *gid = group;
+ else {
+ struct group *gr = getgrnam(*argv);
+
+ if (!gr) {
+ fprintf(stderr, "invalid group \"%s\"\n", *argv);
+ exit(-1);
+ }
+ *gid = gr->gr_gid;
+ }
+ } else if (matches(*argv, "pi") == 0) {
+ ifr->ifr_flags &= ~IFF_NO_PI;
+ } else if (matches(*argv, "one_queue") == 0) {
+ ifr->ifr_flags |= IFF_ONE_QUEUE;
+ } else if (matches(*argv, "vnet_hdr") == 0) {
+ ifr->ifr_flags |= IFF_VNET_HDR;
+ } else if (matches(*argv, "multi_queue") == 0) {
+ ifr->ifr_flags |= IFF_MULTI_QUEUE;
+ } else if (matches(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (get_ifname(ifr->ifr_name, *argv))
+ invarg("\"dev\" not a valid ifname", *argv);
+ } else {
+ if (matches(*argv, "name") == 0) {
+ NEXT_ARG();
+ } else if (matches(*argv, "help") == 0)
+ usage();
+ if (ifr->ifr_name[0])
+ duparg2("name", *argv);
+ if (get_ifname(ifr->ifr_name, *argv))
+ invarg("\"name\" not a valid ifname", *argv);
+ }
+ argc--; argv++;
+ }
+
+ if (!(ifr->ifr_flags & TUN_TYPE_MASK)) {
+ fprintf(stderr, "You failed to specify a tunnel mode\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int do_add(int argc, char **argv)
+{
+ struct ifreq ifr;
+ uid_t uid = -1;
+ gid_t gid = -1;
+
+ if (parse_args(argc, argv, &ifr, &uid, &gid) < 0)
+ return -1;
+
+ return tap_add_ioctl(&ifr, uid, gid);
+}
+
+static int do_del(int argc, char **argv)
+{
+ struct ifreq ifr;
+
+ if (parse_args(argc, argv, &ifr, NULL, NULL) < 0)
+ return -1;
+
+ return tap_del_ioctl(&ifr);
+}
+
+static void print_flags(long flags)
+{
+ open_json_array(PRINT_JSON, "flags");
+
+ if (flags & IFF_TUN)
+ print_string(PRINT_ANY, NULL, " %s", "tun");
+
+ if (flags & IFF_TAP)
+ print_string(PRINT_ANY, NULL, " %s", "tap");
+
+ if (!(flags & IFF_NO_PI))
+ print_string(PRINT_ANY, NULL, " %s", "pi");
+
+ if (flags & IFF_ONE_QUEUE)
+ print_string(PRINT_ANY, NULL, " %s", "one_queue");
+
+ if (flags & IFF_MULTI_QUEUE)
+ print_string(PRINT_ANY, NULL, " %s", "multi_queue");
+
+ if (flags & IFF_VNET_HDR)
+ print_string(PRINT_ANY, NULL, " %s", "vnet_hdr");
+
+ if (flags & IFF_PERSIST)
+ print_string(PRINT_ANY, NULL, " %s", "persist");
+
+ if (!(flags & IFF_NOFILTER))
+ print_string(PRINT_ANY, NULL, " %s", "filter");
+
+ flags &= ~(IFF_TUN | IFF_TAP | IFF_NO_PI | IFF_ONE_QUEUE |
+ IFF_MULTI_QUEUE | IFF_VNET_HDR | IFF_PERSIST |
+ IFF_NOFILTER);
+ if (flags)
+ print_0xhex(PRINT_ANY, NULL, " %#llx", flags);
+
+ close_json_array(PRINT_JSON, NULL);
+}
+
+static void show_processes(const char *name)
+{
+ glob_t globbuf = { };
+ char **fd_path;
+ int err;
+
+ err = glob("/proc/[0-9]*/fd/[0-9]*", GLOB_NOSORT,
+ NULL, &globbuf);
+ if (err)
+ return;
+
+ open_json_array(PRINT_JSON, "processes");
+
+ fd_path = globbuf.gl_pathv;
+ while (*fd_path) {
+ const size_t linkbuf_len = strlen(TUNDEV) + 2;
+ char linkbuf[linkbuf_len], *fdinfo;
+ int pid, fd;
+ FILE *f;
+
+ if (sscanf(*fd_path, "/proc/%d/fd/%d", &pid, &fd) != 2)
+ goto next;
+
+ if (pid == getpid())
+ goto next;
+
+ err = readlink(*fd_path, linkbuf, linkbuf_len - 1);
+ if (err < 0) {
+ perror("readlink");
+ goto next;
+ }
+ linkbuf[err] = '\0';
+ if (strcmp(TUNDEV, linkbuf))
+ goto next;
+
+ if (asprintf(&fdinfo, "/proc/%d/fdinfo/%d", pid, fd) < 0)
+ goto next;
+
+ f = fopen(fdinfo, "r");
+ free(fdinfo);
+ if (!f) {
+ perror("fopen");
+ goto next;
+ }
+
+ while (!feof(f)) {
+ char *key = NULL, *value = NULL;
+
+ err = fscanf(f, "%m[^:]: %ms\n", &key, &value);
+ if (err == EOF) {
+ if (ferror(f))
+ perror("fscanf");
+ break;
+ } else if (err == 2 &&
+ !strcmp("iff", key) &&
+ !strcmp(name, value)) {
+ SPRINT_BUF(pname);
+
+ if (get_task_name(pid, pname, sizeof(pname)))
+ print_string(PRINT_ANY, "name",
+ "%s", "<NULL>");
+ else
+ print_string(PRINT_ANY, "name",
+ "%s", pname);
+
+ print_uint(PRINT_ANY, "pid", "(%d)", pid);
+ }
+
+ free(key);
+ free(value);
+ }
+ if (fclose(f))
+ perror("fclose");
+
+next:
+ ++fd_path;
+ }
+ close_json_array(PRINT_JSON, NULL);
+
+ globfree(&globbuf);
+}
+
+static int tuntap_filter_req(struct nlmsghdr *nlh, int reqlen)
+{
+ struct rtattr *linkinfo;
+ int err;
+
+ linkinfo = addattr_nest(nlh, reqlen, IFLA_LINKINFO);
+
+ err = addattr_l(nlh, reqlen, IFLA_INFO_KIND,
+ drv_name, sizeof(drv_name) - 1);
+ if (err)
+ return err;
+
+ addattr_nest_end(nlh, linkinfo);
+
+ return 0;
+}
+
+static int print_tuntap(struct nlmsghdr *n, void *arg)
+{
+ struct ifinfomsg *ifi = NLMSG_DATA(n);
+ struct rtattr *tb[IFLA_MAX+1];
+ struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+ const char *name, *kind;
+ long flags, owner = -1, group = -1;
+
+ if (n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK)
+ return 0;
+
+ if (n->nlmsg_len < NLMSG_LENGTH(sizeof(*ifi)))
+ return -1;
+
+ switch (ifi->ifi_type) {
+ case ARPHRD_NONE:
+ case ARPHRD_ETHER:
+ break;
+ default:
+ return 0;
+ }
+
+ parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), IFLA_PAYLOAD(n));
+
+ if (!tb[IFLA_IFNAME])
+ return 0;
+
+ if (!tb[IFLA_LINKINFO])
+ return 0;
+
+ parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+ if (!linkinfo[IFLA_INFO_KIND])
+ return 0;
+
+ kind = rta_getattr_str(linkinfo[IFLA_INFO_KIND]);
+ if (strcmp(kind, drv_name))
+ return 0;
+
+ name = rta_getattr_str(tb[IFLA_IFNAME]);
+
+ if (read_prop(name, "tun_flags", &flags))
+ return 0;
+ if (read_prop(name, "owner", &owner))
+ return 0;
+ if (read_prop(name, "group", &group))
+ return 0;
+
+ open_json_object(NULL);
+ print_color_string(PRINT_ANY, COLOR_IFNAME,
+ "ifname", "%s:", name);
+ print_flags(flags);
+ if (owner != -1)
+ print_u64(PRINT_ANY, "user",
+ " user %ld", owner);
+ if (group != -1)
+ print_u64(PRINT_ANY, "group",
+ " group %ld", group);
+
+ if (show_details) {
+ print_string(PRINT_FP, NULL,
+ "%s\tAttached to processes:", _SL_);
+ show_processes(name);
+ }
+ close_json_object();
+ print_string(PRINT_FP, NULL, "%s", "\n");
+
+ return 0;
+}
+
+static int do_show(int argc, char **argv)
+{
+ if (rtnl_linkdump_req_filter_fn(&rth, AF_UNSPEC,
+ tuntap_filter_req) < 0) {
+ perror("Cannot send dump request\n");
+ return -1;
+ }
+
+ new_json_obj(json);
+
+ if (rtnl_dump_filter(&rth, print_tuntap, NULL) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ delete_json_obj();
+ return -1;
+ }
+
+ delete_json_obj();
+ fflush(stdout);
+
+ return 0;
+}
+
+int do_iptuntap(int argc, char **argv)
+{
+ if (argc > 0) {
+ if (matches(*argv, "add") == 0)
+ return do_add(argc-1, argv+1);
+ if (matches(*argv, "delete") == 0)
+ return do_del(argc-1, argv+1);
+ if (matches(*argv, "show") == 0 ||
+ matches(*argv, "lst") == 0 ||
+ matches(*argv, "list") == 0)
+ return do_show(argc-1, argv+1);
+ if (matches(*argv, "help") == 0)
+ usage();
+ } else
+ return do_show(0, NULL);
+
+ fprintf(stderr, "Command \"%s\" is unknown, try \"ip tuntap help\".\n",
+ *argv);
+ exit(-1);
+}
+
+static void print_owner(FILE *f, uid_t uid)
+{
+ struct passwd *pw = getpwuid(uid);
+
+ if (pw)
+ print_string(PRINT_ANY, "user", "user %s ", pw->pw_name);
+ else
+ print_uint(PRINT_ANY, "user", "user %u ", uid);
+}
+
+static void print_group(FILE *f, gid_t gid)
+{
+ struct group *group = getgrgid(gid);
+
+ if (group)
+ print_string(PRINT_ANY, "group", "group %s ", group->gr_name);
+ else
+ print_uint(PRINT_ANY, "group", "group %u ", gid);
+}
+
+static void print_mq(FILE *f, struct rtattr *tb[])
+{
+ if (!tb[IFLA_TUN_MULTI_QUEUE] ||
+ !rta_getattr_u8(tb[IFLA_TUN_MULTI_QUEUE])) {
+ if (is_json_context())
+ print_bool(PRINT_JSON, "multi_queue", NULL, false);
+ return;
+ }
+
+ print_bool(PRINT_ANY, "multi_queue", "multi_queue ", true);
+
+ if (tb[IFLA_TUN_NUM_QUEUES]) {
+ print_uint(PRINT_ANY, "numqueues", "numqueues %u ",
+ rta_getattr_u32(tb[IFLA_TUN_NUM_QUEUES]));
+ }
+
+ if (tb[IFLA_TUN_NUM_DISABLED_QUEUES]) {
+ print_uint(PRINT_ANY, "numdisabled", "numdisabled %u ",
+ rta_getattr_u32(tb[IFLA_TUN_NUM_DISABLED_QUEUES]));
+ }
+}
+
+static void print_type(FILE *f, __u8 type)
+{
+ SPRINT_BUF(buf);
+ const char *str = buf;
+
+ if (type == IFF_TUN)
+ str = "tun";
+ else if (type == IFF_TAP)
+ str = "tap";
+ else
+ snprintf(buf, sizeof(buf), "UNKNOWN:%hhu", type);
+
+ print_string(PRINT_ANY, "type", "type %s ", str);
+}
+
+static void tun_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ if (!tb)
+ return;
+
+ if (tb[IFLA_TUN_TYPE])
+ print_type(f, rta_getattr_u8(tb[IFLA_TUN_TYPE]));
+
+ if (tb[IFLA_TUN_PI])
+ print_on_off(PRINT_ANY, "pi", "pi %s ",
+ rta_getattr_u8(tb[IFLA_TUN_PI]));
+
+ if (tb[IFLA_TUN_VNET_HDR]) {
+ print_on_off(PRINT_ANY, "vnet_hdr", "vnet_hdr %s ",
+ rta_getattr_u8(tb[IFLA_TUN_VNET_HDR]));
+ }
+
+ print_mq(f, tb);
+
+ if (tb[IFLA_TUN_PERSIST])
+ print_on_off(PRINT_ANY, "persist", "persist %s ",
+ rta_getattr_u8(tb[IFLA_TUN_PERSIST]));
+
+ if (tb[IFLA_TUN_OWNER])
+ print_owner(f, rta_getattr_u32(tb[IFLA_TUN_OWNER]));
+
+ if (tb[IFLA_TUN_GROUP])
+ print_group(f, rta_getattr_u32(tb[IFLA_TUN_GROUP]));
+}
+
+struct link_util tun_link_util = {
+ .id = "tun",
+ .maxattr = IFLA_TUN_MAX,
+ .print_opt = tun_print_opt,
+};
diff --git a/ip/ipvrf.c b/ip/ipvrf.c
new file mode 100644
index 0000000..e7c702a
--- /dev/null
+++ b/ip/ipvrf.c
@@ -0,0 +1,654 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * ipvrf.c "ip vrf"
+ *
+ * Authors: David Ahern <dsa@cumulusnetworks.com>
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/mount.h>
+#include <linux/bpf.h>
+#include <linux/if.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <dirent.h>
+#include <errno.h>
+#include <limits.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "bpf_util.h"
+#include "selinux.h"
+
+#define CGRP_PROC_FILE "/cgroup.procs"
+
+static struct link_filter vrf_filter;
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip vrf show [NAME] ...\n"
+ " ip vrf exec [NAME] cmd ...\n"
+ " ip vrf identify [PID]\n"
+ " ip vrf pids [NAME]\n");
+
+ exit(-1);
+}
+
+/*
+ * parse process based cgroup file looking for PATH/vrf/NAME where
+ * NAME is the name of the vrf the process is associated with
+ */
+static int vrf_identify(pid_t pid, char *name, size_t len)
+{
+ char path[PATH_MAX];
+ char buf[4096];
+ char *vrf, *end;
+ FILE *fp;
+
+ snprintf(path, sizeof(path), "/proc/%d/cgroup", pid);
+ fp = fopen(path, "r");
+ if (!fp)
+ return -1;
+
+ memset(name, 0, len);
+
+ while (fgets(buf, sizeof(buf), fp)) {
+ /* want the controller-less cgroup */
+ if (strstr(buf, "::/") == NULL)
+ continue;
+
+ vrf = strstr(buf, "/vrf/");
+ if (vrf) {
+ vrf += 5; /* skip past "/vrf/" */
+ end = strchr(vrf, '\n');
+ if (end)
+ *end = '\0';
+
+ strlcpy(name, vrf, len);
+ break;
+ }
+ }
+
+ fclose(fp);
+
+ return 0;
+}
+
+static int ipvrf_identify(int argc, char **argv)
+{
+ char vrf[32];
+ int rc;
+ unsigned int pid;
+
+ if (argc < 1)
+ pid = getpid();
+ else if (argc > 1)
+ invarg("Extra arguments specified\n", argv[1]);
+ else if (get_unsigned(&pid, argv[0], 10))
+ invarg("Invalid pid\n", argv[0]);
+
+ rc = vrf_identify(pid, vrf, sizeof(vrf));
+ if (!rc) {
+ if (vrf[0] != '\0')
+ printf("%s\n", vrf);
+ } else {
+ fprintf(stderr, "Failed to lookup vrf association: %s\n",
+ strerror(errno));
+ }
+
+ return rc;
+}
+
+/* read PATH/vrf/NAME/cgroup.procs file */
+static void read_cgroup_pids(const char *base_path, char *name)
+{
+ char path[PATH_MAX];
+ char buf[4096];
+ FILE *fp;
+
+ if (snprintf(path, sizeof(path), "%s/vrf/%s%s",
+ base_path, name, CGRP_PROC_FILE) >= sizeof(path))
+ return;
+
+ fp = fopen(path, "r");
+ if (!fp)
+ return; /* no cgroup file, nothing to show */
+
+ /* dump contents (pids) of cgroup.procs */
+ while (fgets(buf, sizeof(buf), fp)) {
+ char *nl, comm[32];
+
+ nl = strchr(buf, '\n');
+ if (nl)
+ *nl = '\0';
+
+ if (get_command_name(buf, comm, sizeof(comm)))
+ strcpy(comm, "<terminated?>");
+
+ printf("%5s %s\n", buf, comm);
+ }
+
+ fclose(fp);
+}
+
+/* recurse path looking for PATH[/NETNS]/vrf/NAME */
+static int recurse_dir(char *base_path, char *name, const char *netns)
+{
+ char path[PATH_MAX];
+ struct dirent *de;
+ struct stat fstat;
+ int rc;
+ DIR *d;
+
+ d = opendir(base_path);
+ if (!d)
+ return -1;
+
+ while ((de = readdir(d)) != NULL) {
+ if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
+ continue;
+
+ if (!strcmp(de->d_name, "vrf")) {
+ const char *pdir = strrchr(base_path, '/');
+
+ /* found a 'vrf' directory. if it is for the given
+ * namespace then dump the cgroup pids
+ */
+ if (*netns == '\0' ||
+ (pdir && !strcmp(pdir+1, netns)))
+ read_cgroup_pids(base_path, name);
+
+ continue;
+ }
+
+ /* is this a subdir that needs to be walked */
+ if (snprintf(path, sizeof(path), "%s/%s",
+ base_path, de->d_name) >= sizeof(path))
+ continue;
+
+ if (lstat(path, &fstat) < 0)
+ continue;
+
+ if (S_ISDIR(fstat.st_mode)) {
+ rc = recurse_dir(path, name, netns);
+ if (rc != 0)
+ goto out;
+ }
+ }
+
+ rc = 0;
+out:
+ closedir(d);
+
+ return rc;
+}
+
+static int ipvrf_get_netns(char *netns, int len)
+{
+ if (netns_identify_pid("self", netns, len-3)) {
+ fprintf(stderr, "Failed to get name of network namespace: %s\n",
+ strerror(errno));
+ return -1;
+ }
+
+ if (*netns != '\0')
+ strcat(netns, "-ns");
+
+ return 0;
+}
+
+static int ipvrf_pids(int argc, char **argv)
+{
+ char *mnt, *vrf;
+ char netns[256];
+ int ret = -1;
+
+ if (argc != 1) {
+ fprintf(stderr, "Invalid arguments\n");
+ return -1;
+ }
+
+ vrf = argv[0];
+ if (!name_is_vrf(vrf)) {
+ fprintf(stderr, "Invalid VRF name\n");
+ return -1;
+ }
+
+ mnt = find_cgroup2_mount(true);
+ if (!mnt)
+ return -1;
+
+ if (ipvrf_get_netns(netns, sizeof(netns)) < 0)
+ goto out;
+
+ ret = recurse_dir(mnt, vrf, netns);
+
+out:
+ free(mnt);
+
+ return ret;
+}
+
+/* load BPF program to set sk_bound_dev_if for sockets */
+static char bpf_log_buf[256*1024];
+
+static int prog_load(int idx)
+{
+ struct bpf_insn prog[] = {
+ BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
+ BPF_MOV64_IMM(BPF_REG_3, idx),
+ BPF_MOV64_IMM(BPF_REG_2,
+ offsetof(struct bpf_sock, bound_dev_if)),
+ BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_3,
+ offsetof(struct bpf_sock, bound_dev_if)),
+ BPF_MOV64_IMM(BPF_REG_0, 1), /* r0 = verdict */
+ BPF_EXIT_INSN(),
+ };
+
+ return bpf_program_load(BPF_PROG_TYPE_CGROUP_SOCK, prog, sizeof(prog),
+ "GPL", bpf_log_buf, sizeof(bpf_log_buf),
+ false);
+}
+
+static int vrf_configure_cgroup(const char *path, int ifindex)
+{
+ int rc = -1, cg_fd, prog_fd = -1;
+
+ cg_fd = open(path, O_DIRECTORY | O_RDONLY);
+ if (cg_fd < 0) {
+ fprintf(stderr,
+ "Failed to open cgroup path: '%s'\n",
+ strerror(errno));
+ goto out;
+ }
+
+ /*
+ * Load bpf program into kernel and attach to cgroup to affect
+ * socket creates
+ */
+ prog_fd = prog_load(ifindex);
+ if (prog_fd < 0) {
+ fprintf(stderr, "Failed to load BPF prog: '%s'\n%s",
+ strerror(errno), bpf_log_buf);
+
+ if (errno != EPERM) {
+ fprintf(stderr,
+ "Kernel compiled with CGROUP_BPF enabled?\n");
+ }
+ goto out;
+ }
+
+ if (bpf_program_attach(prog_fd, cg_fd, BPF_CGROUP_INET_SOCK_CREATE)) {
+ fprintf(stderr, "Failed to attach prog to cgroup: '%s'\n",
+ strerror(errno));
+ goto out;
+ }
+
+ rc = 0;
+out:
+ close(cg_fd);
+ close(prog_fd);
+
+ return rc;
+}
+
+/* get base path for controller-less cgroup for a process.
+ * path returned does not include /vrf/NAME if it exists
+ */
+static int vrf_path(char *vpath, size_t len)
+{
+ char path[PATH_MAX];
+ char buf[4096];
+ char *vrf;
+ FILE *fp;
+
+ snprintf(path, sizeof(path), "/proc/%d/cgroup", getpid());
+ fp = fopen(path, "r");
+ if (!fp)
+ return -1;
+
+ vpath[0] = '\0';
+
+ while (fgets(buf, sizeof(buf), fp)) {
+ char *start, *nl;
+
+ start = strstr(buf, "::/");
+ if (!start)
+ continue;
+
+ /* advance past '::' */
+ start += 2;
+
+ nl = strchr(start, '\n');
+ if (nl)
+ *nl = '\0';
+
+ vrf = strstr(start, "/vrf");
+ if (vrf)
+ *vrf = '\0';
+
+ strlcpy(vpath, start, len);
+
+ /* if vrf path is just / then return nothing */
+ if (!strcmp(vpath, "/"))
+ vpath[0] = '\0';
+
+ break;
+ }
+
+ fclose(fp);
+
+ return 0;
+}
+
+static int vrf_switch(const char *name)
+{
+ char path[PATH_MAX], *mnt, pid[16];
+ char vpath[PATH_MAX], netns[256];
+ int ifindex = 0;
+ int rc = -1, len, fd = -1;
+
+ if (strcmp(name, "default")) {
+ ifindex = name_is_vrf(name);
+ if (!ifindex) {
+ fprintf(stderr, "Invalid VRF name\n");
+ return -1;
+ }
+ }
+
+ mnt = find_cgroup2_mount(true);
+ if (!mnt)
+ return -1;
+
+ /* -1 on length to add '/' to the end */
+ if (ipvrf_get_netns(netns, sizeof(netns) - 1) < 0)
+ goto out;
+
+ if (vrf_path(vpath, sizeof(vpath)) < 0) {
+ fprintf(stderr, "Failed to get base cgroup path: %s\n",
+ strerror(errno));
+ goto out;
+ }
+
+ /* if path already ends in netns then don't add it again */
+ if (*netns != '\0') {
+ char *pdir = strrchr(vpath, '/');
+
+ if (!pdir)
+ pdir = vpath;
+ else
+ pdir++;
+
+ if (strcmp(pdir, netns) == 0)
+ *pdir = '\0';
+
+ strcat(netns, "/");
+ }
+
+ /* path to cgroup; make sure buffer has room to cat "/cgroup.procs"
+ * to the end of the path
+ */
+ len = snprintf(path, sizeof(path) - sizeof(CGRP_PROC_FILE),
+ "%s%s/%svrf/%s",
+ mnt, vpath, netns, ifindex ? name : "");
+ if (len > sizeof(path) - sizeof(CGRP_PROC_FILE)) {
+ fprintf(stderr, "Invalid path to cgroup2 mount\n");
+ goto out;
+ }
+
+ if (make_path(path, 0755)) {
+ fprintf(stderr, "Failed to setup vrf cgroup2 directory\n");
+ goto out;
+ }
+
+ if (ifindex && vrf_configure_cgroup(path, ifindex))
+ goto out;
+
+ /*
+ * write pid to cgroup.procs making process part of cgroup
+ */
+ strcat(path, CGRP_PROC_FILE);
+ fd = open(path, O_RDWR | O_APPEND);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open cgroups.procs file: %s.\n",
+ strerror(errno));
+ goto out;
+ }
+
+ snprintf(pid, sizeof(pid), "%d", getpid());
+ if (write(fd, pid, strlen(pid)) < 0) {
+ fprintf(stderr, "Failed to join cgroup\n");
+ goto out2;
+ }
+
+ rc = 0;
+out2:
+ close(fd);
+out:
+ free(mnt);
+
+ drop_cap();
+
+ return rc;
+}
+
+static int do_switch(void *arg)
+{
+ char *vrf = arg;
+
+ return vrf_switch(vrf);
+}
+
+static int ipvrf_exec(int argc, char **argv)
+{
+ if (argc < 1) {
+ fprintf(stderr, "No VRF name specified\n");
+ return -1;
+ }
+ if (argc < 2) {
+ fprintf(stderr, "No command specified\n");
+ return -1;
+ }
+
+ if (is_selinux_enabled() && setexecfilecon(argv[1], "ifconfig_t")) {
+ fprintf(stderr, "setexecfilecon for \"%s\" failed\n", argv[1]);
+ return -1;
+ }
+
+ return -cmd_exec(argv[1], argv + 1, !!batch_mode, do_switch, argv[0]);
+}
+
+/* reset VRF association of current process to default VRF;
+ * used by netns_exec
+ */
+void vrf_reset(void)
+{
+ char vrf[32];
+
+ if (vrf_identify(getpid(), vrf, sizeof(vrf)) ||
+ (vrf[0] == '\0'))
+ return;
+
+ vrf_switch("default");
+}
+
+static int ipvrf_filter_req(struct nlmsghdr *nlh, int reqlen)
+{
+ struct rtattr *linkinfo;
+ int err;
+
+ if (vrf_filter.kind) {
+ linkinfo = addattr_nest(nlh, reqlen, IFLA_LINKINFO);
+
+ err = addattr_l(nlh, reqlen, IFLA_INFO_KIND, vrf_filter.kind,
+ strlen(vrf_filter.kind));
+ if (err)
+ return err;
+
+ addattr_nest_end(nlh, linkinfo);
+ }
+
+ return 0;
+}
+
+/* input arg is linkinfo */
+static __u32 vrf_table_linkinfo(struct rtattr *li[])
+{
+ struct rtattr *attr[IFLA_VRF_MAX + 1];
+
+ if (li[IFLA_INFO_DATA]) {
+ parse_rtattr_nested(attr, IFLA_VRF_MAX, li[IFLA_INFO_DATA]);
+
+ if (attr[IFLA_VRF_TABLE])
+ return rta_getattr_u32(attr[IFLA_VRF_TABLE]);
+ }
+
+ return 0;
+}
+
+static int ipvrf_print(struct nlmsghdr *n)
+{
+ struct ifinfomsg *ifi = NLMSG_DATA(n);
+ struct rtattr *tb[IFLA_MAX+1];
+ struct rtattr *li[IFLA_INFO_MAX+1];
+ int len = n->nlmsg_len;
+ const char *name;
+ __u32 tb_id;
+
+ len -= NLMSG_LENGTH(sizeof(*ifi));
+ if (len < 0)
+ return 0;
+
+ if (vrf_filter.ifindex && vrf_filter.ifindex != ifi->ifi_index)
+ return 0;
+
+ parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+
+ /* kernel does not support filter by master device */
+ if (tb[IFLA_MASTER]) {
+ int master = *(int *)RTA_DATA(tb[IFLA_MASTER]);
+
+ if (vrf_filter.master && master != vrf_filter.master)
+ return 0;
+ }
+
+ if (!tb[IFLA_IFNAME]) {
+ fprintf(stderr,
+ "BUG: device with ifindex %d has nil ifname\n",
+ ifi->ifi_index);
+ return 0;
+ }
+ name = rta_getattr_str(tb[IFLA_IFNAME]);
+
+ /* missing LINKINFO means not VRF. e.g., kernel does not
+ * support filtering on kind, so userspace needs to handle
+ */
+ if (!tb[IFLA_LINKINFO])
+ return 0;
+
+ parse_rtattr_nested(li, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+ if (!li[IFLA_INFO_KIND])
+ return 0;
+
+ if (strcmp(RTA_DATA(li[IFLA_INFO_KIND]), "vrf"))
+ return 0;
+
+ tb_id = vrf_table_linkinfo(li);
+ if (!tb_id) {
+ fprintf(stderr,
+ "BUG: VRF %s is missing table id\n", name);
+ return 0;
+ }
+
+ open_json_object(NULL);
+ print_string(PRINT_ANY, "name", "%-16s", name);
+ print_uint(PRINT_ANY, "table", " %5u", tb_id);
+ print_string(PRINT_FP, NULL, "%s", "\n");
+ close_json_object();
+
+ return 1;
+}
+
+static int ipvrf_show(int argc, char **argv)
+{
+ struct nlmsg_chain linfo = { NULL, NULL};
+ int rc = 0;
+
+ vrf_filter.kind = "vrf";
+
+ if (argc > 1)
+ usage();
+
+ if (argc == 1) {
+ __u32 tb_id;
+
+ tb_id = ipvrf_get_table(argv[0]);
+ if (!tb_id) {
+ fprintf(stderr, "Invalid VRF\n");
+ return 1;
+ }
+ printf("%s %u\n", argv[0], tb_id);
+ return 0;
+ }
+
+ if (ip_link_list(ipvrf_filter_req, &linfo) == 0) {
+ struct nlmsg_list *l;
+ unsigned nvrf = 0;
+
+ new_json_obj(json);
+
+ print_string(PRINT_FP, NULL, "%-16s", "Name");
+ print_string(PRINT_FP, NULL, " %5s\n", "Table");
+ print_string(PRINT_FP, NULL, "%s\n",
+ "-----------------------");
+
+ for (l = linfo.head; l; l = l->next)
+ nvrf += ipvrf_print(&l->h);
+
+ if (!nvrf)
+ print_string(PRINT_FP, NULL, "%s\n",
+ "No VRF has been configured");
+ delete_json_obj();
+ } else
+ rc = 1;
+
+ free_nlmsg_chain(&linfo);
+
+ return rc;
+}
+
+int do_ipvrf(int argc, char **argv)
+{
+ if (argc == 0)
+ return ipvrf_show(0, NULL);
+
+ if (matches(*argv, "identify") == 0)
+ return ipvrf_identify(argc-1, argv+1);
+
+ if (matches(*argv, "pids") == 0)
+ return ipvrf_pids(argc-1, argv+1);
+
+ if (matches(*argv, "exec") == 0)
+ return ipvrf_exec(argc-1, argv+1);
+
+ if (matches(*argv, "show") == 0 ||
+ matches(*argv, "lst") == 0 ||
+ matches(*argv, "list") == 0)
+ return ipvrf_show(argc-1, argv+1);
+
+ if (matches(*argv, "help") == 0)
+ usage();
+
+ fprintf(stderr, "Command \"%s\" is unknown, try \"ip vrf help\".\n",
+ *argv);
+
+ exit(-1);
+}
diff --git a/ip/ipxfrm.c b/ip/ipxfrm.c
new file mode 100644
index 0000000..b78c712
--- /dev/null
+++ b/ip/ipxfrm.c
@@ -0,0 +1,1553 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C)2004 USAGI/WIDE Project
+ *
+ * based on ip.c, iproute.c
+ *
+ * Authors:
+ * Masahide NAKAMURA @USAGI
+ */
+
+#include <alloca.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <netdb.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/udp.h>
+
+#include "utils.h"
+#include "xfrm.h"
+#include "ip_common.h"
+
+#define STRBUF_SIZE (128)
+
+struct xfrm_filter filter;
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip xfrm XFRM-OBJECT { COMMAND | help }\n"
+ "where XFRM-OBJECT := state | policy | monitor\n");
+ exit(-1);
+}
+
+/* This is based on utils.c(inet_addr_match) */
+int xfrm_addr_match(xfrm_address_t *x1, xfrm_address_t *x2, int bits)
+{
+ __u32 *a1 = (__u32 *)x1;
+ __u32 *a2 = (__u32 *)x2;
+ int words = bits >> 0x05;
+
+ bits &= 0x1f;
+
+ if (words)
+ if (memcmp(a1, a2, words << 2))
+ return -1;
+
+ if (bits) {
+ __u32 w1, w2;
+ __u32 mask;
+
+ w1 = a1[words];
+ w2 = a2[words];
+
+ mask = htonl((0xffffffff) << (0x20 - bits));
+
+ if ((w1 ^ w2) & mask)
+ return 1;
+ }
+
+ return 0;
+}
+
+int xfrm_xfrmproto_is_ipsec(__u8 proto)
+{
+ return (proto == IPPROTO_ESP ||
+ proto == IPPROTO_AH ||
+ proto == IPPROTO_COMP);
+}
+
+int xfrm_xfrmproto_is_ro(__u8 proto)
+{
+ return (proto == IPPROTO_ROUTING ||
+ proto == IPPROTO_DSTOPTS);
+}
+
+struct typeent {
+ const char *t_name;
+ int t_type;
+};
+
+static const struct typeent xfrmproto_types[] = {
+ { "esp", IPPROTO_ESP }, { "ah", IPPROTO_AH }, { "comp", IPPROTO_COMP },
+ { "route2", IPPROTO_ROUTING }, { "hao", IPPROTO_DSTOPTS },
+ { "ipsec-any", IPSEC_PROTO_ANY },
+ { NULL, -1 }
+};
+
+int xfrm_xfrmproto_getbyname(char *name)
+{
+ int i;
+
+ for (i = 0; ; i++) {
+ const struct typeent *t = &xfrmproto_types[i];
+
+ if (!t->t_name || t->t_type == -1)
+ break;
+
+ if (strcmp(t->t_name, name) == 0)
+ return t->t_type;
+ }
+
+ return -1;
+}
+
+const char *strxf_xfrmproto(__u8 proto)
+{
+ static char str[16];
+ int i;
+
+ for (i = 0; ; i++) {
+ const struct typeent *t = &xfrmproto_types[i];
+
+ if (!t->t_name || t->t_type == -1)
+ break;
+
+ if (t->t_type == proto)
+ return t->t_name;
+ }
+
+ sprintf(str, "%u", proto);
+ return str;
+}
+
+static const struct typeent algo_types[] = {
+ { "enc", XFRMA_ALG_CRYPT }, { "auth", XFRMA_ALG_AUTH },
+ { "comp", XFRMA_ALG_COMP }, { "aead", XFRMA_ALG_AEAD },
+ { "auth-trunc", XFRMA_ALG_AUTH_TRUNC },
+ { NULL, -1 }
+};
+
+int xfrm_algotype_getbyname(char *name)
+{
+ int i;
+
+ for (i = 0; ; i++) {
+ const struct typeent *t = &algo_types[i];
+
+ if (!t->t_name || t->t_type == -1)
+ break;
+
+ if (strcmp(t->t_name, name) == 0)
+ return t->t_type;
+ }
+
+ return -1;
+}
+
+const char *strxf_algotype(int type)
+{
+ static char str[32];
+ int i;
+
+ for (i = 0; ; i++) {
+ const struct typeent *t = &algo_types[i];
+
+ if (!t->t_name || t->t_type == -1)
+ break;
+
+ if (t->t_type == type)
+ return t->t_name;
+ }
+
+ sprintf(str, "%d", type);
+ return str;
+}
+
+static const char *strxf_mask8(__u8 mask)
+{
+ static char str[16];
+ const int sn = sizeof(mask) * 8 - 1;
+ __u8 b;
+ int i = 0;
+
+ for (b = (1 << sn); b > 0; b >>= 1)
+ str[i++] = ((b & mask) ? '1' : '0');
+ str[i] = '\0';
+
+ return str;
+}
+
+const char *strxf_mask32(__u32 mask)
+{
+ static char str[16];
+
+ sprintf(str, "%.8x", mask);
+
+ return str;
+}
+
+static const char *strxf_share(__u8 share)
+{
+ static char str[32];
+
+ switch (share) {
+ case XFRM_SHARE_ANY:
+ strcpy(str, "any");
+ break;
+ case XFRM_SHARE_SESSION:
+ strcpy(str, "session");
+ break;
+ case XFRM_SHARE_USER:
+ strcpy(str, "user");
+ break;
+ case XFRM_SHARE_UNIQUE:
+ strcpy(str, "unique");
+ break;
+ default:
+ sprintf(str, "%u", share);
+ break;
+ }
+
+ return str;
+}
+
+const char *strxf_proto(__u8 proto)
+{
+ static char buf[32];
+ struct protoent *pp;
+ const char *p;
+
+ pp = getprotobynumber(proto);
+ if (pp)
+ p = pp->p_name;
+ else {
+ sprintf(buf, "%u", proto);
+ p = buf;
+ }
+
+ return p;
+}
+
+const char *strxf_ptype(__u8 ptype)
+{
+ static char str[16];
+
+ switch (ptype) {
+ case XFRM_POLICY_TYPE_MAIN:
+ strcpy(str, "main");
+ break;
+ case XFRM_POLICY_TYPE_SUB:
+ strcpy(str, "sub");
+ break;
+ default:
+ sprintf(str, "%u", ptype);
+ break;
+ }
+
+ return str;
+}
+
+static void xfrm_id_info_print(xfrm_address_t *saddr, struct xfrm_id *id,
+ __u8 mode, __u32 reqid, __u16 family, int force_spi,
+ FILE *fp, const char *prefix, const char *title)
+{
+ if (title)
+ fputs(title, fp);
+
+ fprintf(fp, "src %s ", rt_addr_n2a(family, sizeof(*saddr), saddr));
+ fprintf(fp, "dst %s", rt_addr_n2a(family, sizeof(id->daddr), &id->daddr));
+ fprintf(fp, "%s", _SL_);
+
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, "\t");
+
+ fprintf(fp, "proto %s ", strxf_xfrmproto(id->proto));
+
+ if (show_stats > 0 || force_spi || id->spi) {
+ __u32 spi = ntohl(id->spi);
+
+ fprintf(fp, "spi 0x%08x", spi);
+ if (show_stats > 0)
+ fprintf(fp, "(%u)", spi);
+ fprintf(fp, " ");
+ }
+
+ fprintf(fp, "reqid %u", reqid);
+ if (show_stats > 0)
+ fprintf(fp, "(0x%08x)", reqid);
+ fprintf(fp, " ");
+
+ fprintf(fp, "mode ");
+ switch (mode) {
+ case XFRM_MODE_TRANSPORT:
+ fprintf(fp, "transport");
+ break;
+ case XFRM_MODE_TUNNEL:
+ fprintf(fp, "tunnel");
+ break;
+ case XFRM_MODE_ROUTEOPTIMIZATION:
+ fprintf(fp, "ro");
+ break;
+ case XFRM_MODE_IN_TRIGGER:
+ fprintf(fp, "in_trigger");
+ break;
+ case XFRM_MODE_BEET:
+ fprintf(fp, "beet");
+ break;
+ default:
+ fprintf(fp, "%u", mode);
+ break;
+ }
+ fprintf(fp, "%s", _SL_);
+}
+
+static const char *strxf_limit(__u64 limit)
+{
+ static char str[32];
+
+ if (limit == XFRM_INF)
+ strcpy(str, "(INF)");
+ else
+ sprintf(str, "%llu", (unsigned long long) limit);
+
+ return str;
+}
+
+static void xfrm_stats_print(struct xfrm_stats *s, FILE *fp,
+ const char *prefix)
+{
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, "stats:%s", _SL_);
+
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, " replay-window %u replay %u failed %u%s",
+ s->replay_window, s->replay, s->integrity_failed, _SL_);
+}
+
+static const char *strxf_time(__u64 time)
+{
+ static char str[32];
+
+ if (time == 0)
+ strcpy(str, "-");
+ else {
+ time_t t;
+ struct tm *tp;
+
+ /* XXX: treat time in the same manner of kernel's
+ * net/xfrm/xfrm_{user,state}.c
+ */
+ t = (long)time;
+ tp = localtime(&t);
+
+ strftime(str, sizeof(str), "%Y-%m-%d %T", tp);
+ }
+
+ return str;
+}
+
+static void xfrm_lifetime_print(struct xfrm_lifetime_cfg *cfg,
+ struct xfrm_lifetime_cur *cur,
+ FILE *fp, const char *prefix)
+{
+ if (cfg) {
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, "lifetime config:%s", _SL_);
+
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, " limit: soft %s(bytes),",
+ strxf_limit(cfg->soft_byte_limit));
+ fprintf(fp, " hard %s(bytes)%s",
+ strxf_limit(cfg->hard_byte_limit), _SL_);
+
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, " limit: soft %s(packets),",
+ strxf_limit(cfg->soft_packet_limit));
+ fprintf(fp, " hard %s(packets)%s",
+ strxf_limit(cfg->hard_packet_limit), _SL_);
+
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, " expire add: soft %llu(sec), hard %llu(sec)%s",
+ (unsigned long long) cfg->soft_add_expires_seconds,
+ (unsigned long long) cfg->hard_add_expires_seconds,
+ _SL_);
+
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, " expire use: soft %llu(sec), hard %llu(sec)%s",
+ (unsigned long long) cfg->soft_use_expires_seconds,
+ (unsigned long long) cfg->hard_use_expires_seconds,
+ _SL_);
+ }
+ if (cur) {
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, "lifetime current:%s", _SL_);
+
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, " %llu(bytes), %llu(packets)%s",
+ (unsigned long long) cur->bytes,
+ (unsigned long long) cur->packets,
+ _SL_);
+
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, " add %s ", strxf_time(cur->add_time));
+ fprintf(fp, "use %s%s", strxf_time(cur->use_time), _SL_);
+ }
+}
+
+void xfrm_selector_print(struct xfrm_selector *sel, __u16 family,
+ FILE *fp, const char *prefix)
+{
+ __u16 f;
+
+ f = sel->family;
+ if (f == AF_UNSPEC)
+ f = family;
+ if (f == AF_UNSPEC)
+ f = preferred_family;
+
+ if (prefix)
+ fputs(prefix, fp);
+
+ fprintf(fp, "src %s/%u ",
+ rt_addr_n2a(f, sizeof(sel->saddr), &sel->saddr),
+ sel->prefixlen_s);
+
+ fprintf(fp, "dst %s/%u ",
+ rt_addr_n2a(f, sizeof(sel->daddr), &sel->daddr),
+ sel->prefixlen_d);
+
+ if (sel->proto)
+ fprintf(fp, "proto %s ", strxf_proto(sel->proto));
+ switch (sel->proto) {
+ case IPPROTO_TCP:
+ case IPPROTO_UDP:
+ case IPPROTO_SCTP:
+ case IPPROTO_DCCP:
+ default: /* XXX */
+ if (sel->sport_mask)
+ fprintf(fp, "sport %u ", ntohs(sel->sport));
+ if (sel->dport_mask)
+ fprintf(fp, "dport %u ", ntohs(sel->dport));
+ break;
+ case IPPROTO_ICMP:
+ case IPPROTO_ICMPV6:
+ /* type/code is stored at sport/dport in selector */
+ if (sel->sport_mask)
+ fprintf(fp, "type %u ", ntohs(sel->sport));
+ if (sel->dport_mask)
+ fprintf(fp, "code %u ", ntohs(sel->dport));
+ break;
+ case IPPROTO_GRE:
+ if (sel->sport_mask || sel->dport_mask)
+ fprintf(fp, "key %u ",
+ (((__u32)ntohs(sel->sport)) << 16) +
+ ntohs(sel->dport));
+ break;
+ case IPPROTO_MH:
+ if (sel->sport_mask)
+ fprintf(fp, "type %u ", ntohs(sel->sport));
+ if (sel->dport_mask) {
+ if (show_stats > 0)
+ fprintf(fp, "(dport) 0x%.4x ", sel->dport);
+ }
+ break;
+ }
+
+ if (sel->ifindex > 0)
+ fprintf(fp, "dev %s ", ll_index_to_name(sel->ifindex));
+
+ if (show_stats > 0)
+ fprintf(fp, "uid %u", sel->user);
+
+ fprintf(fp, "%s", _SL_);
+}
+
+static void __xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
+ FILE *fp, const char *prefix, int newline,
+ bool nokeys)
+{
+ int keylen;
+ int i;
+
+ if (prefix)
+ fputs(prefix, fp);
+
+ fprintf(fp, "%s ", strxf_algotype(type));
+
+ if (len < sizeof(*algo)) {
+ fprintf(fp, "(ERROR truncated)");
+ goto fin;
+ }
+ len -= sizeof(*algo);
+
+ fprintf(fp, "%s ", algo->alg_name);
+
+ keylen = algo->alg_key_len / 8;
+ if (len < keylen) {
+ fprintf(fp, "(ERROR truncated)");
+ goto fin;
+ }
+
+ if (nokeys)
+ fprintf(fp, "<<Keys hidden>>");
+ else if (keylen > 0) {
+ fprintf(fp, "0x");
+ for (i = 0; i < keylen; i++)
+ fprintf(fp, "%.2x", (unsigned char)algo->alg_key[i]);
+
+ if (show_stats > 0)
+ fprintf(fp, " (%d bits)", algo->alg_key_len);
+ }
+
+ fin:
+ if (newline)
+ fprintf(fp, "%s", _SL_);
+}
+
+static inline void xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
+ FILE *fp, const char *prefix, bool nokeys)
+{
+ return __xfrm_algo_print(algo, type, len, fp, prefix, 1, nokeys);
+}
+
+static void xfrm_aead_print(struct xfrm_algo_aead *algo, int len,
+ FILE *fp, const char *prefix, bool nokeys)
+{
+ struct xfrm_algo *base_algo = alloca(sizeof(*base_algo) + algo->alg_key_len / 8);
+
+ memcpy(base_algo->alg_name, algo->alg_name, sizeof(base_algo->alg_name));
+ base_algo->alg_key_len = algo->alg_key_len;
+ memcpy(base_algo->alg_key, algo->alg_key, algo->alg_key_len / 8);
+
+ __xfrm_algo_print(base_algo, XFRMA_ALG_AEAD, len, fp, prefix, 0,
+ nokeys);
+
+ fprintf(fp, " %d", algo->alg_icv_len);
+
+ fprintf(fp, "%s", _SL_);
+}
+
+static void xfrm_auth_trunc_print(struct xfrm_algo_auth *algo, int len,
+ FILE *fp, const char *prefix, bool nokeys)
+{
+ struct xfrm_algo *base_algo = alloca(sizeof(*base_algo) + algo->alg_key_len / 8);
+
+ memcpy(base_algo->alg_name, algo->alg_name, sizeof(base_algo->alg_name));
+ base_algo->alg_key_len = algo->alg_key_len;
+ memcpy(base_algo->alg_key, algo->alg_key, algo->alg_key_len / 8);
+
+ __xfrm_algo_print(base_algo, XFRMA_ALG_AUTH_TRUNC, len, fp, prefix, 0,
+ nokeys);
+
+ fprintf(fp, " %d", algo->alg_trunc_len);
+
+ fprintf(fp, "%s", _SL_);
+}
+
+static void xfrm_tmpl_print(struct xfrm_user_tmpl *tmpls, int len,
+ FILE *fp, const char *prefix)
+{
+ int ntmpls = len / sizeof(struct xfrm_user_tmpl);
+ int i;
+
+ if (ntmpls <= 0) {
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, "(ERROR \"tmpl\" truncated)");
+ fprintf(fp, "%s", _SL_);
+ return;
+ }
+
+ for (i = 0; i < ntmpls; i++) {
+ struct xfrm_user_tmpl *tmpl = &tmpls[i];
+
+ if (prefix)
+ fputs(prefix, fp);
+
+ xfrm_id_info_print(&tmpl->saddr, &tmpl->id, tmpl->mode,
+ tmpl->reqid, tmpl->family, 0, fp, prefix, "tmpl ");
+
+ if (show_stats > 0 || tmpl->optional) {
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, "\t");
+ switch (tmpl->optional) {
+ case 0:
+ if (show_stats > 0)
+ fprintf(fp, "level required ");
+ break;
+ case 1:
+ fprintf(fp, "level use ");
+ break;
+ default:
+ fprintf(fp, "level %u ", tmpl->optional);
+ break;
+ }
+
+ if (show_stats > 0)
+ fprintf(fp, "share %s ", strxf_share(tmpl->share));
+
+ fprintf(fp, "%s", _SL_);
+ }
+
+ if (show_stats > 0) {
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, "\t");
+ fprintf(fp, "%s-mask %s ",
+ strxf_algotype(XFRMA_ALG_CRYPT),
+ strxf_mask32(tmpl->ealgos));
+ fprintf(fp, "%s-mask %s ",
+ strxf_algotype(XFRMA_ALG_AUTH),
+ strxf_mask32(tmpl->aalgos));
+ fprintf(fp, "%s-mask %s",
+ strxf_algotype(XFRMA_ALG_COMP),
+ strxf_mask32(tmpl->calgos));
+
+ fprintf(fp, "%s", _SL_);
+ }
+ }
+}
+
+static void xfrm_output_mark_print(struct rtattr *tb[], FILE *fp)
+{
+ __u32 output_mark = rta_getattr_u32(tb[XFRMA_OUTPUT_MARK]);
+
+ fprintf(fp, "output-mark 0x%x", output_mark);
+ if (tb[XFRMA_SET_MARK_MASK]) {
+ __u32 mask = rta_getattr_u32(tb[XFRMA_SET_MARK_MASK]);
+ fprintf(fp, "/0x%x", mask);
+ }
+}
+
+int xfrm_parse_mark(struct xfrm_mark *mark, int *argcp, char ***argvp)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+
+ NEXT_ARG();
+ if (get_u32(&mark->v, *argv, 0)) {
+ invarg("MARK value is invalid\n", *argv);
+ }
+ if (argc > 1)
+ NEXT_ARG();
+ else { /* last entry on parse line */
+ mark->m = 0xffffffff;
+ goto done;
+ }
+
+ if (strcmp(*argv, "mask") == 0) {
+ NEXT_ARG();
+ if (get_u32(&mark->m, *argv, 0)) {
+ invarg("MASK value is invalid\n", *argv);
+ }
+ } else {
+ mark->m = 0xffffffff;
+ PREV_ARG();
+ }
+
+done:
+ *argcp = argc;
+ *argvp = argv;
+
+ return 0;
+}
+
+void xfrm_xfrma_print(struct rtattr *tb[], __u16 family, FILE *fp,
+ const char *prefix, bool nokeys, bool dir)
+{
+ if (tb[XFRMA_MARK]) {
+ struct rtattr *rta = tb[XFRMA_MARK];
+ struct xfrm_mark *m = RTA_DATA(rta);
+
+ fprintf(fp, "\tmark %#x/%#x ", m->v, m->m);
+
+ if (tb[XFRMA_OUTPUT_MARK])
+ xfrm_output_mark_print(tb, fp);
+ fprintf(fp, "%s", _SL_);
+ } else if (tb[XFRMA_OUTPUT_MARK]) {
+ fprintf(fp, "\t");
+
+ xfrm_output_mark_print(tb, fp);
+ fprintf(fp, "%s", _SL_);
+ }
+
+ if (tb[XFRMA_ALG_AUTH] && !tb[XFRMA_ALG_AUTH_TRUNC]) {
+ struct rtattr *rta = tb[XFRMA_ALG_AUTH];
+
+ xfrm_algo_print(RTA_DATA(rta), XFRMA_ALG_AUTH, RTA_PAYLOAD(rta),
+ fp, prefix, nokeys);
+ }
+
+ if (tb[XFRMA_ALG_AUTH_TRUNC]) {
+ struct rtattr *rta = tb[XFRMA_ALG_AUTH_TRUNC];
+
+ xfrm_auth_trunc_print(RTA_DATA(rta), RTA_PAYLOAD(rta), fp,
+ prefix, nokeys);
+ }
+
+ if (tb[XFRMA_ALG_AEAD]) {
+ struct rtattr *rta = tb[XFRMA_ALG_AEAD];
+
+ xfrm_aead_print(RTA_DATA(rta), RTA_PAYLOAD(rta), fp, prefix,
+ nokeys);
+ }
+
+ if (tb[XFRMA_ALG_CRYPT]) {
+ struct rtattr *rta = tb[XFRMA_ALG_CRYPT];
+
+ xfrm_algo_print(RTA_DATA(rta), XFRMA_ALG_CRYPT,
+ RTA_PAYLOAD(rta), fp, prefix, nokeys);
+ }
+
+ if (tb[XFRMA_ALG_COMP]) {
+ struct rtattr *rta = tb[XFRMA_ALG_COMP];
+
+ xfrm_algo_print(RTA_DATA(rta), XFRMA_ALG_COMP, RTA_PAYLOAD(rta),
+ fp, prefix, nokeys);
+ }
+
+ if (tb[XFRMA_ENCAP]) {
+ struct xfrm_encap_tmpl *e;
+
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, "encap ");
+
+ if (RTA_PAYLOAD(tb[XFRMA_ENCAP]) < sizeof(*e)) {
+ fprintf(fp, "(ERROR truncated)");
+ fprintf(fp, "%s", _SL_);
+ return;
+ }
+ e = RTA_DATA(tb[XFRMA_ENCAP]);
+
+ fprintf(fp, "type ");
+ switch (e->encap_type) {
+ case UDP_ENCAP_ESPINUDP_NON_IKE:
+ fprintf(fp, "espinudp-nonike ");
+ break;
+ case UDP_ENCAP_ESPINUDP:
+ fprintf(fp, "espinudp ");
+ break;
+ case TCP_ENCAP_ESPINTCP:
+ fprintf(fp, "espintcp ");
+ break;
+ default:
+ fprintf(fp, "%u ", e->encap_type);
+ break;
+ }
+ fprintf(fp, "sport %u ", ntohs(e->encap_sport));
+ fprintf(fp, "dport %u ", ntohs(e->encap_dport));
+
+ fprintf(fp, "addr %s",
+ rt_addr_n2a(family, sizeof(e->encap_oa), &e->encap_oa));
+ fprintf(fp, "%s", _SL_);
+ }
+
+ if (tb[XFRMA_TMPL]) {
+ struct rtattr *rta = tb[XFRMA_TMPL];
+
+ xfrm_tmpl_print(RTA_DATA(rta),
+ RTA_PAYLOAD(rta), fp, prefix);
+ }
+
+ if (tb[XFRMA_COADDR]) {
+ const xfrm_address_t *coa;
+
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, "coa ");
+
+ coa = RTA_DATA(tb[XFRMA_COADDR]);
+ if (RTA_PAYLOAD(tb[XFRMA_COADDR]) < sizeof(*coa)) {
+ fprintf(fp, "(ERROR truncated)");
+ fprintf(fp, "%s", _SL_);
+ return;
+ }
+
+ fprintf(fp, "%s",
+ rt_addr_n2a(family, sizeof(*coa), coa));
+ fprintf(fp, "%s", _SL_);
+ }
+
+ if (tb[XFRMA_LASTUSED]) {
+ __u64 lastused;
+
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, "lastused ");
+
+ if (RTA_PAYLOAD(tb[XFRMA_LASTUSED]) < sizeof(lastused)) {
+ fprintf(fp, "(ERROR truncated)");
+ fprintf(fp, "%s", _SL_);
+ return;
+ }
+
+ lastused = rta_getattr_u64(tb[XFRMA_LASTUSED]);
+
+ fprintf(fp, "%s", strxf_time(lastused));
+ fprintf(fp, "%s", _SL_);
+ }
+
+ if (tb[XFRMA_REPLAY_VAL]) {
+ struct xfrm_replay_state *replay;
+
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, "anti-replay context: ");
+
+ if (RTA_PAYLOAD(tb[XFRMA_REPLAY_VAL]) < sizeof(*replay)) {
+ fprintf(fp, "(ERROR truncated)");
+ fprintf(fp, "%s", _SL_);
+ return;
+ }
+
+ replay = RTA_DATA(tb[XFRMA_REPLAY_VAL]);
+ fprintf(fp, "seq 0x%x, oseq 0x%x, bitmap 0x%08x",
+ replay->seq, replay->oseq, replay->bitmap);
+ fprintf(fp, "%s", _SL_);
+ }
+
+ if (tb[XFRMA_REPLAY_ESN_VAL]) {
+ struct xfrm_replay_state_esn *replay;
+ unsigned int i, j;
+
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, "anti-replay esn context:");
+
+ if (RTA_PAYLOAD(tb[XFRMA_REPLAY_ESN_VAL]) < sizeof(*replay)) {
+ fprintf(fp, "(ERROR truncated)");
+ fprintf(fp, "%s", _SL_);
+ return;
+ }
+ fprintf(fp, "%s", _SL_);
+
+ replay = RTA_DATA(tb[XFRMA_REPLAY_ESN_VAL]);
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, " seq-hi 0x%x, seq 0x%x, oseq-hi 0x%0x, oseq 0x%0x",
+ replay->seq_hi, replay->seq, replay->oseq_hi,
+ replay->oseq);
+ fprintf(fp, "%s", _SL_);
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, " replay_window %u, bitmap-length %u",
+ replay->replay_window, replay->bmp_len);
+ for (i = replay->bmp_len, j = 0; i; i--) {
+ if (j++ % 8 == 0) {
+ fprintf(fp, "%s", _SL_);
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, " ");
+ }
+ fprintf(fp, "%08x ", replay->bmp[i - 1]);
+ }
+ fprintf(fp, "%s", _SL_);
+ }
+ if (tb[XFRMA_OFFLOAD_DEV]) {
+ struct xfrm_user_offload *xuo;
+
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, "crypto offload parameters: ");
+
+ if (RTA_PAYLOAD(tb[XFRMA_OFFLOAD_DEV]) < sizeof(*xuo)) {
+ fprintf(fp, "(ERROR truncated)");
+ fprintf(fp, "%s", _SL_);
+ return;
+ }
+
+ xuo = (struct xfrm_user_offload *)
+ RTA_DATA(tb[XFRMA_OFFLOAD_DEV]);
+ fprintf(fp, "dev %s ",
+ ll_index_to_name(xuo->ifindex));
+ if (dir)
+ fprintf(fp, "dir %s ",
+ (xuo->flags & XFRM_OFFLOAD_INBOUND) ? "in" : "out");
+ fprintf(fp, "mode %s",
+ (xuo->flags & XFRM_OFFLOAD_PACKET) ? "packet" : "crypto");
+ fprintf(fp, "%s", _SL_);
+ }
+ if (tb[XFRMA_IF_ID]) {
+ __u32 if_id = rta_getattr_u32(tb[XFRMA_IF_ID]);
+
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, "if_id %#x", if_id);
+ fprintf(fp, "%s", _SL_);
+ }
+ if (tb[XFRMA_TFCPAD]) {
+ __u32 tfcpad = rta_getattr_u32(tb[XFRMA_TFCPAD]);
+
+ if (prefix)
+ fputs(prefix, fp);
+ fprintf(fp, "tfcpad %u", tfcpad);
+ fprintf(fp, "%s", _SL_);
+ }
+}
+
+static int xfrm_selector_iszero(struct xfrm_selector *s)
+{
+ struct xfrm_selector s0 = {};
+
+ return (memcmp(&s0, s, sizeof(s0)) == 0);
+}
+
+static void xfrm_sec_ctx_print(FILE *fp, struct rtattr *attr)
+{
+ struct xfrm_user_sec_ctx *sctx;
+
+ fprintf(fp, "\tsecurity context ");
+
+ if (RTA_PAYLOAD(attr) < sizeof(*sctx))
+ fprintf(fp, "(ERROR truncated)");
+
+ sctx = RTA_DATA(attr);
+ fprintf(fp, "%.*s %s", sctx->ctx_len, (char *)(sctx + 1), _SL_);
+}
+
+void xfrm_state_info_print(struct xfrm_usersa_info *xsinfo,
+ struct rtattr *tb[], FILE *fp, const char *prefix,
+ const char *title, bool nokeys)
+{
+ char buf[STRBUF_SIZE] = {};
+ int force_spi = xfrm_xfrmproto_is_ipsec(xsinfo->id.proto);
+
+ xfrm_id_info_print(&xsinfo->saddr, &xsinfo->id, xsinfo->mode,
+ xsinfo->reqid, xsinfo->family, force_spi, fp,
+ prefix, title);
+
+ if (prefix)
+ strlcat(buf, prefix, sizeof(buf));
+ strlcat(buf, "\t", sizeof(buf));
+
+ fputs(buf, fp);
+ fprintf(fp, "replay-window %u ", xsinfo->replay_window);
+ if (show_stats > 0)
+ fprintf(fp, "seq 0x%08u ", xsinfo->seq);
+ if (show_stats > 0 || xsinfo->flags) {
+ __u8 flags = xsinfo->flags;
+
+ fprintf(fp, "flag ");
+ XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_NOECN, "noecn");
+ XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_DECAP_DSCP, "decap-dscp");
+ XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_NOPMTUDISC, "nopmtudisc");
+ XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_WILDRECV, "wildrecv");
+ XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_ICMP, "icmp");
+ XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_AF_UNSPEC, "af-unspec");
+ XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_ALIGN4, "align4");
+ XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_ESN, "esn");
+ if (flags)
+ fprintf(fp, "%x", flags);
+ }
+ if (show_stats > 0 && tb[XFRMA_SA_EXTRA_FLAGS]) {
+ __u32 extra_flags = rta_getattr_u32(tb[XFRMA_SA_EXTRA_FLAGS]);
+
+ fprintf(fp, "extra_flag ");
+ XFRM_FLAG_PRINT(fp, extra_flags,
+ XFRM_SA_XFLAG_DONT_ENCAP_DSCP,
+ "dont-encap-dscp");
+ XFRM_FLAG_PRINT(fp, extra_flags,
+ XFRM_SA_XFLAG_OSEQ_MAY_WRAP,
+ "oseq-may-wrap");
+ if (extra_flags)
+ fprintf(fp, "%x", extra_flags);
+ }
+ if (show_stats > 0)
+ fprintf(fp, " (0x%s)", strxf_mask8(xsinfo->flags));
+ fprintf(fp, "%s", _SL_);
+
+ xfrm_xfrma_print(tb, xsinfo->family, fp, buf, nokeys, true);
+
+ if (!xfrm_selector_iszero(&xsinfo->sel)) {
+ char sbuf[STRBUF_SIZE];
+
+ memcpy(sbuf, buf, sizeof(sbuf));
+ strlcat(sbuf, "sel ", sizeof(sbuf));
+
+ xfrm_selector_print(&xsinfo->sel, xsinfo->family, fp, sbuf);
+ }
+
+ if (show_stats > 0) {
+ xfrm_lifetime_print(&xsinfo->lft, &xsinfo->curlft, fp, buf);
+ xfrm_stats_print(&xsinfo->stats, fp, buf);
+ }
+
+ if (tb[XFRMA_SEC_CTX])
+ xfrm_sec_ctx_print(fp, tb[XFRMA_SEC_CTX]);
+}
+
+void xfrm_policy_info_print(struct xfrm_userpolicy_info *xpinfo,
+ struct rtattr *tb[], FILE *fp, const char *prefix,
+ const char *title)
+{
+ char buf[STRBUF_SIZE] = {};
+
+ xfrm_selector_print(&xpinfo->sel, preferred_family, fp, title);
+
+ if (tb[XFRMA_SEC_CTX])
+ xfrm_sec_ctx_print(fp, tb[XFRMA_SEC_CTX]);
+
+ if (prefix)
+ strlcat(buf, prefix, sizeof(buf));
+ strlcat(buf, "\t", sizeof(buf));
+
+ fputs(buf, fp);
+ if (xpinfo->dir >= XFRM_POLICY_MAX) {
+ xpinfo->dir -= XFRM_POLICY_MAX;
+ fprintf(fp, "socket ");
+ } else
+ fprintf(fp, "dir ");
+
+ switch (xpinfo->dir) {
+ case XFRM_POLICY_IN:
+ fprintf(fp, "in");
+ break;
+ case XFRM_POLICY_OUT:
+ fprintf(fp, "out");
+ break;
+ case XFRM_POLICY_FWD:
+ fprintf(fp, "fwd");
+ break;
+ default:
+ fprintf(fp, "%u", xpinfo->dir);
+ break;
+ }
+ fprintf(fp, " ");
+
+ switch (xpinfo->action) {
+ case XFRM_POLICY_ALLOW:
+ if (show_stats > 0)
+ fprintf(fp, "action allow ");
+ break;
+ case XFRM_POLICY_BLOCK:
+ fprintf(fp, "action block ");
+ break;
+ default:
+ fprintf(fp, "action %u ", xpinfo->action);
+ break;
+ }
+
+ if (show_stats)
+ fprintf(fp, "index %u ", xpinfo->index);
+ fprintf(fp, "priority %u ", xpinfo->priority);
+
+ if (tb[XFRMA_POLICY_TYPE]) {
+ struct xfrm_userpolicy_type *upt;
+
+ fprintf(fp, "ptype ");
+
+ if (RTA_PAYLOAD(tb[XFRMA_POLICY_TYPE]) < sizeof(*upt))
+ fprintf(fp, "(ERROR truncated)");
+
+ upt = RTA_DATA(tb[XFRMA_POLICY_TYPE]);
+ fprintf(fp, "%s ", strxf_ptype(upt->type));
+ }
+
+ if (show_stats > 0)
+ fprintf(fp, "share %s ", strxf_share(xpinfo->share));
+
+ if (show_stats > 0 || xpinfo->flags) {
+ __u8 flags = xpinfo->flags;
+
+ fprintf(fp, "flag ");
+ XFRM_FLAG_PRINT(fp, flags, XFRM_POLICY_LOCALOK, "localok");
+ XFRM_FLAG_PRINT(fp, flags, XFRM_POLICY_ICMP, "icmp");
+ if (flags)
+ fprintf(fp, "%x", flags);
+ }
+ if (show_stats > 0)
+ fprintf(fp, " (0x%s)", strxf_mask8(xpinfo->flags));
+ fprintf(fp, "%s", _SL_);
+
+ if (show_stats > 0)
+ xfrm_lifetime_print(&xpinfo->lft, &xpinfo->curlft, fp, buf);
+
+ xfrm_xfrma_print(tb, xpinfo->sel.family, fp, buf, false, false);
+}
+
+int xfrm_id_parse(xfrm_address_t *saddr, struct xfrm_id *id, __u16 *family,
+ int loose, int *argcp, char ***argvp)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+ inet_prefix dst = {};
+ inet_prefix src = {};
+
+ while (1) {
+ if (strcmp(*argv, "src") == 0) {
+ NEXT_ARG();
+
+ get_prefix(&src, *argv, preferred_family);
+ if (src.family == AF_UNSPEC)
+ invarg("value after \"src\" has an unrecognized address family", *argv);
+ if (family)
+ *family = src.family;
+
+ memcpy(saddr, &src.data, sizeof(*saddr));
+
+ filter.id_src_mask = src.bitlen;
+
+ } else if (strcmp(*argv, "dst") == 0) {
+ NEXT_ARG();
+
+ get_prefix(&dst, *argv, preferred_family);
+ if (dst.family == AF_UNSPEC)
+ invarg("value after \"dst\" has an unrecognized address family", *argv);
+ if (family)
+ *family = dst.family;
+
+ memcpy(&id->daddr, &dst.data, sizeof(id->daddr));
+
+ filter.id_dst_mask = dst.bitlen;
+
+ } else if (strcmp(*argv, "proto") == 0) {
+ int ret;
+
+ NEXT_ARG();
+
+ ret = xfrm_xfrmproto_getbyname(*argv);
+ if (ret < 0)
+ invarg("XFRM-PROTO value is invalid", *argv);
+
+ id->proto = (__u8)ret;
+
+ filter.id_proto_mask = XFRM_FILTER_MASK_FULL;
+
+ } else if (strcmp(*argv, "spi") == 0) {
+ NEXT_ARG();
+ if (get_be32(&id->spi, *argv, 0))
+ invarg("SPI value is invalid", *argv);
+
+ filter.id_spi_mask = XFRM_FILTER_MASK_FULL;
+
+ } else {
+ PREV_ARG(); /* back track */
+ break;
+ }
+
+ if (!NEXT_ARG_OK())
+ break;
+ NEXT_ARG();
+ }
+
+ if (src.family && dst.family && (src.family != dst.family))
+ invarg("the same address family is required between values after \"src\" and \"dst\"", *argv);
+
+ if (id->spi && id->proto) {
+ if (xfrm_xfrmproto_is_ro(id->proto)) {
+ fprintf(stderr, "\"spi\" is invalid with XFRM-PROTO value \"%s\"\n",
+ strxf_xfrmproto(id->proto));
+ exit(1);
+ } else if (id->proto == IPPROTO_COMP && ntohl(id->spi) >= 0x10000) {
+ fprintf(stderr, "SPI value is too large with XFRM-PROTO value \"%s\"\n",
+ strxf_xfrmproto(id->proto));
+ exit(1);
+ }
+ }
+
+ if (loose == 0 && id->proto == 0)
+ missarg("XFRM-PROTO");
+ if (argc == *argcp)
+ missarg("ID");
+
+ *argcp = argc;
+ *argvp = argv;
+
+ return 0;
+}
+
+int xfrm_mode_parse(__u8 *mode, int *argcp, char ***argvp)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+
+ if (matches(*argv, "transport") == 0)
+ *mode = XFRM_MODE_TRANSPORT;
+ else if (matches(*argv, "tunnel") == 0)
+ *mode = XFRM_MODE_TUNNEL;
+ else if (matches(*argv, "ro") == 0)
+ *mode = XFRM_MODE_ROUTEOPTIMIZATION;
+ else if (matches(*argv, "in_trigger") == 0)
+ *mode = XFRM_MODE_IN_TRIGGER;
+ else if (matches(*argv, "beet") == 0)
+ *mode = XFRM_MODE_BEET;
+ else
+ invarg("MODE value is invalid", *argv);
+
+ *argcp = argc;
+ *argvp = argv;
+
+ return 0;
+}
+
+int xfrm_encap_type_parse(__u16 *type, int *argcp, char ***argvp)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+
+ if (strcmp(*argv, "espinudp-nonike") == 0)
+ *type = UDP_ENCAP_ESPINUDP_NON_IKE;
+ else if (strcmp(*argv, "espinudp") == 0)
+ *type = UDP_ENCAP_ESPINUDP;
+ else if (strcmp(*argv, "espintcp") == 0)
+ *type = TCP_ENCAP_ESPINTCP;
+ else
+ invarg("ENCAP-TYPE value is invalid", *argv);
+
+ *argcp = argc;
+ *argvp = argv;
+
+ return 0;
+}
+
+/* NOTE: reqid is used by host-byte order */
+int xfrm_reqid_parse(__u32 *reqid, int *argcp, char ***argvp)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+
+ if (get_u32(reqid, *argv, 0))
+ invarg("REQID value is invalid", *argv);
+
+ *argcp = argc;
+ *argvp = argv;
+
+ return 0;
+}
+
+static int xfrm_selector_upspec_parse(struct xfrm_selector *sel,
+ int *argcp, char ***argvp)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+ char *sportp = NULL;
+ char *dportp = NULL;
+ char *typep = NULL;
+ char *codep = NULL;
+ char *grekey = NULL;
+
+ while (1) {
+ if (strcmp(*argv, "proto") == 0) {
+ __u8 upspec;
+
+ NEXT_ARG();
+
+ if (strcmp(*argv, "any") == 0)
+ upspec = 0;
+ else {
+ struct protoent *pp;
+
+ pp = getprotobyname(*argv);
+ if (pp)
+ upspec = pp->p_proto;
+ else {
+ if (get_u8(&upspec, *argv, 0))
+ invarg("PROTO value is invalid", *argv);
+ }
+ }
+ sel->proto = upspec;
+
+ filter.upspec_proto_mask = XFRM_FILTER_MASK_FULL;
+
+ } else if (strcmp(*argv, "sport") == 0) {
+ sportp = *argv;
+
+ NEXT_ARG();
+
+ if (get_be16(&sel->sport, *argv, 0))
+ invarg("value after \"sport\" is invalid", *argv);
+ if (sel->sport)
+ sel->sport_mask = ~((__u16)0);
+
+ filter.upspec_sport_mask = XFRM_FILTER_MASK_FULL;
+
+ } else if (strcmp(*argv, "dport") == 0) {
+ dportp = *argv;
+
+ NEXT_ARG();
+
+ if (get_be16(&sel->dport, *argv, 0))
+ invarg("value after \"dport\" is invalid", *argv);
+ if (sel->dport)
+ sel->dport_mask = ~((__u16)0);
+
+ filter.upspec_dport_mask = XFRM_FILTER_MASK_FULL;
+
+ } else if (strcmp(*argv, "type") == 0) {
+ typep = *argv;
+
+ NEXT_ARG();
+
+ if (get_u16(&sel->sport, *argv, 0) ||
+ (sel->sport & ~((__u16)0xff)))
+ invarg("value after \"type\" is invalid", *argv);
+ sel->sport = htons(sel->sport);
+ sel->sport_mask = ~((__u16)0);
+
+ filter.upspec_sport_mask = XFRM_FILTER_MASK_FULL;
+
+
+ } else if (strcmp(*argv, "code") == 0) {
+ codep = *argv;
+
+ NEXT_ARG();
+
+ if (get_u16(&sel->dport, *argv, 0) ||
+ (sel->dport & ~((__u16)0xff)))
+ invarg("value after \"code\" is invalid", *argv);
+ sel->dport = htons(sel->dport);
+ sel->dport_mask = ~((__u16)0);
+
+ filter.upspec_dport_mask = XFRM_FILTER_MASK_FULL;
+
+ } else if (strcmp(*argv, "key") == 0) {
+ unsigned int uval;
+
+ grekey = *argv;
+
+ NEXT_ARG();
+
+ if (strchr(*argv, '.'))
+ uval = htonl(get_addr32(*argv));
+ else {
+ if (get_unsigned(&uval, *argv, 0) < 0) {
+ fprintf(stderr, "value after \"key\" is invalid\n");
+ exit(-1);
+ }
+ }
+
+ sel->sport = htons(uval >> 16);
+ sel->dport = htons(uval & 0xffff);
+ sel->sport_mask = ~((__u16)0);
+ sel->dport_mask = ~((__u16)0);
+
+ filter.upspec_dport_mask = XFRM_FILTER_MASK_FULL;
+
+ } else {
+ PREV_ARG(); /* back track */
+ break;
+ }
+
+ if (!NEXT_ARG_OK())
+ break;
+ NEXT_ARG();
+ }
+ if (argc == *argcp)
+ missarg("UPSPEC");
+ if (sportp || dportp) {
+ switch (sel->proto) {
+ case IPPROTO_TCP:
+ case IPPROTO_UDP:
+ case IPPROTO_SCTP:
+ case IPPROTO_DCCP:
+ case IPPROTO_IP: /* to allow shared SA for different protocols */
+ break;
+ default:
+ fprintf(stderr, "\"sport\" and \"dport\" are invalid with PROTO value \"%s\"\n", strxf_proto(sel->proto));
+ exit(1);
+ }
+ }
+ if (typep || codep) {
+ switch (sel->proto) {
+ case IPPROTO_ICMP:
+ case IPPROTO_ICMPV6:
+ case IPPROTO_MH:
+ break;
+ default:
+ fprintf(stderr, "\"type\" and \"code\" are invalid with PROTO value \"%s\"\n", strxf_proto(sel->proto));
+ exit(1);
+ }
+ }
+ if (grekey) {
+ switch (sel->proto) {
+ case IPPROTO_GRE:
+ break;
+ default:
+ fprintf(stderr, "\"key\" is invalid with PROTO value \"%s\"\n", strxf_proto(sel->proto));
+ exit(1);
+ }
+ }
+
+ *argcp = argc;
+ *argvp = argv;
+
+ return 0;
+}
+
+int xfrm_selector_parse(struct xfrm_selector *sel, int *argcp, char ***argvp)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+ inet_prefix dst = {};
+ inet_prefix src = {};
+ char *upspecp = NULL;
+
+ while (1) {
+ if (strcmp(*argv, "src") == 0) {
+ NEXT_ARG();
+
+ get_prefix(&src, *argv, preferred_family);
+ if (src.family == AF_UNSPEC)
+ invarg("value after \"src\" has an unrecognized address family", *argv);
+ sel->family = src.family;
+
+ memcpy(&sel->saddr, &src.data, sizeof(sel->saddr));
+ sel->prefixlen_s = src.bitlen;
+
+ filter.sel_src_mask = src.bitlen;
+
+ } else if (strcmp(*argv, "dst") == 0) {
+ NEXT_ARG();
+
+ get_prefix(&dst, *argv, preferred_family);
+ if (dst.family == AF_UNSPEC)
+ invarg("value after \"dst\" has an unrecognized address family", *argv);
+ sel->family = dst.family;
+
+ memcpy(&sel->daddr, &dst.data, sizeof(sel->daddr));
+ sel->prefixlen_d = dst.bitlen;
+
+ filter.sel_dst_mask = dst.bitlen;
+
+ } else if (strcmp(*argv, "dev") == 0) {
+ int ifindex;
+
+ NEXT_ARG();
+
+ if (strcmp(*argv, "none") == 0)
+ ifindex = 0;
+ else {
+ ifindex = ll_name_to_index(*argv);
+ if (ifindex <= 0)
+ invarg("DEV value is invalid", *argv);
+ }
+ sel->ifindex = ifindex;
+
+ filter.sel_dev_mask = XFRM_FILTER_MASK_FULL;
+
+ } else {
+ if (upspecp) {
+ PREV_ARG(); /* back track */
+ break;
+ } else {
+ upspecp = *argv;
+ xfrm_selector_upspec_parse(sel, &argc, &argv);
+ }
+ }
+
+ if (!NEXT_ARG_OK())
+ break;
+
+ NEXT_ARG();
+ }
+
+ if (src.family && dst.family && (src.family != dst.family))
+ invarg("the same address family is required between values after \"src\" and \"dst\"", *argv);
+
+ if (argc == *argcp)
+ missarg("SELECTOR");
+
+ *argcp = argc;
+ *argvp = argv;
+
+ return 0;
+}
+
+int xfrm_lifetime_cfg_parse(struct xfrm_lifetime_cfg *lft,
+ int *argcp, char ***argvp)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+ int ret;
+
+ if (strcmp(*argv, "time-soft") == 0) {
+ NEXT_ARG();
+ ret = get_u64(&lft->soft_add_expires_seconds, *argv, 0);
+ if (ret)
+ invarg("value after \"time-soft\" is invalid", *argv);
+ } else if (strcmp(*argv, "time-hard") == 0) {
+ NEXT_ARG();
+ ret = get_u64(&lft->hard_add_expires_seconds, *argv, 0);
+ if (ret)
+ invarg("value after \"time-hard\" is invalid", *argv);
+ } else if (strcmp(*argv, "time-use-soft") == 0) {
+ NEXT_ARG();
+ ret = get_u64(&lft->soft_use_expires_seconds, *argv, 0);
+ if (ret)
+ invarg("value after \"time-use-soft\" is invalid", *argv);
+ } else if (strcmp(*argv, "time-use-hard") == 0) {
+ NEXT_ARG();
+ ret = get_u64(&lft->hard_use_expires_seconds, *argv, 0);
+ if (ret)
+ invarg("value after \"time-use-hard\" is invalid", *argv);
+ } else if (strcmp(*argv, "byte-soft") == 0) {
+ NEXT_ARG();
+ ret = get_u64(&lft->soft_byte_limit, *argv, 0);
+ if (ret)
+ invarg("value after \"byte-soft\" is invalid", *argv);
+ } else if (strcmp(*argv, "byte-hard") == 0) {
+ NEXT_ARG();
+ ret = get_u64(&lft->hard_byte_limit, *argv, 0);
+ if (ret)
+ invarg("value after \"byte-hard\" is invalid", *argv);
+ } else if (strcmp(*argv, "packet-soft") == 0) {
+ NEXT_ARG();
+ ret = get_u64(&lft->soft_packet_limit, *argv, 0);
+ if (ret)
+ invarg("value after \"packet-soft\" is invalid", *argv);
+ } else if (strcmp(*argv, "packet-hard") == 0) {
+ NEXT_ARG();
+ ret = get_u64(&lft->hard_packet_limit, *argv, 0);
+ if (ret)
+ invarg("value after \"packet-hard\" is invalid", *argv);
+ } else
+ invarg("LIMIT value is invalid", *argv);
+
+ *argcp = argc;
+ *argvp = argv;
+
+ return 0;
+}
+
+int do_xfrm(int argc, char **argv)
+{
+ memset(&filter, 0, sizeof(filter));
+
+ if (argc < 1)
+ usage();
+
+ if (matches(*argv, "state") == 0 ||
+ matches(*argv, "sa") == 0)
+ return do_xfrm_state(argc-1, argv+1);
+ else if (matches(*argv, "policy") == 0)
+ return do_xfrm_policy(argc-1, argv+1);
+ else if (matches(*argv, "monitor") == 0)
+ return do_xfrm_monitor(argc-1, argv+1);
+ else if (matches(*argv, "help") == 0) {
+ usage();
+ fprintf(stderr, "xfrm Object \"%s\" is unknown.\n", *argv);
+ exit(-1);
+ }
+ usage();
+}
diff --git a/ip/link_gre.c b/ip/link_gre.c
new file mode 100644
index 0000000..010b482
--- /dev/null
+++ b/ip/link_gre.c
@@ -0,0 +1,576 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * link_gre.c gre driver module
+ *
+ * Authors: Herbert Xu <herbert@gondor.apana.org.au>
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+static bool gre_is_erspan(struct link_util *lu)
+{
+ return !strcmp(lu->id, "erspan");
+}
+
+static void gre_print_help(struct link_util *lu, int argc, char **argv, FILE *f)
+{
+ bool is_erspan = gre_is_erspan(lu);
+
+ fprintf(f,
+ "Usage: ... %-9s [ remote ADDR ]\n"
+ " [ local ADDR ]\n"
+ " [ [no][i|o]seq ]\n"
+ " [ [i|o]key KEY | no[i|o]key ]\n"
+ " [ [no][i|o]csum ]\n"
+ " [ ttl TTL ]\n"
+ " [ tos TOS ]\n"
+ " [ [no]pmtudisc ]\n"
+ " [ [no]ignore-df ]\n"
+ " [ dev PHYS_DEV ]\n"
+ " [ fwmark MARK ]\n"
+ " [ external ]\n"
+ " [ noencap ]\n"
+ " [ encap { fou | gue | none } ]\n"
+ " [ encap-sport PORT ]\n"
+ " [ encap-dport PORT ]\n"
+ " [ [no]encap-csum ]\n"
+ " [ [no]encap-csum6 ]\n"
+ " [ [no]encap-remcsum ]\n", lu->id);
+ if (is_erspan)
+ fprintf(f,
+ " [ erspan_ver version ]\n"
+ " [ erspan IDX ]\n"
+ " [ erspan_dir { ingress | egress } ]\n"
+ " [ erspan_hwid hwid ]\n");
+ fprintf(f,
+ "\n"
+ "Where: ADDR := { IP_ADDRESS | any }\n"
+ " TOS := { NUMBER | inherit }\n"
+ " TTL := { 1..255 | inherit }\n"
+ " KEY := { DOTTED_QUAD | NUMBER }\n"
+ " MARK := { 0x0..0xffffffff }\n");
+}
+
+static int gre_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ struct ifinfomsg *ifi = NLMSG_DATA(n);
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg i;
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(*ifi)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_GETLINK,
+ .i.ifi_family = preferred_family,
+ .i.ifi_index = ifi->ifi_index,
+ };
+ struct nlmsghdr *answer = NULL;
+ struct rtattr *tb[IFLA_MAX + 1];
+ struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+ struct rtattr *greinfo[IFLA_GRE_MAX + 1];
+ int len;
+ __u16 iflags = 0;
+ __u16 oflags = 0;
+ __be32 ikey = 0;
+ __be32 okey = 0;
+ inet_prefix saddr, daddr;
+ __u8 pmtudisc = 1;
+ __u8 ignore_df = 0;
+ __u8 tos = 0;
+ __u8 ttl = 0;
+ __u32 link = 0;
+ __u16 encaptype = 0;
+ __u16 encapflags = 0;
+ __u16 encapsport = 0;
+ __u16 encapdport = 0;
+ __u8 metadata = 0;
+ __u32 fwmark = 0;
+ bool is_erspan = gre_is_erspan(lu);
+ __u32 erspan_idx = 0;
+ __u8 erspan_ver = 1;
+ __u8 erspan_dir = 0;
+ __u16 erspan_hwid = 0;
+
+ inet_prefix_reset(&saddr);
+ inet_prefix_reset(&daddr);
+
+ if (!(n->nlmsg_flags & NLM_F_CREATE)) {
+ const struct rtattr *rta;
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0)
+ goto get_failed;
+
+ len = answer->nlmsg_len;
+ len -= NLMSG_LENGTH(sizeof(*ifi));
+ if (len < 0)
+ goto get_failed;
+
+ parse_rtattr(tb, IFLA_MAX, IFLA_RTA(NLMSG_DATA(answer)), len);
+
+ if (!tb[IFLA_LINKINFO])
+ goto get_failed;
+
+ parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+ if (!linkinfo[IFLA_INFO_DATA])
+ goto get_failed;
+
+ parse_rtattr_nested(greinfo, IFLA_GRE_MAX,
+ linkinfo[IFLA_INFO_DATA]);
+
+ rta = greinfo[IFLA_GRE_LOCAL];
+ if (rta && get_addr_rta(&saddr, rta, AF_INET))
+ goto get_failed;
+
+ rta = greinfo[IFLA_GRE_REMOTE];
+ if (rta && get_addr_rta(&daddr, rta, AF_INET))
+ goto get_failed;
+
+ if (greinfo[IFLA_GRE_IKEY])
+ ikey = rta_getattr_u32(greinfo[IFLA_GRE_IKEY]);
+
+ if (greinfo[IFLA_GRE_OKEY])
+ okey = rta_getattr_u32(greinfo[IFLA_GRE_OKEY]);
+
+ if (greinfo[IFLA_GRE_IFLAGS])
+ iflags = rta_getattr_u16(greinfo[IFLA_GRE_IFLAGS]);
+
+ if (greinfo[IFLA_GRE_OFLAGS])
+ oflags = rta_getattr_u16(greinfo[IFLA_GRE_OFLAGS]);
+
+ if (greinfo[IFLA_GRE_PMTUDISC])
+ pmtudisc = rta_getattr_u8(
+ greinfo[IFLA_GRE_PMTUDISC]);
+
+ if (greinfo[IFLA_GRE_IGNORE_DF])
+ ignore_df =
+ !!rta_getattr_u8(greinfo[IFLA_GRE_IGNORE_DF]);
+
+ if (greinfo[IFLA_GRE_TOS])
+ tos = rta_getattr_u8(greinfo[IFLA_GRE_TOS]);
+
+ if (greinfo[IFLA_GRE_TTL])
+ ttl = rta_getattr_u8(greinfo[IFLA_GRE_TTL]);
+
+ if (greinfo[IFLA_GRE_LINK])
+ link = rta_getattr_u32(greinfo[IFLA_GRE_LINK]);
+
+ if (greinfo[IFLA_GRE_ENCAP_TYPE])
+ encaptype = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_TYPE]);
+
+ if (greinfo[IFLA_GRE_ENCAP_FLAGS])
+ encapflags = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_FLAGS]);
+
+ if (greinfo[IFLA_GRE_ENCAP_SPORT])
+ encapsport = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_SPORT]);
+
+ if (greinfo[IFLA_GRE_ENCAP_DPORT])
+ encapdport = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_DPORT]);
+
+ if (greinfo[IFLA_GRE_COLLECT_METADATA])
+ metadata = 1;
+
+ if (greinfo[IFLA_GRE_FWMARK])
+ fwmark = rta_getattr_u32(greinfo[IFLA_GRE_FWMARK]);
+
+ if (greinfo[IFLA_GRE_ERSPAN_INDEX])
+ erspan_idx = rta_getattr_u32(greinfo[IFLA_GRE_ERSPAN_INDEX]);
+
+ if (greinfo[IFLA_GRE_ERSPAN_VER])
+ erspan_ver = rta_getattr_u8(greinfo[IFLA_GRE_ERSPAN_VER]);
+
+ if (greinfo[IFLA_GRE_ERSPAN_DIR])
+ erspan_dir = rta_getattr_u8(greinfo[IFLA_GRE_ERSPAN_DIR]);
+
+ if (greinfo[IFLA_GRE_ERSPAN_HWID])
+ erspan_hwid = rta_getattr_u16(greinfo[IFLA_GRE_ERSPAN_HWID]);
+
+ free(answer);
+ }
+
+ while (argc > 0) {
+ if (!matches(*argv, "key")) {
+ NEXT_ARG();
+ iflags |= GRE_KEY;
+ oflags |= GRE_KEY;
+ ikey = okey = tnl_parse_key("key", *argv);
+ } else if (!matches(*argv, "nokey")) {
+ iflags &= ~GRE_KEY;
+ oflags &= ~GRE_KEY;
+ ikey = okey = 0;
+ } else if (!matches(*argv, "ikey")) {
+ NEXT_ARG();
+ iflags |= GRE_KEY;
+ ikey = tnl_parse_key("ikey", *argv);
+ } else if (!matches(*argv, "noikey")) {
+ iflags &= ~GRE_KEY;
+ ikey = 0;
+ } else if (!matches(*argv, "okey")) {
+ NEXT_ARG();
+ oflags |= GRE_KEY;
+ okey = tnl_parse_key("okey", *argv);
+ } else if (!matches(*argv, "nookey")) {
+ oflags &= ~GRE_KEY;
+ okey = 0;
+ } else if (!matches(*argv, "seq")) {
+ iflags |= GRE_SEQ;
+ oflags |= GRE_SEQ;
+ } else if (!matches(*argv, "noseq")) {
+ iflags &= ~GRE_SEQ;
+ oflags &= ~GRE_SEQ;
+ } else if (!matches(*argv, "iseq")) {
+ iflags |= GRE_SEQ;
+ } else if (!matches(*argv, "noiseq")) {
+ iflags &= ~GRE_SEQ;
+ } else if (!matches(*argv, "oseq")) {
+ oflags |= GRE_SEQ;
+ } else if (!matches(*argv, "nooseq")) {
+ oflags &= ~GRE_SEQ;
+ } else if (!matches(*argv, "csum")) {
+ iflags |= GRE_CSUM;
+ oflags |= GRE_CSUM;
+ } else if (!matches(*argv, "nocsum")) {
+ iflags &= ~GRE_CSUM;
+ oflags &= ~GRE_CSUM;
+ } else if (!matches(*argv, "icsum")) {
+ iflags |= GRE_CSUM;
+ } else if (!matches(*argv, "noicsum")) {
+ iflags &= ~GRE_CSUM;
+ } else if (!matches(*argv, "ocsum")) {
+ oflags |= GRE_CSUM;
+ } else if (!matches(*argv, "noocsum")) {
+ oflags &= ~GRE_CSUM;
+ } else if (!matches(*argv, "nopmtudisc")) {
+ pmtudisc = 0;
+ } else if (!matches(*argv, "pmtudisc")) {
+ pmtudisc = 1;
+ } else if (!matches(*argv, "remote")) {
+ NEXT_ARG();
+ get_addr(&daddr, *argv, AF_INET);
+ } else if (!matches(*argv, "local")) {
+ NEXT_ARG();
+ get_addr(&saddr, *argv, AF_INET);
+ } else if (!matches(*argv, "dev")) {
+ NEXT_ARG();
+ link = ll_name_to_index(*argv);
+ if (!link)
+ exit(nodev(*argv));
+ } else if (!matches(*argv, "ttl") ||
+ !matches(*argv, "hoplimit") ||
+ !matches(*argv, "hlim")) {
+ NEXT_ARG();
+ if (strcmp(*argv, "inherit") != 0) {
+ if (get_u8(&ttl, *argv, 0))
+ invarg("invalid TTL\n", *argv);
+ } else
+ ttl = 0;
+ } else if (!matches(*argv, "tos") ||
+ !matches(*argv, "tclass") ||
+ !matches(*argv, "dsfield")) {
+ __u32 uval;
+
+ NEXT_ARG();
+ if (strcmp(*argv, "inherit") != 0) {
+ if (rtnl_dsfield_a2n(&uval, *argv))
+ invarg("bad TOS value", *argv);
+ tos = uval;
+ } else
+ tos = 1;
+ } else if (strcmp(*argv, "noencap") == 0) {
+ encaptype = TUNNEL_ENCAP_NONE;
+ } else if (strcmp(*argv, "encap") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "fou") == 0)
+ encaptype = TUNNEL_ENCAP_FOU;
+ else if (strcmp(*argv, "gue") == 0)
+ encaptype = TUNNEL_ENCAP_GUE;
+ else if (strcmp(*argv, "none") == 0)
+ encaptype = TUNNEL_ENCAP_NONE;
+ else
+ invarg("Invalid encap type.", *argv);
+ } else if (strcmp(*argv, "encap-sport") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "auto") == 0)
+ encapsport = 0;
+ else if (get_u16(&encapsport, *argv, 0))
+ invarg("Invalid source port.", *argv);
+ } else if (strcmp(*argv, "encap-dport") == 0) {
+ NEXT_ARG();
+ if (get_u16(&encapdport, *argv, 0))
+ invarg("Invalid destination port.", *argv);
+ } else if (strcmp(*argv, "encap-csum") == 0) {
+ encapflags |= TUNNEL_ENCAP_FLAG_CSUM;
+ } else if (strcmp(*argv, "noencap-csum") == 0) {
+ encapflags &= ~TUNNEL_ENCAP_FLAG_CSUM;
+ } else if (strcmp(*argv, "encap-udp6-csum") == 0) {
+ encapflags |= TUNNEL_ENCAP_FLAG_CSUM6;
+ } else if (strcmp(*argv, "noencap-udp6-csum") == 0) {
+ encapflags &= ~TUNNEL_ENCAP_FLAG_CSUM6;
+ } else if (strcmp(*argv, "encap-remcsum") == 0) {
+ encapflags |= TUNNEL_ENCAP_FLAG_REMCSUM;
+ } else if (strcmp(*argv, "noencap-remcsum") == 0) {
+ encapflags &= ~TUNNEL_ENCAP_FLAG_REMCSUM;
+ } else if (strcmp(*argv, "external") == 0) {
+ metadata = 1;
+ } else if (strcmp(*argv, "ignore-df") == 0) {
+ ignore_df = 1;
+ } else if (strcmp(*argv, "noignore-df") == 0) {
+ /*
+ *only the lsb is significant, use 2 for presence
+ */
+ ignore_df = 2;
+ } else if (strcmp(*argv, "fwmark") == 0) {
+ NEXT_ARG();
+ if (get_u32(&fwmark, *argv, 0))
+ invarg("invalid fwmark\n", *argv);
+ } else if (is_erspan && strcmp(*argv, "erspan") == 0) {
+ NEXT_ARG();
+ if (get_u32(&erspan_idx, *argv, 0))
+ invarg("invalid erspan index\n", *argv);
+ if (erspan_idx & ~((1<<20) - 1) || erspan_idx == 0)
+ invarg("erspan index must be > 0 and <= 20-bit\n", *argv);
+ } else if (is_erspan && strcmp(*argv, "erspan_ver") == 0) {
+ NEXT_ARG();
+ if (get_u8(&erspan_ver, *argv, 0))
+ invarg("invalid erspan version\n", *argv);
+ if (erspan_ver > 2)
+ invarg("erspan version must be 0/1/2\n", *argv);
+ } else if (is_erspan && strcmp(*argv, "erspan_dir") == 0) {
+ NEXT_ARG();
+ if (matches(*argv, "ingress") == 0)
+ erspan_dir = 0;
+ else if (matches(*argv, "egress") == 0)
+ erspan_dir = 1;
+ else
+ invarg("Invalid erspan direction.", *argv);
+ } else if (is_erspan && strcmp(*argv, "erspan_hwid") == 0) {
+ NEXT_ARG();
+ if (get_u16(&erspan_hwid, *argv, 0))
+ invarg("invalid erspan hwid\n", *argv);
+ } else {
+ gre_print_help(lu, argc, argv, stderr);
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (is_addrtype_inet_multi(&daddr)) {
+ if (!ikey) {
+ ikey = daddr.data[0];
+ iflags |= GRE_KEY;
+ }
+ if (!okey) {
+ okey = daddr.data[0];
+ oflags |= GRE_KEY;
+ }
+ if (!is_addrtype_inet_not_unspec(&saddr)) {
+ fprintf(stderr,
+ "A broadcast tunnel requires a source address.\n");
+ return -1;
+ }
+ }
+
+ if (metadata) {
+ addattr_l(n, 1024, IFLA_GRE_COLLECT_METADATA, NULL, 0);
+ return 0;
+ }
+
+ addattr32(n, 1024, IFLA_GRE_IKEY, ikey);
+ addattr32(n, 1024, IFLA_GRE_OKEY, okey);
+ addattr_l(n, 1024, IFLA_GRE_IFLAGS, &iflags, 2);
+ addattr_l(n, 1024, IFLA_GRE_OFLAGS, &oflags, 2);
+ if (is_addrtype_inet_not_unspec(&saddr))
+ addattr_l(n, 1024, IFLA_GRE_LOCAL, saddr.data, saddr.bytelen);
+ if (is_addrtype_inet_not_unspec(&daddr))
+ addattr_l(n, 1024, IFLA_GRE_REMOTE, daddr.data, daddr.bytelen);
+ addattr_l(n, 1024, IFLA_GRE_PMTUDISC, &pmtudisc, 1);
+ if (ignore_df)
+ addattr8(n, 1024, IFLA_GRE_IGNORE_DF, ignore_df & 1);
+ addattr_l(n, 1024, IFLA_GRE_TOS, &tos, 1);
+ if (link)
+ addattr32(n, 1024, IFLA_GRE_LINK, link);
+ addattr_l(n, 1024, IFLA_GRE_TTL, &ttl, 1);
+ addattr32(n, 1024, IFLA_GRE_FWMARK, fwmark);
+ if (is_erspan) {
+ addattr8(n, 1024, IFLA_GRE_ERSPAN_VER, erspan_ver);
+ if (erspan_ver == 1 && erspan_idx != 0) {
+ addattr32(n, 1024, IFLA_GRE_ERSPAN_INDEX, erspan_idx);
+ } else if (erspan_ver == 2) {
+ addattr8(n, 1024, IFLA_GRE_ERSPAN_DIR, erspan_dir);
+ addattr16(n, 1024, IFLA_GRE_ERSPAN_HWID, erspan_hwid);
+ }
+ }
+ addattr16(n, 1024, IFLA_GRE_ENCAP_TYPE, encaptype);
+ addattr16(n, 1024, IFLA_GRE_ENCAP_FLAGS, encapflags);
+ addattr16(n, 1024, IFLA_GRE_ENCAP_SPORT, htons(encapsport));
+ addattr16(n, 1024, IFLA_GRE_ENCAP_DPORT, htons(encapdport));
+
+ return 0;
+
+get_failed:
+ fprintf(stderr, "Failed to get existing tunnel info.\n");
+ free(answer);
+ return -1;
+}
+
+static void gre_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ char s2[64];
+ __u16 iflags = 0;
+ __u16 oflags = 0;
+ __u8 ttl = 0;
+ __u8 tos = 0;
+
+ if (!tb)
+ return;
+
+ if (tb[IFLA_GRE_COLLECT_METADATA]) {
+ print_bool(PRINT_ANY, "external", "external ", true);
+ }
+
+ tnl_print_endpoint("remote", tb[IFLA_GRE_REMOTE], AF_INET);
+ tnl_print_endpoint("local", tb[IFLA_GRE_LOCAL], AF_INET);
+
+ if (tb[IFLA_GRE_LINK]) {
+ __u32 link = rta_getattr_u32(tb[IFLA_GRE_LINK]);
+
+ if (link) {
+ print_string(PRINT_ANY, "link", "dev %s ",
+ ll_index_to_name(link));
+ }
+ }
+
+ if (tb[IFLA_GRE_TTL])
+ ttl = rta_getattr_u8(tb[IFLA_GRE_TTL]);
+ if (is_json_context() || ttl)
+ print_uint(PRINT_ANY, "ttl", "ttl %u ", ttl);
+ else
+ print_string(PRINT_FP, NULL, "ttl %s ", "inherit");
+
+ if (tb[IFLA_GRE_TOS])
+ tos = rta_getattr_u8(tb[IFLA_GRE_TOS]);
+ if (tos) {
+ if (is_json_context() || tos != 1)
+ print_0xhex(PRINT_ANY, "tos", "tos %#llx ", tos);
+ else
+ print_string(PRINT_FP, NULL, "tos %s ", "inherit");
+ }
+
+ if (tb[IFLA_GRE_PMTUDISC]) {
+ if (!rta_getattr_u8(tb[IFLA_GRE_PMTUDISC]))
+ print_bool(PRINT_ANY, "pmtudisc", "nopmtudisc ", false);
+ else
+ print_bool(PRINT_JSON, "pmtudisc", NULL, true);
+ }
+
+ if (tb[IFLA_GRE_IGNORE_DF] && rta_getattr_u8(tb[IFLA_GRE_IGNORE_DF]))
+ print_bool(PRINT_ANY, "ignore_df", "ignore-df ", true);
+
+ if (tb[IFLA_GRE_IFLAGS])
+ iflags = rta_getattr_u16(tb[IFLA_GRE_IFLAGS]);
+
+ if (tb[IFLA_GRE_OFLAGS])
+ oflags = rta_getattr_u16(tb[IFLA_GRE_OFLAGS]);
+
+ if ((iflags & GRE_KEY) && tb[IFLA_GRE_IKEY]) {
+ inet_ntop(AF_INET, RTA_DATA(tb[IFLA_GRE_IKEY]), s2, sizeof(s2));
+ print_string(PRINT_ANY, "ikey", "ikey %s ", s2);
+ }
+
+ if ((oflags & GRE_KEY) && tb[IFLA_GRE_OKEY]) {
+ inet_ntop(AF_INET, RTA_DATA(tb[IFLA_GRE_OKEY]), s2, sizeof(s2));
+ print_string(PRINT_ANY, "okey", "okey %s ", s2);
+ }
+
+ if (iflags & GRE_SEQ)
+ print_bool(PRINT_ANY, "iseq", "iseq ", true);
+ if (oflags & GRE_SEQ)
+ print_bool(PRINT_ANY, "oseq", "oseq ", true);
+ if (iflags & GRE_CSUM)
+ print_bool(PRINT_ANY, "icsum", "icsum ", true);
+ if (oflags & GRE_CSUM)
+ print_bool(PRINT_ANY, "ocsum", "ocsum ", true);
+
+ if (tb[IFLA_GRE_FWMARK]) {
+ __u32 fwmark = rta_getattr_u32(tb[IFLA_GRE_FWMARK]);
+
+ if (fwmark) {
+ print_0xhex(PRINT_ANY,
+ "fwmark", "fwmark %#llx ", fwmark);
+ }
+ }
+
+ if (tb[IFLA_GRE_ERSPAN_INDEX]) {
+ __u32 erspan_idx = rta_getattr_u32(tb[IFLA_GRE_ERSPAN_INDEX]);
+
+ print_uint(PRINT_ANY,
+ "erspan_index", "erspan_index %u ", erspan_idx);
+ }
+
+ if (tb[IFLA_GRE_ERSPAN_VER]) {
+ __u8 erspan_ver = rta_getattr_u8(tb[IFLA_GRE_ERSPAN_VER]);
+
+ print_uint(PRINT_ANY,
+ "erspan_ver", "erspan_ver %u ", erspan_ver);
+ }
+
+ if (tb[IFLA_GRE_ERSPAN_DIR]) {
+ __u8 erspan_dir = rta_getattr_u8(tb[IFLA_GRE_ERSPAN_DIR]);
+
+ if (erspan_dir == 0)
+ print_string(PRINT_ANY, "erspan_dir",
+ "erspan_dir %s ", "ingress");
+ else
+ print_string(PRINT_ANY, "erspan_dir",
+ "erspan_dir %s ", "egress");
+ }
+
+ if (tb[IFLA_GRE_ERSPAN_HWID]) {
+ __u16 erspan_hwid = rta_getattr_u16(tb[IFLA_GRE_ERSPAN_HWID]);
+
+ print_0xhex(PRINT_ANY,
+ "erspan_hwid", "erspan_hwid %#llx ", erspan_hwid);
+ }
+
+ tnl_print_encap(tb,
+ IFLA_GRE_ENCAP_TYPE,
+ IFLA_GRE_ENCAP_FLAGS,
+ IFLA_GRE_ENCAP_SPORT,
+ IFLA_GRE_ENCAP_DPORT);
+}
+
+struct link_util gre_link_util = {
+ .id = "gre",
+ .maxattr = IFLA_GRE_MAX,
+ .parse_opt = gre_parse_opt,
+ .print_opt = gre_print_opt,
+ .print_help = gre_print_help,
+};
+
+struct link_util gretap_link_util = {
+ .id = "gretap",
+ .maxattr = IFLA_GRE_MAX,
+ .parse_opt = gre_parse_opt,
+ .print_opt = gre_print_opt,
+ .print_help = gre_print_help,
+};
+
+struct link_util erspan_link_util = {
+ .id = "erspan",
+ .maxattr = IFLA_GRE_MAX,
+ .parse_opt = gre_parse_opt,
+ .print_opt = gre_print_opt,
+ .print_help = gre_print_help,
+};
diff --git a/ip/link_gre6.c b/ip/link_gre6.c
new file mode 100644
index 0000000..fc3e35f
--- /dev/null
+++ b/ip/link_gre6.c
@@ -0,0 +1,634 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * link_gre6.c gre driver module
+ *
+ * Authors: Dmitry Kozlov <xeb@mail.ru>
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include <linux/ip6_tunnel.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+#define IP6_FLOWINFO_TCLASS htonl(0x0FF00000)
+#define IP6_FLOWINFO_FLOWLABEL htonl(0x000FFFFF)
+
+#define DEFAULT_TNL_HOP_LIMIT (64)
+
+static void gre_print_help(struct link_util *lu, int argc, char **argv, FILE *f)
+{
+ fprintf(f,
+ "Usage: ... %-9s [ remote ADDR ]\n"
+ " [ local ADDR ]\n"
+ " [ [no][i|o]seq ]\n"
+ " [ [i|o]key KEY | no[i|o]key ]\n"
+ " [ [no][i|o]csum ]\n"
+ " [ hoplimit TTL ]\n"
+ " [ encaplimit ELIM ]\n"
+ " [ tclass TCLASS ]\n"
+ " [ flowlabel FLOWLABEL ]\n"
+ " [ dscp inherit ]\n"
+ " [ dev PHYS_DEV ]\n"
+ " [ fwmark MARK ]\n"
+ " [ [no]allow-localremote ]\n"
+ " [ external ]\n"
+ " [ noencap ]\n"
+ " [ encap { fou | gue | none } ]\n"
+ " [ encap-sport PORT ]\n"
+ " [ encap-dport PORT ]\n"
+ " [ [no]encap-csum ]\n"
+ " [ [no]encap-csum6 ]\n"
+ " [ [no]encap-remcsum ]\n"
+ " [ erspan_ver version ]\n"
+ " [ erspan IDX ]\n"
+ " [ erspan_dir { ingress | egress } ]\n"
+ " [ erspan_hwid hwid ]\n"
+ "\n"
+ "Where: ADDR := IPV6_ADDRESS\n"
+ " TTL := { 0..255 } (default=%d)\n"
+ " KEY := { DOTTED_QUAD | NUMBER }\n"
+ " ELIM := { none | 0..255 }(default=%d)\n"
+ " TCLASS := { 0x0..0xff | inherit }\n"
+ " FLOWLABEL := { 0x0..0xfffff | inherit }\n"
+ " MARK := { 0x0..0xffffffff | inherit }\n",
+ lu->id,
+ DEFAULT_TNL_HOP_LIMIT, IPV6_DEFAULT_TNL_ENCAP_LIMIT);
+}
+
+static int gre_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ struct ifinfomsg *ifi = NLMSG_DATA(n);
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg i;
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(*ifi)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_GETLINK,
+ .i.ifi_family = preferred_family,
+ .i.ifi_index = ifi->ifi_index,
+ };
+ struct nlmsghdr *answer = NULL;
+ struct rtattr *tb[IFLA_MAX + 1];
+ struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+ struct rtattr *greinfo[IFLA_GRE_MAX + 1];
+ int len;
+ __u16 iflags = 0;
+ __u16 oflags = 0;
+ __be32 ikey = 0;
+ __be32 okey = 0;
+ inet_prefix saddr, daddr;
+ __u8 hop_limit = DEFAULT_TNL_HOP_LIMIT;
+ __u8 encap_limit = IPV6_DEFAULT_TNL_ENCAP_LIMIT;
+ __u32 flowinfo = 0;
+ __u32 flags = 0;
+ __u32 link = 0;
+ __u16 encaptype = 0;
+ __u16 encapflags = TUNNEL_ENCAP_FLAG_CSUM6;
+ __u16 encapsport = 0;
+ __u16 encapdport = 0;
+ __u8 metadata = 0;
+ __u32 fwmark = 0;
+ __u32 erspan_idx = 0;
+ __u8 erspan_ver = 1;
+ __u8 erspan_dir = 0;
+ __u16 erspan_hwid = 0;
+
+ inet_prefix_reset(&saddr);
+ inet_prefix_reset(&daddr);
+
+ if (!(n->nlmsg_flags & NLM_F_CREATE)) {
+ const struct rtattr *rta;
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0)
+ goto get_failed;
+
+ len = answer->nlmsg_len;
+ len -= NLMSG_LENGTH(sizeof(*ifi));
+ if (len < 0)
+ goto get_failed;
+
+ parse_rtattr(tb, IFLA_MAX, IFLA_RTA(NLMSG_DATA(answer)), len);
+
+ if (!tb[IFLA_LINKINFO])
+ goto get_failed;
+
+ parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+ if (!linkinfo[IFLA_INFO_DATA])
+ goto get_failed;
+
+ parse_rtattr_nested(greinfo, IFLA_GRE_MAX,
+ linkinfo[IFLA_INFO_DATA]);
+
+ rta = greinfo[IFLA_GRE_LOCAL];
+ if (rta && get_addr_rta(&saddr, rta, AF_INET6))
+ goto get_failed;
+
+ rta = greinfo[IFLA_GRE_REMOTE];
+ if (rta && get_addr_rta(&daddr, rta, AF_INET6))
+ goto get_failed;
+
+ if (greinfo[IFLA_GRE_IKEY])
+ ikey = rta_getattr_u32(greinfo[IFLA_GRE_IKEY]);
+
+ if (greinfo[IFLA_GRE_OKEY])
+ okey = rta_getattr_u32(greinfo[IFLA_GRE_OKEY]);
+
+ if (greinfo[IFLA_GRE_IFLAGS])
+ iflags = rta_getattr_u16(greinfo[IFLA_GRE_IFLAGS]);
+
+ if (greinfo[IFLA_GRE_OFLAGS])
+ oflags = rta_getattr_u16(greinfo[IFLA_GRE_OFLAGS]);
+
+ if (greinfo[IFLA_GRE_TTL])
+ hop_limit = rta_getattr_u8(greinfo[IFLA_GRE_TTL]);
+
+ if (greinfo[IFLA_GRE_LINK])
+ link = rta_getattr_u32(greinfo[IFLA_GRE_LINK]);
+
+ if (greinfo[IFLA_GRE_ENCAP_LIMIT])
+ encap_limit = rta_getattr_u8(greinfo[IFLA_GRE_ENCAP_LIMIT]);
+
+ if (greinfo[IFLA_GRE_FLOWINFO])
+ flowinfo = rta_getattr_u32(greinfo[IFLA_GRE_FLOWINFO]);
+
+ if (greinfo[IFLA_GRE_FLAGS])
+ flags = rta_getattr_u32(greinfo[IFLA_GRE_FLAGS]);
+
+ if (greinfo[IFLA_GRE_ENCAP_TYPE])
+ encaptype = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_TYPE]);
+
+ if (greinfo[IFLA_GRE_ENCAP_FLAGS])
+ encapflags = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_FLAGS]);
+
+ if (greinfo[IFLA_GRE_ENCAP_SPORT])
+ encapsport = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_SPORT]);
+
+ if (greinfo[IFLA_GRE_ENCAP_DPORT])
+ encapdport = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_DPORT]);
+
+ if (greinfo[IFLA_GRE_COLLECT_METADATA])
+ metadata = 1;
+
+ if (greinfo[IFLA_GRE_FWMARK])
+ fwmark = rta_getattr_u32(greinfo[IFLA_GRE_FWMARK]);
+
+ if (greinfo[IFLA_GRE_ERSPAN_INDEX])
+ erspan_idx = rta_getattr_u32(greinfo[IFLA_GRE_ERSPAN_INDEX]);
+
+ if (greinfo[IFLA_GRE_ERSPAN_VER])
+ erspan_ver = rta_getattr_u8(greinfo[IFLA_GRE_ERSPAN_VER]);
+
+ if (greinfo[IFLA_GRE_ERSPAN_DIR])
+ erspan_dir = rta_getattr_u8(greinfo[IFLA_GRE_ERSPAN_DIR]);
+
+ if (greinfo[IFLA_GRE_ERSPAN_HWID])
+ erspan_hwid = rta_getattr_u16(greinfo[IFLA_GRE_ERSPAN_HWID]);
+
+ free(answer);
+ }
+
+ while (argc > 0) {
+ if (!matches(*argv, "key")) {
+ NEXT_ARG();
+ iflags |= GRE_KEY;
+ oflags |= GRE_KEY;
+ ikey = okey = tnl_parse_key("key", *argv);
+ } else if (!matches(*argv, "nokey")) {
+ iflags &= ~GRE_KEY;
+ oflags &= ~GRE_KEY;
+ ikey = okey = 0;
+ } else if (!matches(*argv, "ikey")) {
+ NEXT_ARG();
+ iflags |= GRE_KEY;
+ ikey = tnl_parse_key("ikey", *argv);
+ } else if (!matches(*argv, "noikey")) {
+ iflags &= ~GRE_KEY;
+ ikey = 0;
+ } else if (!matches(*argv, "okey")) {
+ NEXT_ARG();
+ oflags |= GRE_KEY;
+ okey = tnl_parse_key("okey", *argv);
+ } else if (!matches(*argv, "nookey")) {
+ oflags &= ~GRE_KEY;
+ okey = 0;
+ } else if (!matches(*argv, "seq")) {
+ iflags |= GRE_SEQ;
+ oflags |= GRE_SEQ;
+ } else if (!matches(*argv, "noseq")) {
+ iflags &= ~GRE_SEQ;
+ oflags &= ~GRE_SEQ;
+ } else if (!matches(*argv, "iseq")) {
+ iflags |= GRE_SEQ;
+ } else if (!matches(*argv, "noiseq")) {
+ iflags &= ~GRE_SEQ;
+ } else if (!matches(*argv, "oseq")) {
+ oflags |= GRE_SEQ;
+ } else if (!matches(*argv, "nooseq")) {
+ oflags &= ~GRE_SEQ;
+ } else if (!matches(*argv, "csum")) {
+ iflags |= GRE_CSUM;
+ oflags |= GRE_CSUM;
+ } else if (!matches(*argv, "nocsum")) {
+ iflags &= ~GRE_CSUM;
+ oflags &= ~GRE_CSUM;
+ } else if (!matches(*argv, "icsum")) {
+ iflags |= GRE_CSUM;
+ } else if (!matches(*argv, "noicsum")) {
+ iflags &= ~GRE_CSUM;
+ } else if (!matches(*argv, "ocsum")) {
+ oflags |= GRE_CSUM;
+ } else if (!matches(*argv, "noocsum")) {
+ oflags &= ~GRE_CSUM;
+ } else if (!matches(*argv, "remote")) {
+ NEXT_ARG();
+ get_addr(&daddr, *argv, AF_INET6);
+ } else if (!matches(*argv, "local")) {
+ NEXT_ARG();
+ get_addr(&saddr, *argv, AF_INET6);
+ } else if (!matches(*argv, "dev")) {
+ NEXT_ARG();
+ link = ll_name_to_index(*argv);
+ if (!link)
+ exit(nodev(*argv));
+ } else if (!matches(*argv, "ttl") ||
+ !matches(*argv, "hoplimit") ||
+ !matches(*argv, "hlim")) {
+ NEXT_ARG();
+ if (strcmp(*argv, "inherit") != 0) {
+ if (get_u8(&hop_limit, *argv, 0))
+ invarg("invalid HLIM\n", *argv);
+ } else
+ hop_limit = 0;
+ } else if (!matches(*argv, "tos") ||
+ !matches(*argv, "tclass") ||
+ !matches(*argv, "dsfield")) {
+ __u8 uval;
+
+ NEXT_ARG();
+ flowinfo &= ~IP6_FLOWINFO_TCLASS;
+ if (strcmp(*argv, "inherit") == 0)
+ flags |= IP6_TNL_F_USE_ORIG_TCLASS;
+ else {
+ if (get_u8(&uval, *argv, 16))
+ invarg("invalid TClass", *argv);
+ flowinfo |= htonl((__u32)uval << 20) & IP6_FLOWINFO_TCLASS;
+ flags &= ~IP6_TNL_F_USE_ORIG_TCLASS;
+ }
+ } else if (strcmp(*argv, "flowlabel") == 0 ||
+ strcmp(*argv, "fl") == 0) {
+ __u32 uval;
+
+ NEXT_ARG();
+ flowinfo &= ~IP6_FLOWINFO_FLOWLABEL;
+ if (strcmp(*argv, "inherit") == 0)
+ flags |= IP6_TNL_F_USE_ORIG_FLOWLABEL;
+ else {
+ if (get_u32(&uval, *argv, 16))
+ invarg("invalid Flowlabel", *argv);
+ if (uval > 0xFFFFF)
+ invarg("invalid Flowlabel", *argv);
+ flowinfo |= htonl(uval) & IP6_FLOWINFO_FLOWLABEL;
+ flags &= ~IP6_TNL_F_USE_ORIG_FLOWLABEL;
+ }
+ } else if (strcmp(*argv, "dscp") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "inherit") != 0)
+ invarg("not inherit", *argv);
+ flags |= IP6_TNL_F_RCV_DSCP_COPY;
+ } else if (strcmp(*argv, "noencap") == 0) {
+ encaptype = TUNNEL_ENCAP_NONE;
+ } else if (strcmp(*argv, "encap") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "fou") == 0)
+ encaptype = TUNNEL_ENCAP_FOU;
+ else if (strcmp(*argv, "gue") == 0)
+ encaptype = TUNNEL_ENCAP_GUE;
+ else if (strcmp(*argv, "none") == 0)
+ encaptype = TUNNEL_ENCAP_NONE;
+ else
+ invarg("Invalid encap type.", *argv);
+ } else if (strcmp(*argv, "encap-sport") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "auto") == 0)
+ encapsport = 0;
+ else if (get_u16(&encapsport, *argv, 0))
+ invarg("Invalid source port.", *argv);
+ } else if (strcmp(*argv, "encap-dport") == 0) {
+ NEXT_ARG();
+ if (get_u16(&encapdport, *argv, 0))
+ invarg("Invalid destination port.", *argv);
+ } else if (strcmp(*argv, "encap-csum") == 0) {
+ encapflags |= TUNNEL_ENCAP_FLAG_CSUM;
+ } else if (strcmp(*argv, "noencap-csum") == 0) {
+ encapflags &= ~TUNNEL_ENCAP_FLAG_CSUM;
+ } else if (strcmp(*argv, "encap-udp6-csum") == 0) {
+ encapflags |= TUNNEL_ENCAP_FLAG_CSUM6;
+ } else if (strcmp(*argv, "noencap-udp6-csum") == 0) {
+ encapflags &= ~TUNNEL_ENCAP_FLAG_CSUM6;
+ } else if (strcmp(*argv, "encap-remcsum") == 0) {
+ encapflags |= TUNNEL_ENCAP_FLAG_REMCSUM;
+ } else if (strcmp(*argv, "noencap-remcsum") == 0) {
+ encapflags &= ~TUNNEL_ENCAP_FLAG_REMCSUM;
+ } else if (strcmp(*argv, "external") == 0) {
+ metadata = 1;
+ } else if (strcmp(*argv, "fwmark") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "inherit") == 0) {
+ flags |= IP6_TNL_F_USE_ORIG_FWMARK;
+ fwmark = 0;
+ } else {
+ if (get_u32(&fwmark, *argv, 0))
+ invarg("invalid fwmark\n", *argv);
+ flags &= ~IP6_TNL_F_USE_ORIG_FWMARK;
+ }
+ } else if (strcmp(*argv, "allow-localremote") == 0) {
+ flags |= IP6_TNL_F_ALLOW_LOCAL_REMOTE;
+ } else if (strcmp(*argv, "noallow-localremote") == 0) {
+ flags &= ~IP6_TNL_F_ALLOW_LOCAL_REMOTE;
+ } else if (strcmp(*argv, "encaplimit") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "none") == 0) {
+ flags |= IP6_TNL_F_IGN_ENCAP_LIMIT;
+ } else {
+ __u8 uval;
+
+ if (get_u8(&uval, *argv, 0))
+ invarg("invalid ELIM", *argv);
+ encap_limit = uval;
+ flags &= ~IP6_TNL_F_IGN_ENCAP_LIMIT;
+ }
+ } else if (strcmp(*argv, "erspan") == 0) {
+ NEXT_ARG();
+ if (get_u32(&erspan_idx, *argv, 0))
+ invarg("invalid erspan index\n", *argv);
+ if (erspan_idx & ~((1<<20) - 1) || erspan_idx == 0)
+ invarg("erspan index must be > 0 and <= 20-bit\n", *argv);
+ } else if (strcmp(*argv, "erspan_ver") == 0) {
+ NEXT_ARG();
+ if (get_u8(&erspan_ver, *argv, 0))
+ invarg("invalid erspan version\n", *argv);
+ if (erspan_ver > 2)
+ invarg("erspan version must be 0/1/2\n", *argv);
+ } else if (strcmp(*argv, "erspan_dir") == 0) {
+ NEXT_ARG();
+ if (matches(*argv, "ingress") == 0)
+ erspan_dir = 0;
+ else if (matches(*argv, "egress") == 0)
+ erspan_dir = 1;
+ else
+ invarg("Invalid erspan direction.", *argv);
+ } else if (strcmp(*argv, "erspan_hwid") == 0) {
+ NEXT_ARG();
+ if (get_u16(&erspan_hwid, *argv, 0))
+ invarg("invalid erspan hwid\n", *argv);
+ } else {
+ gre_print_help(lu, argc, argv, stderr);
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (metadata) {
+ addattr_l(n, 1024, IFLA_GRE_COLLECT_METADATA, NULL, 0);
+ return 0;
+ }
+
+ addattr32(n, 1024, IFLA_GRE_IKEY, ikey);
+ addattr32(n, 1024, IFLA_GRE_OKEY, okey);
+ addattr_l(n, 1024, IFLA_GRE_IFLAGS, &iflags, 2);
+ addattr_l(n, 1024, IFLA_GRE_OFLAGS, &oflags, 2);
+ if (is_addrtype_inet_not_unspec(&saddr))
+ addattr_l(n, 1024, IFLA_GRE_LOCAL, saddr.data, saddr.bytelen);
+ if (is_addrtype_inet_not_unspec(&daddr))
+ addattr_l(n, 1024, IFLA_GRE_REMOTE, daddr.data, daddr.bytelen);
+ if (link)
+ addattr32(n, 1024, IFLA_GRE_LINK, link);
+ addattr_l(n, 1024, IFLA_GRE_TTL, &hop_limit, 1);
+ addattr_l(n, 1024, IFLA_GRE_ENCAP_LIMIT, &encap_limit, 1);
+ addattr_l(n, 1024, IFLA_GRE_FLOWINFO, &flowinfo, 4);
+ addattr32(n, 1024, IFLA_GRE_FLAGS, flags);
+ addattr32(n, 1024, IFLA_GRE_FWMARK, fwmark);
+ if (erspan_ver <= 2) {
+ addattr8(n, 1024, IFLA_GRE_ERSPAN_VER, erspan_ver);
+ if (erspan_ver == 1 && erspan_idx != 0) {
+ addattr32(n, 1024, IFLA_GRE_ERSPAN_INDEX, erspan_idx);
+ } else if (erspan_ver == 2) {
+ addattr8(n, 1024, IFLA_GRE_ERSPAN_DIR, erspan_dir);
+ addattr16(n, 1024, IFLA_GRE_ERSPAN_HWID, erspan_hwid);
+ }
+ }
+ addattr16(n, 1024, IFLA_GRE_ENCAP_TYPE, encaptype);
+ addattr16(n, 1024, IFLA_GRE_ENCAP_FLAGS, encapflags);
+ addattr16(n, 1024, IFLA_GRE_ENCAP_SPORT, htons(encapsport));
+ addattr16(n, 1024, IFLA_GRE_ENCAP_DPORT, htons(encapdport));
+
+ return 0;
+
+get_failed:
+ fprintf(stderr, "Failed to get existing tunnel info.\n");
+ free(answer);
+ return -1;
+}
+
+static void gre_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ char s2[64];
+ __u16 iflags = 0;
+ __u16 oflags = 0;
+ __u32 flags = 0;
+ __u32 flowinfo = 0;
+ __u8 ttl = 0;
+
+ if (!tb)
+ return;
+
+ if (tb[IFLA_GRE_COLLECT_METADATA]) {
+ print_bool(PRINT_ANY, "external", "external ", true);
+ }
+
+ if (tb[IFLA_GRE_FLAGS])
+ flags = rta_getattr_u32(tb[IFLA_GRE_FLAGS]);
+
+ if (tb[IFLA_GRE_FLOWINFO])
+ flowinfo = rta_getattr_u32(tb[IFLA_GRE_FLOWINFO]);
+
+ tnl_print_endpoint("remote", tb[IFLA_GRE_REMOTE], AF_INET6);
+ tnl_print_endpoint("local", tb[IFLA_GRE_LOCAL], AF_INET6);
+
+ if (tb[IFLA_GRE_LINK]) {
+ __u32 link = rta_getattr_u32(tb[IFLA_GRE_LINK]);
+
+ if (link) {
+ print_string(PRINT_ANY, "link", "dev %s ",
+ ll_index_to_name(link));
+ }
+ }
+
+ if (tb[IFLA_GRE_TTL])
+ ttl = rta_getattr_u8(tb[IFLA_GRE_TTL]);
+ if (is_json_context() || ttl)
+ print_uint(PRINT_ANY, "ttl", "hoplimit %u ", ttl);
+ else
+ print_string(PRINT_FP, NULL, "hoplimit %s ", "inherit");
+
+ if (flags & IP6_TNL_F_IGN_ENCAP_LIMIT) {
+ print_bool(PRINT_ANY,
+ "ip6_tnl_f_ign_encap_limit",
+ "encaplimit none ",
+ true);
+ } else if (tb[IFLA_GRE_ENCAP_LIMIT]) {
+ __u8 val = rta_getattr_u8(tb[IFLA_GRE_ENCAP_LIMIT]);
+
+ print_uint(PRINT_ANY, "encap_limit", "encaplimit %u ", val);
+ }
+
+ if (flags & IP6_TNL_F_USE_ORIG_TCLASS) {
+ print_bool(PRINT_ANY,
+ "ip6_tnl_f_use_orig_tclass",
+ "tclass inherit ",
+ true);
+ } else if (tb[IFLA_GRE_FLOWINFO]) {
+ __u32 val = ntohl(flowinfo & IP6_FLOWINFO_TCLASS) >> 20;
+
+ snprintf(s2, sizeof(s2), "0x%02x", val);
+ print_string(PRINT_ANY, "tclass", "tclass %s ", s2);
+ }
+
+ if (flags & IP6_TNL_F_USE_ORIG_FLOWLABEL) {
+ print_bool(PRINT_ANY,
+ "ip6_tnl_f_use_orig_flowlabel",
+ "flowlabel inherit ",
+ true);
+ } else if (tb[IFLA_GRE_FLOWINFO]) {
+ __u32 val = ntohl(flowinfo & IP6_FLOWINFO_FLOWLABEL);
+
+ snprintf(s2, sizeof(s2), "0x%05x", val);
+ print_string(PRINT_ANY, "flowlabel", "flowlabel %s ", s2);
+ }
+
+ if (flags & IP6_TNL_F_RCV_DSCP_COPY)
+ print_bool(PRINT_ANY,
+ "ip6_tnl_f_rcv_dscp_copy",
+ "dscp inherit ",
+ true);
+
+ if (tb[IFLA_GRE_IFLAGS])
+ iflags = rta_getattr_u16(tb[IFLA_GRE_IFLAGS]);
+
+ if (tb[IFLA_GRE_OFLAGS])
+ oflags = rta_getattr_u16(tb[IFLA_GRE_OFLAGS]);
+
+ if ((iflags & GRE_KEY) && tb[IFLA_GRE_IKEY]) {
+ inet_ntop(AF_INET, RTA_DATA(tb[IFLA_GRE_IKEY]), s2, sizeof(s2));
+ print_string(PRINT_ANY, "ikey", "ikey %s ", s2);
+ }
+
+ if ((oflags & GRE_KEY) && tb[IFLA_GRE_OKEY]) {
+ inet_ntop(AF_INET, RTA_DATA(tb[IFLA_GRE_OKEY]), s2, sizeof(s2));
+ print_string(PRINT_ANY, "okey", "okey %s ", s2);
+ }
+
+ if (iflags & GRE_SEQ)
+ print_bool(PRINT_ANY, "iseq", "iseq ", true);
+ if (oflags & GRE_SEQ)
+ print_bool(PRINT_ANY, "oseq", "oseq ", true);
+ if (iflags & GRE_CSUM)
+ print_bool(PRINT_ANY, "icsum", "icsum ", true);
+ if (oflags & GRE_CSUM)
+ print_bool(PRINT_ANY, "ocsum", "ocsum ", true);
+
+ if (flags & IP6_TNL_F_ALLOW_LOCAL_REMOTE)
+ print_bool(PRINT_ANY,
+ "ip6_tnl_f_allow_local_remote",
+ "allow-localremote ",
+ true);
+
+ if (flags & IP6_TNL_F_USE_ORIG_FWMARK) {
+ print_bool(PRINT_ANY,
+ "ip6_tnl_f_use_orig_fwmark",
+ "fwmark inherit ",
+ true);
+ } else if (tb[IFLA_GRE_FWMARK]) {
+ __u32 fwmark = rta_getattr_u32(tb[IFLA_GRE_FWMARK]);
+
+ if (fwmark) {
+ print_0xhex(PRINT_ANY,
+ "fwmark", "fwmark %#llx ", fwmark);
+ }
+ }
+
+ if (tb[IFLA_GRE_ERSPAN_INDEX]) {
+ __u32 erspan_idx = rta_getattr_u32(tb[IFLA_GRE_ERSPAN_INDEX]);
+
+ print_uint(PRINT_ANY,
+ "erspan_index", "erspan_index %u ", erspan_idx);
+ }
+
+ if (tb[IFLA_GRE_ERSPAN_VER]) {
+ __u8 erspan_ver = rta_getattr_u8(tb[IFLA_GRE_ERSPAN_VER]);
+
+ print_uint(PRINT_ANY,
+ "erspan_ver", "erspan_ver %u ", erspan_ver);
+ }
+
+ if (tb[IFLA_GRE_ERSPAN_DIR]) {
+ __u8 erspan_dir = rta_getattr_u8(tb[IFLA_GRE_ERSPAN_DIR]);
+
+ if (erspan_dir == 0)
+ print_string(PRINT_ANY, "erspan_dir",
+ "erspan_dir %s ", "ingress");
+ else
+ print_string(PRINT_ANY, "erspan_dir",
+ "erspan_dir %s ", "egress");
+ }
+
+ if (tb[IFLA_GRE_ERSPAN_HWID]) {
+ __u16 erspan_hwid = rta_getattr_u16(tb[IFLA_GRE_ERSPAN_HWID]);
+
+ print_0xhex(PRINT_ANY,
+ "erspan_hwid", "erspan_hwid %#llx ", erspan_hwid);
+ }
+
+ tnl_print_encap(tb,
+ IFLA_GRE_ENCAP_TYPE,
+ IFLA_GRE_ENCAP_FLAGS,
+ IFLA_GRE_ENCAP_SPORT,
+ IFLA_GRE_ENCAP_DPORT);
+}
+
+struct link_util ip6gre_link_util = {
+ .id = "ip6gre",
+ .maxattr = IFLA_GRE_MAX,
+ .parse_opt = gre_parse_opt,
+ .print_opt = gre_print_opt,
+ .print_help = gre_print_help,
+};
+
+struct link_util ip6gretap_link_util = {
+ .id = "ip6gretap",
+ .maxattr = IFLA_GRE_MAX,
+ .parse_opt = gre_parse_opt,
+ .print_opt = gre_print_opt,
+ .print_help = gre_print_help,
+};
+
+struct link_util ip6erspan_link_util = {
+ .id = "ip6erspan",
+ .maxattr = IFLA_GRE_MAX,
+ .parse_opt = gre_parse_opt,
+ .print_opt = gre_print_opt,
+ .print_help = gre_print_help,
+};
diff --git a/ip/link_ip6tnl.c b/ip/link_ip6tnl.c
new file mode 100644
index 0000000..c2a05ce
--- /dev/null
+++ b/ip/link_ip6tnl.c
@@ -0,0 +1,461 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * link_ip6tnl.c ip6tnl driver module
+ *
+ * Authors: Nicolas Dichtel <nicolas.dichtel@6wind.com>
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include <linux/ip6_tunnel.h>
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+#define IP6_FLOWINFO_TCLASS htonl(0x0FF00000)
+#define IP6_FLOWINFO_FLOWLABEL htonl(0x000FFFFF)
+
+#define DEFAULT_TNL_HOP_LIMIT (64)
+
+static void ip6tunnel_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ fprintf(f,
+ "Usage: ... %-6s [ remote ADDR ]\n"
+ " [ local ADDR ]\n"
+ " [ encaplimit ELIM ]\n"
+ " [ hoplimit HLIM ]\n"
+ " [ tclass TCLASS ]\n"
+ " [ flowlabel FLOWLABEL ]\n"
+ " [ dscp inherit ]\n"
+ " [ [no]allow-localremote ]\n"
+ " [ dev PHYS_DEV ]\n"
+ " [ fwmark MARK ]\n"
+ " [ external ]\n"
+ " [ noencap ]\n"
+ " [ encap { fou | gue | none } ]\n"
+ " [ encap-sport PORT ]\n"
+ " [ encap-dport PORT ]\n"
+ " [ [no]encap-csum ]\n"
+ " [ [no]encap-csum6 ]\n"
+ " [ [no]encap-remcsum ]\n"
+ " [ mode { ip6ip6 | ipip6 | any } ]\n"
+ "\n"
+ "Where: ADDR := IPV6_ADDRESS\n"
+ " ELIM := { none | 0..255 }(default=%d)\n"
+ " HLIM := 0..255 (default=%d)\n"
+ " TCLASS := { 0x0..0xff | inherit }\n"
+ " FLOWLABEL := { 0x0..0xfffff | inherit }\n"
+ " MARK := { 0x0..0xffffffff | inherit }\n",
+ lu->id,
+ IPV6_DEFAULT_TNL_ENCAP_LIMIT, DEFAULT_TNL_HOP_LIMIT);
+}
+
+static int ip6tunnel_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ struct ifinfomsg *ifi = NLMSG_DATA(n);
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg i;
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(*ifi)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_GETLINK,
+ .i.ifi_family = preferred_family,
+ .i.ifi_index = ifi->ifi_index,
+ };
+ struct nlmsghdr *answer = NULL;
+ struct rtattr *tb[IFLA_MAX + 1];
+ struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+ struct rtattr *iptuninfo[IFLA_IPTUN_MAX + 1];
+ int len;
+ inet_prefix saddr, daddr;
+ __u8 hop_limit = DEFAULT_TNL_HOP_LIMIT;
+ __u8 encap_limit = IPV6_DEFAULT_TNL_ENCAP_LIMIT;
+ __u32 flowinfo = 0;
+ __u32 flags = 0;
+ __u8 proto = 0;
+ __u32 link = 0;
+ __u16 encaptype = 0;
+ __u16 encapflags = TUNNEL_ENCAP_FLAG_CSUM6;
+ __u16 encapsport = 0;
+ __u16 encapdport = 0;
+ __u8 metadata = 0;
+ __u32 fwmark = 0;
+
+ inet_prefix_reset(&saddr);
+ inet_prefix_reset(&daddr);
+
+ if (!(n->nlmsg_flags & NLM_F_CREATE)) {
+ const struct rtattr *rta;
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0)
+ goto get_failed;
+
+ len = answer->nlmsg_len;
+ len -= NLMSG_LENGTH(sizeof(*ifi));
+ if (len < 0)
+ goto get_failed;
+
+ parse_rtattr(tb, IFLA_MAX, IFLA_RTA(NLMSG_DATA(answer)), len);
+
+ if (!tb[IFLA_LINKINFO])
+ goto get_failed;
+
+ parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+ if (!linkinfo[IFLA_INFO_DATA])
+ goto get_failed;
+
+ parse_rtattr_nested(iptuninfo, IFLA_IPTUN_MAX,
+ linkinfo[IFLA_INFO_DATA]);
+
+ rta = iptuninfo[IFLA_IPTUN_LOCAL];
+ if (rta && get_addr_rta(&saddr, rta, AF_INET6))
+ goto get_failed;
+
+ rta = iptuninfo[IFLA_IPTUN_REMOTE];
+ if (rta && get_addr_rta(&daddr, rta, AF_INET6))
+ goto get_failed;
+
+ if (iptuninfo[IFLA_IPTUN_TTL])
+ hop_limit = rta_getattr_u8(iptuninfo[IFLA_IPTUN_TTL]);
+
+ if (iptuninfo[IFLA_IPTUN_ENCAP_LIMIT])
+ encap_limit = rta_getattr_u8(iptuninfo[IFLA_IPTUN_ENCAP_LIMIT]);
+
+ if (iptuninfo[IFLA_IPTUN_FLOWINFO])
+ flowinfo = rta_getattr_u32(iptuninfo[IFLA_IPTUN_FLOWINFO]);
+
+ if (iptuninfo[IFLA_IPTUN_FLAGS])
+ flags = rta_getattr_u32(iptuninfo[IFLA_IPTUN_FLAGS]);
+
+ if (iptuninfo[IFLA_IPTUN_LINK])
+ link = rta_getattr_u32(iptuninfo[IFLA_IPTUN_LINK]);
+
+ if (iptuninfo[IFLA_IPTUN_PROTO])
+ proto = rta_getattr_u8(iptuninfo[IFLA_IPTUN_PROTO]);
+ if (iptuninfo[IFLA_IPTUN_COLLECT_METADATA])
+ metadata = 1;
+
+ if (iptuninfo[IFLA_IPTUN_FWMARK])
+ fwmark = rta_getattr_u32(iptuninfo[IFLA_IPTUN_FWMARK]);
+
+ free(answer);
+ }
+
+ while (argc > 0) {
+ if (strcmp(*argv, "mode") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "ipv6/ipv6") == 0 ||
+ strcmp(*argv, "ip6ip6") == 0)
+ proto = IPPROTO_IPV6;
+ else if (strcmp(*argv, "ip/ipv6") == 0 ||
+ strcmp(*argv, "ipv4/ipv6") == 0 ||
+ strcmp(*argv, "ipip6") == 0 ||
+ strcmp(*argv, "ip4ip6") == 0)
+ proto = IPPROTO_IPIP;
+ else if (strcmp(*argv, "any/ipv6") == 0 ||
+ strcmp(*argv, "any") == 0)
+ proto = 0;
+ else
+ invarg("Cannot guess tunnel mode.", *argv);
+ } else if (strcmp(*argv, "remote") == 0) {
+ NEXT_ARG();
+ get_addr(&daddr, *argv, AF_INET6);
+ } else if (strcmp(*argv, "local") == 0) {
+ NEXT_ARG();
+ get_addr(&saddr, *argv, AF_INET6);
+ } else if (matches(*argv, "dev") == 0) {
+ NEXT_ARG();
+ link = ll_name_to_index(*argv);
+ if (!link)
+ exit(nodev(*argv));
+ } else if (strcmp(*argv, "ttl") == 0 ||
+ strcmp(*argv, "hoplimit") == 0 ||
+ strcmp(*argv, "hlim") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "inherit") != 0) {
+ if (get_u8(&hop_limit, *argv, 0))
+ invarg("invalid HLIM\n", *argv);
+ } else
+ hop_limit = 0;
+ } else if (strcmp(*argv, "encaplimit") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "none") == 0) {
+ flags |= IP6_TNL_F_IGN_ENCAP_LIMIT;
+ } else {
+ __u8 uval;
+
+ if (get_u8(&uval, *argv, 0) < -1)
+ invarg("invalid ELIM", *argv);
+ encap_limit = uval;
+ flags &= ~IP6_TNL_F_IGN_ENCAP_LIMIT;
+ }
+ } else if (strcmp(*argv, "tos") == 0 ||
+ strcmp(*argv, "tclass") == 0 ||
+ strcmp(*argv, "tc") == 0 ||
+ matches(*argv, "dsfield") == 0) {
+ __u8 uval;
+
+ NEXT_ARG();
+ flowinfo &= ~IP6_FLOWINFO_TCLASS;
+ if (strcmp(*argv, "inherit") == 0)
+ flags |= IP6_TNL_F_USE_ORIG_TCLASS;
+ else {
+ if (get_u8(&uval, *argv, 16))
+ invarg("invalid TClass", *argv);
+ flowinfo |= htonl((__u32)uval << 20) & IP6_FLOWINFO_TCLASS;
+ flags &= ~IP6_TNL_F_USE_ORIG_TCLASS;
+ }
+ } else if (strcmp(*argv, "flowlabel") == 0 ||
+ strcmp(*argv, "fl") == 0) {
+ __u32 uval;
+
+ NEXT_ARG();
+ flowinfo &= ~IP6_FLOWINFO_FLOWLABEL;
+ if (strcmp(*argv, "inherit") == 0)
+ flags |= IP6_TNL_F_USE_ORIG_FLOWLABEL;
+ else {
+ if (get_u32(&uval, *argv, 16))
+ invarg("invalid Flowlabel", *argv);
+ if (uval > 0xFFFFF)
+ invarg("invalid Flowlabel", *argv);
+ flowinfo |= htonl(uval) & IP6_FLOWINFO_FLOWLABEL;
+ flags &= ~IP6_TNL_F_USE_ORIG_FLOWLABEL;
+ }
+ } else if (strcmp(*argv, "dscp") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "inherit") != 0)
+ invarg("not inherit", *argv);
+ flags |= IP6_TNL_F_RCV_DSCP_COPY;
+ } else if (strcmp(*argv, "fwmark") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "inherit") == 0) {
+ flags |= IP6_TNL_F_USE_ORIG_FWMARK;
+ fwmark = 0;
+ } else {
+ if (get_u32(&fwmark, *argv, 0))
+ invarg("invalid fwmark\n", *argv);
+ flags &= ~IP6_TNL_F_USE_ORIG_FWMARK;
+ }
+ } else if (strcmp(*argv, "allow-localremote") == 0) {
+ flags |= IP6_TNL_F_ALLOW_LOCAL_REMOTE;
+ } else if (strcmp(*argv, "noallow-localremote") == 0) {
+ flags &= ~IP6_TNL_F_ALLOW_LOCAL_REMOTE;
+ } else if (strcmp(*argv, "noencap") == 0) {
+ encaptype = TUNNEL_ENCAP_NONE;
+ } else if (strcmp(*argv, "encap") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "fou") == 0)
+ encaptype = TUNNEL_ENCAP_FOU;
+ else if (strcmp(*argv, "gue") == 0)
+ encaptype = TUNNEL_ENCAP_GUE;
+ else if (strcmp(*argv, "none") == 0)
+ encaptype = TUNNEL_ENCAP_NONE;
+ else
+ invarg("Invalid encap type.", *argv);
+ } else if (strcmp(*argv, "encap-sport") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "auto") == 0)
+ encapsport = 0;
+ else if (get_u16(&encapsport, *argv, 0))
+ invarg("Invalid source port.", *argv);
+ } else if (strcmp(*argv, "encap-dport") == 0) {
+ NEXT_ARG();
+ if (get_u16(&encapdport, *argv, 0))
+ invarg("Invalid destination port.", *argv);
+ } else if (strcmp(*argv, "encap-csum") == 0) {
+ encapflags |= TUNNEL_ENCAP_FLAG_CSUM;
+ } else if (strcmp(*argv, "noencap-csum") == 0) {
+ encapflags &= ~TUNNEL_ENCAP_FLAG_CSUM;
+ } else if (strcmp(*argv, "encap-udp6-csum") == 0) {
+ encapflags |= TUNNEL_ENCAP_FLAG_CSUM6;
+ } else if (strcmp(*argv, "noencap-udp6-csum") == 0) {
+ encapflags &= ~TUNNEL_ENCAP_FLAG_CSUM6;
+ } else if (strcmp(*argv, "encap-remcsum") == 0) {
+ encapflags |= TUNNEL_ENCAP_FLAG_REMCSUM;
+ } else if (strcmp(*argv, "noencap-remcsum") == 0) {
+ encapflags &= ~TUNNEL_ENCAP_FLAG_REMCSUM;
+ } else if (strcmp(*argv, "external") == 0) {
+ metadata = 1;
+ } else {
+ ip6tunnel_print_help(lu, argc, argv, stderr);
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ addattr8(n, 1024, IFLA_IPTUN_PROTO, proto);
+ if (metadata) {
+ addattr_l(n, 1024, IFLA_IPTUN_COLLECT_METADATA, NULL, 0);
+ return 0;
+ }
+
+ if (is_addrtype_inet_not_unspec(&saddr)) {
+ addattr_l(n, 1024, IFLA_IPTUN_LOCAL,
+ saddr.data, saddr.bytelen);
+ }
+ if (is_addrtype_inet_not_unspec(&daddr)) {
+ addattr_l(n, 1024, IFLA_IPTUN_REMOTE,
+ daddr.data, daddr.bytelen);
+ }
+ addattr8(n, 1024, IFLA_IPTUN_TTL, hop_limit);
+ addattr8(n, 1024, IFLA_IPTUN_ENCAP_LIMIT, encap_limit);
+ addattr32(n, 1024, IFLA_IPTUN_FLOWINFO, flowinfo);
+ addattr32(n, 1024, IFLA_IPTUN_FLAGS, flags);
+ addattr32(n, 1024, IFLA_IPTUN_LINK, link);
+ addattr32(n, 1024, IFLA_IPTUN_FWMARK, fwmark);
+
+ addattr16(n, 1024, IFLA_IPTUN_ENCAP_TYPE, encaptype);
+ addattr16(n, 1024, IFLA_IPTUN_ENCAP_FLAGS, encapflags);
+ addattr16(n, 1024, IFLA_IPTUN_ENCAP_SPORT, htons(encapsport));
+ addattr16(n, 1024, IFLA_IPTUN_ENCAP_DPORT, htons(encapdport));
+
+ return 0;
+
+get_failed:
+ fprintf(stderr, "Failed to get existing tunnel info.\n");
+ free(answer);
+ return -1;
+}
+
+static void ip6tunnel_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ char s2[64];
+ __u32 flags = 0;
+ __u32 flowinfo = 0;
+ __u8 ttl = 0;
+
+ if (!tb)
+ return;
+
+ if (tb[IFLA_IPTUN_COLLECT_METADATA]) {
+ print_bool(PRINT_ANY, "external", "external ", true);
+ }
+
+ if (tb[IFLA_IPTUN_FLAGS])
+ flags = rta_getattr_u32(tb[IFLA_IPTUN_FLAGS]);
+
+ if (tb[IFLA_IPTUN_FLOWINFO])
+ flowinfo = rta_getattr_u32(tb[IFLA_IPTUN_FLOWINFO]);
+
+ if (tb[IFLA_IPTUN_PROTO]) {
+ switch (rta_getattr_u8(tb[IFLA_IPTUN_PROTO])) {
+ case IPPROTO_IPIP:
+ print_string(PRINT_ANY, "proto", "%s ", "ipip6");
+ break;
+ case IPPROTO_IPV6:
+ print_string(PRINT_ANY, "proto", "%s ", "ip6ip6");
+ break;
+ case 0:
+ print_string(PRINT_ANY, "proto", "%s ", "any");
+ break;
+ }
+ }
+
+ tnl_print_endpoint("remote", tb[IFLA_IPTUN_REMOTE], AF_INET6);
+ tnl_print_endpoint("local", tb[IFLA_IPTUN_LOCAL], AF_INET6);
+
+ if (tb[IFLA_IPTUN_LINK]) {
+ __u32 link = rta_getattr_u32(tb[IFLA_IPTUN_LINK]);
+
+ if (link) {
+ print_string(PRINT_ANY, "link", "dev %s ",
+ ll_index_to_name(link));
+ }
+ }
+
+ if (tb[IFLA_IPTUN_TTL])
+ ttl = rta_getattr_u8(tb[IFLA_IPTUN_TTL]);
+ if (is_json_context() || ttl)
+ print_uint(PRINT_ANY, "ttl", "hoplimit %u ", ttl);
+ else
+ print_string(PRINT_FP, NULL, "hoplimit %s ", "inherit");
+
+ if (flags & IP6_TNL_F_IGN_ENCAP_LIMIT) {
+ print_bool(PRINT_ANY,
+ "ip6_tnl_f_ign_encap_limit",
+ "encaplimit none ",
+ true);
+ } else if (tb[IFLA_IPTUN_ENCAP_LIMIT]) {
+ __u8 val = rta_getattr_u8(tb[IFLA_IPTUN_ENCAP_LIMIT]);
+
+ print_uint(PRINT_ANY, "encap_limit", "encaplimit %u ", val);
+ }
+
+ if (flags & IP6_TNL_F_USE_ORIG_TCLASS) {
+ print_bool(PRINT_ANY,
+ "ip6_tnl_f_use_orig_tclass",
+ "tclass inherit ",
+ true);
+ } else if (tb[IFLA_IPTUN_FLOWINFO]) {
+ __u32 val = ntohl(flowinfo & IP6_FLOWINFO_TCLASS) >> 20;
+
+ snprintf(s2, sizeof(s2), "0x%02x", val);
+ print_string(PRINT_ANY, "tclass", "tclass %s ", s2);
+ }
+
+ if (flags & IP6_TNL_F_USE_ORIG_FLOWLABEL) {
+ print_bool(PRINT_ANY,
+ "ip6_tnl_f_use_orig_flowlabel",
+ "flowlabel inherit ",
+ true);
+ } else if (tb[IFLA_IPTUN_FLOWINFO]) {
+ __u32 val = ntohl(flowinfo & IP6_FLOWINFO_FLOWLABEL);
+
+ snprintf(s2, sizeof(s2), "0x%05x", val);
+ print_string(PRINT_ANY, "flowlabel", "flowlabel %s ", s2);
+ }
+
+ if (flags & IP6_TNL_F_RCV_DSCP_COPY)
+ print_bool(PRINT_ANY,
+ "ip6_tnl_f_rcv_dscp_copy",
+ "dscp inherit ",
+ true);
+
+ if (flags & IP6_TNL_F_MIP6_DEV)
+ print_bool(PRINT_ANY, "ip6_tnl_f_mip6_dev", "mip6 ", true);
+
+ if (flags & IP6_TNL_F_ALLOW_LOCAL_REMOTE)
+ print_bool(PRINT_ANY,
+ "ip6_tnl_f_allow_local_remote",
+ "allow-localremote ",
+ true);
+
+ if (flags & IP6_TNL_F_USE_ORIG_FWMARK) {
+ print_bool(PRINT_ANY,
+ "ip6_tnl_f_use_orig_fwmark",
+ "fwmark inherit ",
+ true);
+ } else if (tb[IFLA_IPTUN_FWMARK]) {
+ __u32 fwmark = rta_getattr_u32(tb[IFLA_IPTUN_FWMARK]);
+
+ if (fwmark) {
+ print_0xhex(PRINT_ANY,
+ "fwmark", "fwmark %#llx ", fwmark);
+ }
+ }
+
+ tnl_print_encap(tb,
+ IFLA_IPTUN_ENCAP_TYPE,
+ IFLA_IPTUN_ENCAP_FLAGS,
+ IFLA_IPTUN_ENCAP_SPORT,
+ IFLA_IPTUN_ENCAP_DPORT);
+}
+
+struct link_util ip6tnl_link_util = {
+ .id = "ip6tnl",
+ .maxattr = IFLA_IPTUN_MAX,
+ .parse_opt = ip6tunnel_parse_opt,
+ .print_opt = ip6tunnel_print_opt,
+ .print_help = ip6tunnel_print_help,
+};
diff --git a/ip/link_iptnl.c b/ip/link_iptnl.c
new file mode 100644
index 0000000..49c3ae2
--- /dev/null
+++ b/ip/link_iptnl.c
@@ -0,0 +1,492 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * link_iptnl.c ipip and sit driver module
+ *
+ * Authors: Nicolas Dichtel <nicolas.dichtel@6wind.com>
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+static void iptunnel_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ const char *mode;
+
+ if (strcmp(lu->id, "sit") == 0) {
+ mode = "{ ip6ip | ipip | mplsip | any } ]\n"
+ " [ isatap";
+ } else {
+ mode = "{ ipip | mplsip | any }";
+ }
+
+ fprintf(f,
+ "Usage: ... %-6s [ remote ADDR ]\n"
+ " [ local ADDR ]\n"
+ " [ ttl TTL ]\n"
+ " [ tos TOS ]\n"
+ " [ [no]pmtudisc ]\n"
+ " [ 6rd-prefix ADDR ]\n"
+ " [ 6rd-relay_prefix ADDR ]\n"
+ " [ 6rd-reset ]\n"
+ " [ dev PHYS_DEV ]\n"
+ " [ fwmark MARK ]\n"
+ " [ external ]\n"
+ " [ noencap ]\n"
+ " [ encap { fou | gue | none } ]\n"
+ " [ encap-sport PORT ]\n"
+ " [ encap-dport PORT ]\n"
+ " [ [no]encap-csum ]\n"
+ " [ [no]encap-csum6 ]\n"
+ " [ [no]encap-remcsum ]\n"
+ " [ mode %s ]\n"
+ "\n"
+ "Where: ADDR := { IP_ADDRESS | any }\n"
+ " TOS := { NUMBER | inherit }\n"
+ " TTL := { 1..255 | inherit }\n"
+ " MARK := { 0x0..0xffffffff }\n",
+ lu->id, mode);
+}
+
+static int iptunnel_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ struct ifinfomsg *ifi = NLMSG_DATA(n);
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg i;
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(*ifi)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_GETLINK,
+ .i.ifi_family = preferred_family,
+ .i.ifi_index = ifi->ifi_index,
+ };
+ struct nlmsghdr *answer = NULL;
+ struct rtattr *tb[IFLA_MAX + 1];
+ struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+ struct rtattr *iptuninfo[IFLA_IPTUN_MAX + 1];
+ int len;
+ inet_prefix saddr, daddr, ip6rdprefix, ip6rdrelayprefix;
+ __u8 pmtudisc = 1;
+ __u8 tos = 0;
+ __u16 iflags = 0;
+ __u8 ttl = 0;
+ __u8 proto = 0;
+ __u32 link = 0;
+ __u16 encaptype = 0;
+ __u16 encapflags = 0;
+ __u16 encapsport = 0;
+ __u16 encapdport = 0;
+ __u8 metadata = 0;
+ __u32 fwmark = 0;
+
+ inet_prefix_reset(&saddr);
+ inet_prefix_reset(&daddr);
+
+ inet_prefix_reset(&ip6rdprefix);
+ inet_prefix_reset(&ip6rdrelayprefix);
+
+ if (!(n->nlmsg_flags & NLM_F_CREATE)) {
+ const struct rtattr *rta;
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0)
+ goto get_failed;
+
+ len = answer->nlmsg_len;
+ len -= NLMSG_LENGTH(sizeof(*ifi));
+ if (len < 0)
+ goto get_failed;
+
+ parse_rtattr(tb, IFLA_MAX, IFLA_RTA(NLMSG_DATA(answer)), len);
+
+ if (!tb[IFLA_LINKINFO])
+ goto get_failed;
+
+ parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+ if (!linkinfo[IFLA_INFO_DATA])
+ goto get_failed;
+
+ parse_rtattr_nested(iptuninfo, IFLA_IPTUN_MAX,
+ linkinfo[IFLA_INFO_DATA]);
+
+ rta = iptuninfo[IFLA_IPTUN_LOCAL];
+ if (rta && get_addr_rta(&saddr, rta, AF_INET))
+ goto get_failed;
+
+ rta = iptuninfo[IFLA_IPTUN_REMOTE];
+ if (rta && get_addr_rta(&daddr, rta, AF_INET))
+ goto get_failed;
+
+ rta = iptuninfo[IFLA_IPTUN_6RD_PREFIX];
+ if (rta && get_addr_rta(&ip6rdprefix, rta, AF_INET6))
+ goto get_failed;
+
+ rta = iptuninfo[IFLA_IPTUN_6RD_RELAY_PREFIX];
+ if (rta && get_addr_rta(&ip6rdrelayprefix, rta, AF_INET))
+ goto get_failed;
+
+ rta = iptuninfo[IFLA_IPTUN_6RD_PREFIXLEN];
+ ip6rdprefix.bitlen = rta ? rta_getattr_u16(rta) : 0;
+
+ rta = iptuninfo[IFLA_IPTUN_6RD_RELAY_PREFIXLEN];
+ ip6rdrelayprefix.bitlen = rta ? rta_getattr_u16(rta) : 0;
+
+ if (iptuninfo[IFLA_IPTUN_TTL])
+ ttl = rta_getattr_u8(iptuninfo[IFLA_IPTUN_TTL]);
+
+ if (iptuninfo[IFLA_IPTUN_PMTUDISC])
+ pmtudisc =
+ rta_getattr_u8(iptuninfo[IFLA_IPTUN_PMTUDISC]);
+
+ if (iptuninfo[IFLA_IPTUN_TOS])
+ tos = rta_getattr_u8(iptuninfo[IFLA_IPTUN_TOS]);
+
+ if (iptuninfo[IFLA_IPTUN_FLAGS])
+ iflags = rta_getattr_u16(iptuninfo[IFLA_IPTUN_FLAGS]);
+
+ if (iptuninfo[IFLA_IPTUN_LINK])
+ link = rta_getattr_u32(iptuninfo[IFLA_IPTUN_LINK]);
+
+ if (iptuninfo[IFLA_IPTUN_PROTO])
+ proto = rta_getattr_u8(iptuninfo[IFLA_IPTUN_PROTO]);
+
+ if (iptuninfo[IFLA_IPTUN_ENCAP_TYPE])
+ encaptype = rta_getattr_u16(iptuninfo[IFLA_IPTUN_ENCAP_TYPE]);
+ if (iptuninfo[IFLA_IPTUN_ENCAP_FLAGS])
+ encapflags = rta_getattr_u16(iptuninfo[IFLA_IPTUN_ENCAP_FLAGS]);
+ if (iptuninfo[IFLA_IPTUN_ENCAP_SPORT])
+ encapsport = rta_getattr_u16(iptuninfo[IFLA_IPTUN_ENCAP_SPORT]);
+ if (iptuninfo[IFLA_IPTUN_ENCAP_DPORT])
+ encapdport = rta_getattr_u16(iptuninfo[IFLA_IPTUN_ENCAP_DPORT]);
+
+ if (iptuninfo[IFLA_IPTUN_COLLECT_METADATA])
+ metadata = 1;
+
+ if (iptuninfo[IFLA_IPTUN_FWMARK])
+ fwmark = rta_getattr_u32(iptuninfo[IFLA_IPTUN_FWMARK]);
+
+ free(answer);
+ }
+
+ while (argc > 0) {
+ if (strcmp(*argv, "mode") == 0) {
+ NEXT_ARG();
+ if (strcmp(lu->id, "sit") == 0 &&
+ (strcmp(*argv, "ipv6/ipv4") == 0 ||
+ strcmp(*argv, "ip6ip") == 0))
+ proto = IPPROTO_IPV6;
+ else if (strcmp(*argv, "ipv4/ipv4") == 0 ||
+ strcmp(*argv, "ipip") == 0 ||
+ strcmp(*argv, "ip4ip4") == 0)
+ proto = IPPROTO_IPIP;
+ else if (strcmp(*argv, "mpls/ipv4") == 0 ||
+ strcmp(*argv, "mplsip") == 0)
+ proto = IPPROTO_MPLS;
+ else if (strcmp(*argv, "any/ipv4") == 0 ||
+ strcmp(*argv, "any") == 0)
+ proto = 0;
+ else
+ invarg("Cannot guess tunnel mode.", *argv);
+ } else if (strcmp(*argv, "remote") == 0) {
+ NEXT_ARG();
+ get_addr(&daddr, *argv, AF_INET);
+ } else if (strcmp(*argv, "local") == 0) {
+ NEXT_ARG();
+ get_addr(&saddr, *argv, AF_INET);
+ } else if (matches(*argv, "dev") == 0) {
+ NEXT_ARG();
+ link = ll_name_to_index(*argv);
+ if (!link)
+ exit(nodev(*argv));
+ } else if (strcmp(*argv, "ttl") == 0 ||
+ strcmp(*argv, "hoplimit") == 0 ||
+ strcmp(*argv, "hlim") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "inherit") != 0) {
+ if (get_u8(&ttl, *argv, 0))
+ invarg("invalid TTL\n", *argv);
+ } else
+ ttl = 0;
+ } else if (strcmp(*argv, "tos") == 0 ||
+ strcmp(*argv, "tclass") == 0 ||
+ strcmp(*argv, "tc") == 0 ||
+ matches(*argv, "dsfield") == 0) {
+ __u32 uval;
+
+ NEXT_ARG();
+ if (strcmp(*argv, "inherit") != 0) {
+ if (rtnl_dsfield_a2n(&uval, *argv))
+ invarg("bad TOS value", *argv);
+ tos = uval;
+ } else
+ tos = 1;
+ } else if (strcmp(*argv, "nopmtudisc") == 0) {
+ pmtudisc = 0;
+ } else if (strcmp(*argv, "pmtudisc") == 0) {
+ pmtudisc = 1;
+ } else if (strcmp(lu->id, "sit") == 0 &&
+ strcmp(*argv, "isatap") == 0) {
+ iflags |= SIT_ISATAP;
+ } else if (strcmp(*argv, "noencap") == 0) {
+ encaptype = TUNNEL_ENCAP_NONE;
+ } else if (strcmp(*argv, "encap") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "fou") == 0)
+ encaptype = TUNNEL_ENCAP_FOU;
+ else if (strcmp(*argv, "gue") == 0)
+ encaptype = TUNNEL_ENCAP_GUE;
+ else if (strcmp(*argv, "none") == 0)
+ encaptype = TUNNEL_ENCAP_NONE;
+ else
+ invarg("Invalid encap type.", *argv);
+ } else if (strcmp(*argv, "encap-sport") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "auto") == 0)
+ encapsport = 0;
+ else if (get_u16(&encapsport, *argv, 0))
+ invarg("Invalid source port.", *argv);
+ } else if (strcmp(*argv, "encap-dport") == 0) {
+ NEXT_ARG();
+ if (get_u16(&encapdport, *argv, 0))
+ invarg("Invalid destination port.", *argv);
+ } else if (strcmp(*argv, "encap-csum") == 0) {
+ encapflags |= TUNNEL_ENCAP_FLAG_CSUM;
+ } else if (strcmp(*argv, "noencap-csum") == 0) {
+ encapflags &= ~TUNNEL_ENCAP_FLAG_CSUM;
+ } else if (strcmp(*argv, "encap-udp6-csum") == 0) {
+ encapflags |= TUNNEL_ENCAP_FLAG_CSUM6;
+ } else if (strcmp(*argv, "noencap-udp6-csum") == 0) {
+ encapflags &= ~TUNNEL_ENCAP_FLAG_CSUM6;
+ } else if (strcmp(*argv, "encap-remcsum") == 0) {
+ encapflags |= TUNNEL_ENCAP_FLAG_REMCSUM;
+ } else if (strcmp(*argv, "noencap-remcsum") == 0) {
+ encapflags &= ~TUNNEL_ENCAP_FLAG_REMCSUM;
+ } else if (strcmp(*argv, "external") == 0) {
+ metadata = 1;
+ } else if (strcmp(*argv, "6rd-prefix") == 0) {
+ NEXT_ARG();
+ if (get_prefix(&ip6rdprefix, *argv, AF_INET6))
+ invarg("invalid 6rd_prefix\n", *argv);
+ } else if (strcmp(*argv, "6rd-relay_prefix") == 0) {
+ NEXT_ARG();
+ if (get_prefix(&ip6rdrelayprefix, *argv, AF_INET))
+ invarg("invalid 6rd-relay_prefix\n", *argv);
+ } else if (strcmp(*argv, "6rd-reset") == 0) {
+ get_prefix(&ip6rdprefix, "2002::/16", AF_INET6);
+ inet_prefix_reset(&ip6rdrelayprefix);
+ } else if (strcmp(*argv, "fwmark") == 0) {
+ NEXT_ARG();
+ if (get_u32(&fwmark, *argv, 0))
+ invarg("invalid fwmark\n", *argv);
+ } else {
+ iptunnel_print_help(lu, argc, argv, stderr);
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ if (ttl && pmtudisc == 0) {
+ fprintf(stderr, "ttl != 0 and nopmtudisc are incompatible\n");
+ exit(-1);
+ }
+
+ addattr8(n, 1024, IFLA_IPTUN_PROTO, proto);
+ if (metadata) {
+ addattr_l(n, 1024, IFLA_IPTUN_COLLECT_METADATA, NULL, 0);
+ return 0;
+ }
+
+ if (is_addrtype_inet_not_unspec(&saddr)) {
+ addattr_l(n, 1024, IFLA_IPTUN_LOCAL,
+ saddr.data, saddr.bytelen);
+ }
+ if (is_addrtype_inet_not_unspec(&daddr)) {
+ addattr_l(n, 1024, IFLA_IPTUN_REMOTE,
+ daddr.data, daddr.bytelen);
+ }
+ addattr8(n, 1024, IFLA_IPTUN_PMTUDISC, pmtudisc);
+ addattr8(n, 1024, IFLA_IPTUN_TOS, tos);
+ addattr8(n, 1024, IFLA_IPTUN_TTL, ttl);
+ addattr32(n, 1024, IFLA_IPTUN_LINK, link);
+ addattr32(n, 1024, IFLA_IPTUN_FWMARK, fwmark);
+
+ addattr16(n, 1024, IFLA_IPTUN_ENCAP_TYPE, encaptype);
+ addattr16(n, 1024, IFLA_IPTUN_ENCAP_FLAGS, encapflags);
+ addattr16(n, 1024, IFLA_IPTUN_ENCAP_SPORT, htons(encapsport));
+ addattr16(n, 1024, IFLA_IPTUN_ENCAP_DPORT, htons(encapdport));
+
+ if (strcmp(lu->id, "sit") == 0) {
+ addattr16(n, 1024, IFLA_IPTUN_FLAGS, iflags);
+ if (is_addrtype_inet(&ip6rdprefix)) {
+ addattr_l(n, 1024, IFLA_IPTUN_6RD_PREFIX,
+ ip6rdprefix.data, ip6rdprefix.bytelen);
+ addattr16(n, 1024, IFLA_IPTUN_6RD_PREFIXLEN,
+ ip6rdprefix.bitlen);
+ }
+ if (is_addrtype_inet(&ip6rdrelayprefix)) {
+ addattr32(n, 1024, IFLA_IPTUN_6RD_RELAY_PREFIX,
+ ip6rdrelayprefix.data[0]);
+ addattr16(n, 1024, IFLA_IPTUN_6RD_RELAY_PREFIXLEN,
+ ip6rdrelayprefix.bitlen);
+ }
+ }
+
+ return 0;
+
+get_failed:
+ fprintf(stderr, "Failed to get existing tunnel info.\n");
+ free(answer);
+ return -1;
+}
+
+static void iptunnel_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ char s2[64];
+ __u16 prefixlen;
+ __u8 ttl = 0;
+ __u8 tos = 0;
+
+ if (!tb)
+ return;
+
+ if (tb[IFLA_IPTUN_COLLECT_METADATA]) {
+ print_bool(PRINT_ANY, "external", "external ", true);
+ }
+
+ if (tb[IFLA_IPTUN_PROTO]) {
+ switch (rta_getattr_u8(tb[IFLA_IPTUN_PROTO])) {
+ case IPPROTO_IPIP:
+ print_string(PRINT_ANY, "proto", "%s ", "ipip");
+ break;
+ case IPPROTO_IPV6:
+ print_string(PRINT_ANY, "proto", "%s ", "ip6ip");
+ break;
+ case IPPROTO_MPLS:
+ print_string(PRINT_ANY, "proto", "%s ", "mplsip");
+ break;
+ case 0:
+ print_string(PRINT_ANY, "proto", "%s ", "any");
+ break;
+ }
+ }
+
+ tnl_print_endpoint("remote", tb[IFLA_IPTUN_REMOTE], AF_INET);
+ tnl_print_endpoint("local", tb[IFLA_IPTUN_LOCAL], AF_INET);
+
+ if (tb[IFLA_IPTUN_LINK]) {
+ __u32 link = rta_getattr_u32(tb[IFLA_IPTUN_LINK]);
+
+ if (link) {
+ print_string(PRINT_ANY, "link", "dev %s ",
+ ll_index_to_name(link));
+ }
+ }
+
+ if (tb[IFLA_IPTUN_TTL])
+ ttl = rta_getattr_u8(tb[IFLA_IPTUN_TTL]);
+ if (is_json_context() || ttl)
+ print_uint(PRINT_ANY, "ttl", "ttl %u ", ttl);
+ else
+ print_string(PRINT_FP, NULL, "ttl %s ", "inherit");
+
+ if (tb[IFLA_IPTUN_TOS])
+ tos = rta_getattr_u8(tb[IFLA_IPTUN_TOS]);
+ if (tos) {
+ if (is_json_context() || tos != 1)
+ print_0xhex(PRINT_ANY, "tos", "tos %#llx ", tos);
+ else
+ print_string(PRINT_FP, NULL, "tos %s ", "inherit");
+ }
+
+ if (tb[IFLA_IPTUN_PMTUDISC] && rta_getattr_u8(tb[IFLA_IPTUN_PMTUDISC]))
+ print_bool(PRINT_ANY, "pmtudisc", "pmtudisc ", true);
+ else
+ print_bool(PRINT_ANY, "pmtudisc", "nopmtudisc ", false);
+
+ if (tb[IFLA_IPTUN_FLAGS]) {
+ __u16 iflags = rta_getattr_u16(tb[IFLA_IPTUN_FLAGS]);
+
+ if (iflags & SIT_ISATAP)
+ print_bool(PRINT_ANY, "isatap", "isatap ", true);
+ }
+
+ if (tb[IFLA_IPTUN_6RD_PREFIXLEN] &&
+ (prefixlen = rta_getattr_u16(tb[IFLA_IPTUN_6RD_PREFIXLEN]))) {
+ __u16 relayprefixlen =
+ rta_getattr_u16(tb[IFLA_IPTUN_6RD_RELAY_PREFIXLEN]);
+ __u32 relayprefix =
+ rta_getattr_u32(tb[IFLA_IPTUN_6RD_RELAY_PREFIX]);
+
+ const char *prefix = inet_ntop(AF_INET6,
+ RTA_DATA(tb[IFLA_IPTUN_6RD_PREFIX]),
+ s2, sizeof(s2));
+
+ if (is_json_context()) {
+ print_string(PRINT_JSON, "prefix", NULL, prefix);
+ print_int(PRINT_JSON, "prefixlen", NULL, prefixlen);
+ if (relayprefix) {
+ print_string(PRINT_JSON,
+ "relay_prefix",
+ NULL,
+ format_host(AF_INET,
+ 4,
+ &relayprefix));
+ print_int(PRINT_JSON,
+ "relay_prefixlen",
+ NULL,
+ relayprefixlen);
+ }
+ } else {
+ printf("6rd-prefix %s/%u ", prefix, prefixlen);
+ if (relayprefix) {
+ printf("6rd-relay_prefix %s/%u ",
+ format_host(AF_INET, 4, &relayprefix),
+ relayprefixlen);
+ }
+ }
+ }
+
+ if (tb[IFLA_IPTUN_FWMARK]) {
+ __u32 fwmark = rta_getattr_u32(tb[IFLA_IPTUN_FWMARK]);
+
+ if (fwmark) {
+ print_0xhex(PRINT_ANY,
+ "fwmark", "fwmark %#llx ", fwmark);
+ }
+ }
+
+ tnl_print_encap(tb,
+ IFLA_IPTUN_ENCAP_TYPE,
+ IFLA_IPTUN_ENCAP_FLAGS,
+ IFLA_IPTUN_ENCAP_SPORT,
+ IFLA_IPTUN_ENCAP_DPORT);
+}
+
+struct link_util ipip_link_util = {
+ .id = "ipip",
+ .maxattr = IFLA_IPTUN_MAX,
+ .parse_opt = iptunnel_parse_opt,
+ .print_opt = iptunnel_print_opt,
+ .print_help = iptunnel_print_help,
+};
+
+struct link_util sit_link_util = {
+ .id = "sit",
+ .maxattr = IFLA_IPTUN_MAX,
+ .parse_opt = iptunnel_parse_opt,
+ .print_opt = iptunnel_print_opt,
+ .print_help = iptunnel_print_help,
+};
diff --git a/ip/link_veth.c b/ip/link_veth.c
new file mode 100644
index 0000000..6da5b64
--- /dev/null
+++ b/ip/link_veth.c
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * link_veth.c veth driver module
+ *
+ * Authors: Pavel Emelianov <xemul@openvz.org>
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <linux/veth.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_usage(FILE *f)
+{
+ printf("Usage: ip link <options> type veth [peer <options>]\n"
+ "To get <options> type 'ip link add help'\n");
+}
+
+static void usage(void)
+{
+ print_usage(stderr);
+}
+
+static int veth_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ char *type = NULL;
+ int err;
+ struct rtattr *data;
+ struct ifinfomsg *ifm, *peer_ifm;
+ unsigned int ifi_flags, ifi_change, ifi_index;
+
+ if (strcmp(argv[0], "peer") != 0) {
+ usage();
+ return -1;
+ }
+
+ ifm = NLMSG_DATA(n);
+ ifi_flags = ifm->ifi_flags;
+ ifi_change = ifm->ifi_change;
+ ifi_index = ifm->ifi_index;
+ ifm->ifi_flags = 0;
+ ifm->ifi_change = 0;
+ ifm->ifi_index = 0;
+
+ data = addattr_nest(n, 1024, VETH_INFO_PEER);
+
+ n->nlmsg_len += sizeof(struct ifinfomsg);
+
+ err = iplink_parse(argc - 1, argv + 1, (struct iplink_req *)n, &type);
+ if (err < 0)
+ return err;
+
+ if (type)
+ duparg("type", argv[err]);
+
+ peer_ifm = RTA_DATA(data);
+ peer_ifm->ifi_index = ifm->ifi_index;
+ peer_ifm->ifi_flags = ifm->ifi_flags;
+ peer_ifm->ifi_change = ifm->ifi_change;
+ ifm->ifi_flags = ifi_flags;
+ ifm->ifi_change = ifi_change;
+ ifm->ifi_index = ifi_index;
+
+ addattr_nest_end(n, data);
+ return argc - 1 - err;
+}
+
+static void veth_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ print_usage(f);
+}
+
+struct link_util veth_link_util = {
+ .id = "veth",
+ .parse_opt = veth_parse_opt,
+ .print_help = veth_print_help,
+};
diff --git a/ip/link_vti.c b/ip/link_vti.c
new file mode 100644
index 0000000..2106a9d
--- /dev/null
+++ b/ip/link_vti.c
@@ -0,0 +1,213 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * link_vti.c VTI driver module
+ *
+ * Authors: Herbert Xu <herbert@gondor.apana.org.au>
+ * Saurabh Mohan <saurabh.mohan@vyatta.com> Modified link_gre.c for VTI
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+static void vti_print_help(struct link_util *lu, int argc, char **argv, FILE *f)
+{
+ fprintf(f,
+ "Usage: ... %-4s [ remote ADDR ]\n"
+ " [ local ADDR ]\n"
+ " [ [i|o]key KEY ]\n"
+ " [ dev PHYS_DEV ]\n"
+ " [ fwmark MARK ]\n"
+ "\n"
+ "Where: ADDR := { IP_ADDRESS }\n"
+ " KEY := { DOTTED_QUAD | NUMBER }\n"
+ " MARK := { 0x0..0xffffffff }\n",
+ lu->id);
+}
+
+static int vti_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ struct ifinfomsg *ifi = NLMSG_DATA(n);
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg i;
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(*ifi)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_GETLINK,
+ .i.ifi_family = preferred_family,
+ .i.ifi_index = ifi->ifi_index,
+ };
+ struct nlmsghdr *answer = NULL;
+ struct rtattr *tb[IFLA_MAX + 1];
+ struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+ struct rtattr *vtiinfo[IFLA_VTI_MAX + 1];
+ __be32 ikey = 0;
+ __be32 okey = 0;
+ inet_prefix saddr, daddr;
+ unsigned int link = 0;
+ __u32 fwmark = 0;
+ int len;
+
+ inet_prefix_reset(&saddr);
+ inet_prefix_reset(&daddr);
+
+ if (!(n->nlmsg_flags & NLM_F_CREATE)) {
+ const struct rtattr *rta;
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0)
+ goto get_failed;
+
+ len = answer->nlmsg_len;
+ len -= NLMSG_LENGTH(sizeof(*ifi));
+ if (len < 0)
+ goto get_failed;
+
+ parse_rtattr(tb, IFLA_MAX, IFLA_RTA(NLMSG_DATA(answer)), len);
+
+ if (!tb[IFLA_LINKINFO])
+ goto get_failed;
+
+ parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+ if (!linkinfo[IFLA_INFO_DATA])
+ goto get_failed;
+
+ parse_rtattr_nested(vtiinfo, IFLA_VTI_MAX,
+ linkinfo[IFLA_INFO_DATA]);
+
+ rta = vtiinfo[IFLA_VTI_LOCAL];
+ if (rta && get_addr_rta(&saddr, rta, AF_INET))
+ goto get_failed;
+
+ rta = vtiinfo[IFLA_VTI_REMOTE];
+ if (rta && get_addr_rta(&daddr, rta, AF_INET))
+ goto get_failed;
+
+ if (vtiinfo[IFLA_VTI_IKEY])
+ ikey = rta_getattr_u32(vtiinfo[IFLA_VTI_IKEY]);
+
+ if (vtiinfo[IFLA_VTI_OKEY])
+ okey = rta_getattr_u32(vtiinfo[IFLA_VTI_OKEY]);
+
+ if (vtiinfo[IFLA_VTI_LINK])
+ link = rta_getattr_u8(vtiinfo[IFLA_VTI_LINK]);
+
+ if (vtiinfo[IFLA_VTI_FWMARK])
+ fwmark = rta_getattr_u32(vtiinfo[IFLA_VTI_FWMARK]);
+
+ free(answer);
+ }
+
+ while (argc > 0) {
+ if (!matches(*argv, "key")) {
+ NEXT_ARG();
+ ikey = okey = tnl_parse_key("key", *argv);
+ } else if (!matches(*argv, "ikey")) {
+ NEXT_ARG();
+ ikey = tnl_parse_key("ikey", *argv);
+ } else if (!matches(*argv, "okey")) {
+ NEXT_ARG();
+ okey = tnl_parse_key("okey", *argv);
+ } else if (!matches(*argv, "remote")) {
+ NEXT_ARG();
+ get_addr(&daddr, *argv, AF_INET);
+ } else if (!matches(*argv, "local")) {
+ NEXT_ARG();
+ get_addr(&saddr, *argv, AF_INET);
+ } else if (!matches(*argv, "dev")) {
+ NEXT_ARG();
+ link = ll_name_to_index(*argv);
+ if (!link)
+ exit(nodev(*argv));
+ } else if (strcmp(*argv, "fwmark") == 0) {
+ NEXT_ARG();
+ if (get_u32(&fwmark, *argv, 0))
+ invarg("invalid fwmark\n", *argv);
+ } else {
+ vti_print_help(lu, argc, argv, stderr);
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ addattr32(n, 1024, IFLA_VTI_IKEY, ikey);
+ addattr32(n, 1024, IFLA_VTI_OKEY, okey);
+ if (is_addrtype_inet_not_unspec(&saddr))
+ addattr_l(n, 1024, IFLA_VTI_LOCAL, saddr.data, saddr.bytelen);
+ if (is_addrtype_inet_not_unspec(&daddr))
+ addattr_l(n, 1024, IFLA_VTI_REMOTE, daddr.data, daddr.bytelen);
+ addattr32(n, 1024, IFLA_VTI_FWMARK, fwmark);
+ if (link)
+ addattr32(n, 1024, IFLA_VTI_LINK, link);
+
+ return 0;
+
+get_failed:
+ fprintf(stderr, "Failed to get existing tunnel info.\n");
+ free(answer);
+ return -1;
+}
+
+static void vti_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ char s2[64];
+
+ if (!tb)
+ return;
+
+ tnl_print_endpoint("remote", tb[IFLA_VTI_REMOTE], AF_INET);
+ tnl_print_endpoint("local", tb[IFLA_VTI_LOCAL], AF_INET);
+
+ if (tb[IFLA_VTI_LINK]) {
+ __u32 link = rta_getattr_u32(tb[IFLA_VTI_LINK]);
+
+ if (link) {
+ print_string(PRINT_ANY, "link", "dev %s ",
+ ll_index_to_name(link));
+ }
+ }
+
+ if (tb[IFLA_VTI_IKEY]) {
+ struct rtattr *rta = tb[IFLA_VTI_IKEY];
+ __u32 key = rta_getattr_u32(rta);
+
+ if (key && inet_ntop(AF_INET, RTA_DATA(rta), s2, sizeof(s2)))
+ print_string(PRINT_ANY, "ikey", "ikey %s ", s2);
+ }
+
+ if (tb[IFLA_VTI_OKEY]) {
+ struct rtattr *rta = tb[IFLA_VTI_OKEY];
+ __u32 key = rta_getattr_u32(rta);
+
+ if (key && inet_ntop(AF_INET, RTA_DATA(rta), s2, sizeof(s2)))
+ print_string(PRINT_ANY, "okey", "okey %s ", s2);
+ }
+
+ if (tb[IFLA_VTI_FWMARK]) {
+ __u32 fwmark = rta_getattr_u32(tb[IFLA_VTI_FWMARK]);
+
+ if (fwmark) {
+ print_0xhex(PRINT_ANY,
+ "fwmark", "fwmark %#llx ", fwmark);
+ }
+ }
+}
+
+struct link_util vti_link_util = {
+ .id = "vti",
+ .maxattr = IFLA_VTI_MAX,
+ .parse_opt = vti_parse_opt,
+ .print_opt = vti_print_opt,
+ .print_help = vti_print_help,
+};
diff --git a/ip/link_vti6.c b/ip/link_vti6.c
new file mode 100644
index 0000000..7362f33
--- /dev/null
+++ b/ip/link_vti6.c
@@ -0,0 +1,215 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * link_vti6.c VTI driver module
+ *
+ * Authors: Herbert Xu <herbert@gondor.apana.org.au>
+ * Saurabh Mohan <saurabh.mohan@vyatta.com> Modified link_gre.c for VTI
+ * Steffen Klassert <steffen.klassert@secunet.com> Modified link_vti.c for IPv6
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+static void vti6_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ fprintf(f,
+ "Usage: ... %-4s [ remote ADDR ]\n"
+ " [ local ADDR ]\n"
+ " [ [i|o]key KEY ]\n"
+ " [ dev PHYS_DEV ]\n"
+ " [ fwmark MARK ]\n"
+ "\n"
+ "Where: ADDR := { IPV6_ADDRESS }\n"
+ " KEY := { DOTTED_QUAD | NUMBER }\n"
+ " MARK := { 0x0..0xffffffff }\n",
+ lu->id);
+}
+
+static int vti6_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ struct ifinfomsg *ifi = NLMSG_DATA(n);
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg i;
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(*ifi)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_GETLINK,
+ .i.ifi_family = preferred_family,
+ .i.ifi_index = ifi->ifi_index,
+ };
+ struct nlmsghdr *answer = NULL;
+ struct rtattr *tb[IFLA_MAX + 1];
+ struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+ struct rtattr *vtiinfo[IFLA_VTI_MAX + 1];
+ __be32 ikey = 0;
+ __be32 okey = 0;
+ inet_prefix saddr, daddr;
+ unsigned int link = 0;
+ __u32 fwmark = 0;
+ int len;
+
+ inet_prefix_reset(&saddr);
+ inet_prefix_reset(&daddr);
+
+ if (!(n->nlmsg_flags & NLM_F_CREATE)) {
+ const struct rtattr *rta;
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0)
+ goto get_failed;
+
+ len = answer->nlmsg_len;
+ len -= NLMSG_LENGTH(sizeof(*ifi));
+ if (len < 0)
+ goto get_failed;
+
+ parse_rtattr(tb, IFLA_MAX, IFLA_RTA(NLMSG_DATA(answer)), len);
+
+ if (!tb[IFLA_LINKINFO])
+ goto get_failed;
+
+ parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+ if (!linkinfo[IFLA_INFO_DATA])
+ goto get_failed;
+
+ parse_rtattr_nested(vtiinfo, IFLA_VTI_MAX,
+ linkinfo[IFLA_INFO_DATA]);
+
+ rta = vtiinfo[IFLA_VTI_LOCAL];
+ if (rta && get_addr_rta(&saddr, rta, AF_INET6))
+ goto get_failed;
+
+ rta = vtiinfo[IFLA_VTI_REMOTE];
+ if (rta && get_addr_rta(&daddr, rta, AF_INET6))
+ goto get_failed;
+
+ if (vtiinfo[IFLA_VTI_IKEY])
+ ikey = rta_getattr_u32(vtiinfo[IFLA_VTI_IKEY]);
+
+ if (vtiinfo[IFLA_VTI_OKEY])
+ okey = rta_getattr_u32(vtiinfo[IFLA_VTI_OKEY]);
+
+ if (vtiinfo[IFLA_VTI_LINK])
+ link = rta_getattr_u8(vtiinfo[IFLA_VTI_LINK]);
+
+ if (vtiinfo[IFLA_VTI_FWMARK])
+ fwmark = rta_getattr_u32(vtiinfo[IFLA_VTI_FWMARK]);
+
+ free(answer);
+ }
+
+ while (argc > 0) {
+ if (!matches(*argv, "key")) {
+ NEXT_ARG();
+ ikey = okey = tnl_parse_key("key", *argv);
+ } else if (!matches(*argv, "ikey")) {
+ NEXT_ARG();
+ ikey = tnl_parse_key("ikey", *argv);
+ } else if (!matches(*argv, "okey")) {
+ NEXT_ARG();
+ okey = tnl_parse_key("okey", *argv);
+ } else if (!matches(*argv, "remote")) {
+ NEXT_ARG();
+ get_addr(&daddr, *argv, AF_INET6);
+ } else if (!matches(*argv, "local")) {
+ NEXT_ARG();
+ get_addr(&saddr, *argv, AF_INET6);
+ } else if (!matches(*argv, "dev")) {
+ NEXT_ARG();
+ link = ll_name_to_index(*argv);
+ if (!link)
+ exit(nodev(*argv));
+ } else if (strcmp(*argv, "fwmark") == 0) {
+ NEXT_ARG();
+ if (get_u32(&fwmark, *argv, 0))
+ invarg("invalid fwmark\n", *argv);
+ } else {
+ vti6_print_help(lu, argc, argv, stderr);
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ addattr32(n, 1024, IFLA_VTI_IKEY, ikey);
+ addattr32(n, 1024, IFLA_VTI_OKEY, okey);
+ if (is_addrtype_inet_not_unspec(&saddr))
+ addattr_l(n, 1024, IFLA_VTI_LOCAL, saddr.data, saddr.bytelen);
+ if (is_addrtype_inet_not_unspec(&daddr))
+ addattr_l(n, 1024, IFLA_VTI_REMOTE, daddr.data, daddr.bytelen);
+ addattr32(n, 1024, IFLA_VTI_FWMARK, fwmark);
+ if (link)
+ addattr32(n, 1024, IFLA_VTI_LINK, link);
+
+ return 0;
+
+get_failed:
+ fprintf(stderr, "Failed to get existing tunnel info.\n");
+ free(answer);
+ return -1;
+}
+
+static void vti6_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+ char s2[64];
+
+ if (!tb)
+ return;
+
+ tnl_print_endpoint("remote", tb[IFLA_VTI_REMOTE], AF_INET6);
+ tnl_print_endpoint("local", tb[IFLA_VTI_LOCAL], AF_INET6);
+
+ if (tb[IFLA_VTI_LINK]) {
+ __u32 link = rta_getattr_u32(tb[IFLA_VTI_LINK]);
+
+ if (link) {
+ print_string(PRINT_ANY, "link", "dev %s ",
+ ll_index_to_name(link));
+ }
+ }
+
+ if (tb[IFLA_VTI_IKEY]) {
+ struct rtattr *rta = tb[IFLA_VTI_IKEY];
+ __u32 key = rta_getattr_u32(rta);
+
+ if (key && inet_ntop(AF_INET, RTA_DATA(rta), s2, sizeof(s2)))
+ print_string(PRINT_ANY, "ikey", "ikey %s ", s2);
+ }
+
+ if (tb[IFLA_VTI_OKEY]) {
+ struct rtattr *rta = tb[IFLA_VTI_OKEY];
+ __u32 key = rta_getattr_u32(rta);
+
+ if (key && inet_ntop(AF_INET, RTA_DATA(rta), s2, sizeof(s2)))
+ print_string(PRINT_ANY, "okey", "okey %s ", s2);
+ }
+
+ if (tb[IFLA_VTI_FWMARK]) {
+ __u32 fwmark = rta_getattr_u32(tb[IFLA_VTI_FWMARK]);
+
+ if (fwmark) {
+ print_0xhex(PRINT_ANY,
+ "fwmark", "fwmark %#llx ", fwmark);
+ }
+ }
+}
+
+struct link_util vti6_link_util = {
+ .id = "vti6",
+ .maxattr = IFLA_VTI_MAX,
+ .parse_opt = vti6_parse_opt,
+ .print_opt = vti6_print_opt,
+ .print_help = vti6_print_help,
+};
diff --git a/ip/link_xfrm.c b/ip/link_xfrm.c
new file mode 100644
index 0000000..d76398c
--- /dev/null
+++ b/ip/link_xfrm.c
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * link_xfrm.c Virtual XFRM Interface driver module
+ *
+ * Authors: Matt Ellison <matt@arroyo.io>
+ */
+
+#include <string.h>
+#include <linux/if_link.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+static void xfrm_print_help(struct link_util *lu, int argc, char **argv,
+ FILE *f)
+{
+ fprintf(f,
+ "Usage: ... %-4s dev [ PHYS_DEV ] [ if_id IF-ID ]\n"
+ " [ external ]\n"
+ "\n"
+ "Where: IF-ID := { 0x1..0xffffffff }\n",
+ lu->id);
+}
+
+static int xfrm_parse_opt(struct link_util *lu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ unsigned int link = 0;
+ bool metadata = false;
+ __u32 if_id = 0;
+
+ while (argc > 0) {
+ if (!matches(*argv, "dev")) {
+ NEXT_ARG();
+ link = ll_name_to_index(*argv);
+ if (!link)
+ exit(nodev(*argv));
+ } else if (!matches(*argv, "if_id")) {
+ NEXT_ARG();
+ if (get_u32(&if_id, *argv, 0))
+ invarg("if_id value is invalid", *argv);
+ else if (!if_id)
+ invarg("if_id value is invalid", *argv);
+ else
+ addattr32(n, 1024, IFLA_XFRM_IF_ID, if_id);
+ } else if (!strcmp(*argv, "external")) {
+ metadata = true;
+ } else {
+ xfrm_print_help(lu, argc, argv, stderr);
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (metadata) {
+ if (if_id || link) {
+ fprintf(stderr, "xfrmi: both 'external' and if_id/link cannot be specified\n");
+ return -1;
+ }
+ addattr(n, 1024, IFLA_XFRM_COLLECT_METADATA);
+ return 0;
+ }
+
+ if (!if_id)
+ missarg("IF_ID");
+
+ if (link)
+ addattr32(n, 1024, IFLA_XFRM_LINK, link);
+
+ return 0;
+}
+
+static void xfrm_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+
+ if (!tb)
+ return;
+
+ if (tb[IFLA_XFRM_COLLECT_METADATA]) {
+ print_bool(PRINT_ANY, "external", "external ", true);
+ return;
+ }
+
+ if (tb[IFLA_XFRM_IF_ID]) {
+ __u32 id = rta_getattr_u32(tb[IFLA_XFRM_IF_ID]);
+
+ print_0xhex(PRINT_ANY, "if_id", "if_id %#llx ", id);
+
+ }
+
+}
+
+struct link_util xfrm_link_util = {
+ .id = "xfrm",
+ .maxattr = IFLA_XFRM_MAX,
+ .parse_opt = xfrm_parse_opt,
+ .print_opt = xfrm_print_opt,
+ .print_help = xfrm_print_help,
+};
diff --git a/ip/nh_common.h b/ip/nh_common.h
new file mode 100644
index 0000000..4d6677e
--- /dev/null
+++ b/ip/nh_common.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __NH_COMMON_H__
+#define __NH_COMMON_H__ 1
+
+#include <list.h>
+
+#define NH_CACHE_SIZE 1024
+
+struct nha_res_grp {
+ __u16 buckets;
+ __u32 idle_timer;
+ __u32 unbalanced_timer;
+ __u64 unbalanced_time;
+};
+
+struct nh_entry {
+ struct hlist_node nh_hash;
+
+ __u32 nh_id;
+ __u32 nh_oif;
+ __u32 nh_flags;
+ __u16 nh_grp_type;
+ __u8 nh_family;
+ __u8 nh_scope;
+ __u8 nh_protocol;
+
+ bool nh_blackhole;
+ bool nh_fdb;
+
+ int nh_gateway_len;
+ union {
+ __be32 ipv4;
+ struct in6_addr ipv6;
+ } nh_gateway;
+
+ struct rtattr *nh_encap;
+ union {
+ struct rtattr rta;
+ __u8 _buf[RTA_LENGTH(sizeof(__u16))];
+ } nh_encap_type;
+
+ bool nh_has_res_grp;
+ struct nha_res_grp nh_res_grp;
+
+ int nh_groups_cnt;
+ struct nexthop_grp *nh_groups;
+};
+
+void print_cache_nexthop_id(FILE *fp, const char *fp_prefix, const char *jsobj,
+ __u32 nh_id);
+int print_cache_nexthop(struct nlmsghdr *n, void *arg, bool process_cache);
+
+#endif /* __NH_COMMON_H__ */
diff --git a/ip/routel b/ip/routel
new file mode 100755
index 0000000..09a9012
--- /dev/null
+++ b/ip/routel
@@ -0,0 +1,62 @@
+#! /usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+#
+# This is simple script to process JSON output from ip route
+# command and format it. Based on earlier shell script version.
+"""Script to parse ip route output into more readable text."""
+
+import sys
+import json
+import getopt
+import subprocess
+
+
+def usage():
+ '''Print usage and exit'''
+ print("Usage: {} [tablenr [raw ip args...]]".format(sys.argv[0]))
+ sys.exit(64)
+
+
+def main():
+ '''Process the arguments'''
+ family = 'inet'
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "h46f:", ["help", "family="])
+ except getopt.GetoptError as err:
+ print(err)
+ usage()
+
+ for opt, arg in opts:
+ if opt in ["-h", "--help"]:
+ usage()
+ elif opt == '-6':
+ family = 'inet6'
+ elif opt == "-4":
+ family = 'inet'
+ elif opt in ["-f", "--family"]:
+ family = arg
+ else:
+ assert False, "unhandled option"
+
+ if not args:
+ args = ['0']
+
+ cmd = ['ip', '-f', family, '-j', 'route', 'list', 'table'] + args
+ process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+ tbl = json.load(process.stdout)
+ if family == 'inet':
+ fmt = '{:15} {:15} {:15} {:8} {:8}{:<16} {}'
+ else:
+ fmt = '{:32} {:32} {:32} {:8} {:8}{:<16} {}'
+
+ # ip route json keys
+ keys = ['dst', 'gateway', 'prefsrc', 'protocol', 'scope', 'dev', 'table']
+ print(fmt.format(*map(lambda x: x.capitalize(), keys)))
+
+ for record in tbl:
+ fields = [record[k] if k in record else '' for k in keys]
+ print(fmt.format(*fields))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ip/rtm_map.c b/ip/rtm_map.c
new file mode 100644
index 0000000..29463ba
--- /dev/null
+++ b/ip/rtm_map.c
@@ -0,0 +1,124 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * rtm_map.c
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include "rt_names.h"
+#include "utils.h"
+
+char *rtnl_rtntype_n2a(int id, char *buf, int len)
+{
+
+ if (numeric) {
+ snprintf(buf, len, "%d", id);
+ return buf;
+ }
+
+ switch (id) {
+ case RTN_UNSPEC:
+ return "none";
+ case RTN_UNICAST:
+ return "unicast";
+ case RTN_LOCAL:
+ return "local";
+ case RTN_BROADCAST:
+ return "broadcast";
+ case RTN_ANYCAST:
+ return "anycast";
+ case RTN_MULTICAST:
+ return "multicast";
+ case RTN_BLACKHOLE:
+ return "blackhole";
+ case RTN_UNREACHABLE:
+ return "unreachable";
+ case RTN_PROHIBIT:
+ return "prohibit";
+ case RTN_THROW:
+ return "throw";
+ case RTN_NAT:
+ return "nat";
+ case RTN_XRESOLVE:
+ return "xresolve";
+ default:
+ snprintf(buf, len, "%d", id);
+ return buf;
+ }
+}
+
+
+int rtnl_rtntype_a2n(int *id, char *arg)
+{
+ char *end;
+ unsigned long res;
+
+ if (strcmp(arg, "local") == 0)
+ res = RTN_LOCAL;
+ else if (strcmp(arg, "nat") == 0)
+ res = RTN_NAT;
+ else if (matches(arg, "broadcast") == 0 ||
+ strcmp(arg, "brd") == 0)
+ res = RTN_BROADCAST;
+ else if (matches(arg, "anycast") == 0)
+ res = RTN_ANYCAST;
+ else if (matches(arg, "multicast") == 0)
+ res = RTN_MULTICAST;
+ else if (matches(arg, "prohibit") == 0)
+ res = RTN_PROHIBIT;
+ else if (matches(arg, "unreachable") == 0)
+ res = RTN_UNREACHABLE;
+ else if (matches(arg, "blackhole") == 0)
+ res = RTN_BLACKHOLE;
+ else if (matches(arg, "xresolve") == 0)
+ res = RTN_XRESOLVE;
+ else if (matches(arg, "unicast") == 0)
+ res = RTN_UNICAST;
+ else if (strcmp(arg, "throw") == 0)
+ res = RTN_THROW;
+ else {
+ res = strtoul(arg, &end, 0);
+ if (!end || end == arg || *end || res > 255)
+ return -1;
+ }
+ *id = res;
+ return 0;
+}
+
+static int get_rt_realms(__u32 *realms, char *arg)
+{
+ __u32 realm = 0;
+ char *p = strchr(arg, '/');
+
+ *realms = 0;
+ if (p) {
+ *p = 0;
+ if (rtnl_rtrealm_a2n(realms, arg)) {
+ *p = '/';
+ return -1;
+ }
+ *realms <<= 16;
+ *p = '/';
+ arg = p+1;
+ }
+ if (*arg && rtnl_rtrealm_a2n(&realm, arg))
+ return -1;
+ *realms |= realm;
+ return 0;
+}
+
+int get_rt_realms_or_raw(__u32 *realms, char *arg)
+{
+ if (!get_rt_realms(realms, arg))
+ return 0;
+
+ return get_unsigned(realms, arg, 0);
+}
diff --git a/ip/rtmon.c b/ip/rtmon.c
new file mode 100644
index 0000000..aad9968
--- /dev/null
+++ b/ip/rtmon.c
@@ -0,0 +1,179 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * rtmon.c RTnetlink listener.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <string.h>
+
+#include "version.h"
+
+#include "utils.h"
+#include "libnetlink.h"
+
+static int init_phase = 1;
+
+static void write_stamp(FILE *fp)
+{
+ char buf[128];
+ struct nlmsghdr *n1 = (void *)buf;
+ struct timeval tv;
+
+ n1->nlmsg_type = NLMSG_TSTAMP;
+ n1->nlmsg_flags = 0;
+ n1->nlmsg_seq = 0;
+ n1->nlmsg_pid = 0;
+ n1->nlmsg_len = NLMSG_LENGTH(4*2);
+ gettimeofday(&tv, NULL);
+ ((__u32 *)NLMSG_DATA(n1))[0] = tv.tv_sec;
+ ((__u32 *)NLMSG_DATA(n1))[1] = tv.tv_usec;
+ fwrite((void *)n1, 1, NLMSG_ALIGN(n1->nlmsg_len), fp);
+}
+
+static int dump_msg(struct rtnl_ctrl_data *ctrl,
+ struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+
+ if (!init_phase)
+ write_stamp(fp);
+ fwrite((void *)n, 1, NLMSG_ALIGN(n->nlmsg_len), fp);
+ fflush(fp);
+ return 0;
+}
+
+static int dump_msg2(struct nlmsghdr *n, void *arg)
+{
+ return dump_msg(NULL, n, arg);
+}
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: rtmon [ OPTIONS ] file FILE [ all | LISTofOBJECTS ]\n"
+ "OPTIONS := { -f[amily] { inet | inet6 | link | help } |\n"
+ " -4 | -6 | -0 | -V[ersion] }\n"
+ "LISTofOBJECTS := [ link ] [ address ] [ route ]\n");
+ exit(-1);
+}
+
+int
+main(int argc, char **argv)
+{
+ FILE *fp;
+ struct rtnl_handle rth;
+ int family = AF_UNSPEC;
+ unsigned int groups = ~0U;
+ int llink = 0;
+ int laddr = 0;
+ int lroute = 0;
+ char *file = NULL;
+
+ while (argc > 1) {
+ if (matches(argv[1], "-family") == 0) {
+ argc--;
+ argv++;
+ if (argc <= 1)
+ usage();
+ if (strcmp(argv[1], "inet") == 0)
+ family = AF_INET;
+ else if (strcmp(argv[1], "inet6") == 0)
+ family = AF_INET6;
+ else if (strcmp(argv[1], "link") == 0)
+ family = AF_INET6;
+ else if (strcmp(argv[1], "help") == 0)
+ usage();
+ else {
+ fprintf(stderr, "Protocol ID \"%s\" is unknown, try \"rtmon help\".\n", argv[1]);
+ exit(-1);
+ }
+ } else if (strcmp(argv[1], "-4") == 0) {
+ family = AF_INET;
+ } else if (strcmp(argv[1], "-6") == 0) {
+ family = AF_INET6;
+ } else if (strcmp(argv[1], "-0") == 0) {
+ family = AF_PACKET;
+ } else if (matches(argv[1], "-Version") == 0) {
+ printf("rtmon utility, iproute2-%s\n", version);
+ exit(0);
+ } else if (matches(argv[1], "file") == 0) {
+ argc--;
+ argv++;
+ if (argc <= 1)
+ usage();
+ file = argv[1];
+ } else if (matches(argv[1], "link") == 0) {
+ llink = 1;
+ groups = 0;
+ } else if (matches(argv[1], "address") == 0) {
+ laddr = 1;
+ groups = 0;
+ } else if (matches(argv[1], "route") == 0) {
+ lroute = 1;
+ groups = 0;
+ } else if (strcmp(argv[1], "all") == 0) {
+ groups = ~0U;
+ } else if (matches(argv[1], "help") == 0) {
+ usage();
+ } else {
+ fprintf(stderr, "Argument \"%s\" is unknown, try \"rtmon help\".\n", argv[1]);
+ exit(-1);
+ }
+ argc--; argv++;
+ }
+
+ if (file == NULL) {
+ fprintf(stderr, "Not enough information: argument \"file\" is required\n");
+ exit(-1);
+ }
+ if (llink)
+ groups |= nl_mgrp(RTNLGRP_LINK);
+ if (laddr) {
+ if (!family || family == AF_INET)
+ groups |= nl_mgrp(RTNLGRP_IPV4_IFADDR);
+ if (!family || family == AF_INET6)
+ groups |= nl_mgrp(RTNLGRP_IPV6_IFADDR);
+ }
+ if (lroute) {
+ if (!family || family == AF_INET)
+ groups |= nl_mgrp(RTNLGRP_IPV4_ROUTE);
+ if (!family || family == AF_INET6)
+ groups |= nl_mgrp(RTNLGRP_IPV6_ROUTE);
+ }
+
+ fp = fopen(file, "w");
+ if (fp == NULL) {
+ perror("Cannot fopen");
+ exit(-1);
+ }
+
+ if (rtnl_open(&rth, groups) < 0)
+ exit(1);
+
+ if (rtnl_linkdump_req(&rth, AF_UNSPEC) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+
+ write_stamp(fp);
+
+ if (rtnl_dump_filter(&rth, dump_msg2, fp) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ return 1;
+ }
+
+ init_phase = 0;
+
+ if (rtnl_listen(&rth, dump_msg, (void *)fp) < 0)
+ exit(2);
+
+ exit(0);
+}
diff --git a/ip/static-syms.c b/ip/static-syms.c
new file mode 100644
index 0000000..47c4092
--- /dev/null
+++ b/ip/static-syms.c
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This file creates a dummy version of dynamic loading
+ * for environments where dynamic linking
+ * is not used or available.
+ */
+
+#include <string.h>
+#include "dlfcn.h"
+
+void *_dlsym(const char *sym)
+{
+#include "static-syms.h"
+ return NULL;
+}
diff --git a/ip/tcp_metrics.c b/ip/tcp_metrics.c
new file mode 100644
index 0000000..9c8fb07
--- /dev/null
+++ b/ip/tcp_metrics.c
@@ -0,0 +1,539 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * tcp_metrics.c "ip tcp_metrics/tcpmetrics"
+ *
+ * Authors: Julian Anastasov <ja@ssi.bg>, August 2012
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <linux/if.h>
+
+#include <linux/genetlink.h>
+#include <linux/tcp_metrics.h>
+
+#include "utils.h"
+#include "ip_common.h"
+#include "libgenl.h"
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip tcp_metrics/tcpmetrics { COMMAND | help }\n"
+ " ip tcp_metrics { show | flush } SELECTOR\n"
+ " ip tcp_metrics delete [ address ] ADDRESS\n"
+ "SELECTOR := [ [ address ] PREFIX ]\n");
+ exit(-1);
+}
+
+/* netlink socket */
+static struct rtnl_handle grth = { .fd = -1 };
+static int genl_family = -1;
+static const double usec_per_sec = 1000000.;
+
+#define TCPM_REQUEST(_req, _bufsiz, _cmd, _flags) \
+ GENL_REQUEST(_req, _bufsiz, genl_family, 0, \
+ TCP_METRICS_GENL_VERSION, _cmd, _flags)
+
+#define CMD_LIST 0x0001 /* list, lst, show */
+#define CMD_DEL 0x0002 /* delete, remove */
+#define CMD_FLUSH 0x0004 /* flush */
+
+static const struct {
+ const char *name;
+ int code;
+} cmds[] = {
+ { "list", CMD_LIST },
+ { "lst", CMD_LIST },
+ { "show", CMD_LIST },
+ { "delete", CMD_DEL },
+ { "remove", CMD_DEL },
+ { "flush", CMD_FLUSH },
+};
+
+static const char *metric_name[TCP_METRIC_MAX + 1] = {
+ [TCP_METRIC_RTT] = "rtt",
+ [TCP_METRIC_RTTVAR] = "rttvar",
+ [TCP_METRIC_SSTHRESH] = "ssthresh",
+ [TCP_METRIC_CWND] = "cwnd",
+ [TCP_METRIC_REORDERING] = "reordering",
+};
+
+static struct {
+ int flushed;
+ char *flushb;
+ int flushp;
+ int flushe;
+ int cmd;
+ inet_prefix daddr;
+ inet_prefix saddr;
+} f;
+
+static int flush_update(void)
+{
+ if (rtnl_send_check(&grth, f.flushb, f.flushp) < 0) {
+ perror("Failed to send flush request\n");
+ return -1;
+ }
+ f.flushp = 0;
+ return 0;
+}
+
+static void print_tcp_metrics(struct rtattr *a)
+{
+ struct rtattr *m[TCP_METRIC_MAX + 1 + 1];
+ unsigned long rtt = 0, rttvar = 0;
+ int i;
+
+ parse_rtattr_nested(m, TCP_METRIC_MAX + 1, a);
+
+ for (i = 0; i < TCP_METRIC_MAX + 1; i++) {
+ const char *name;
+ __u32 val;
+ SPRINT_BUF(b1);
+
+ a = m[i + 1];
+ if (!a)
+ continue;
+
+ val = rta_getattr_u32(a);
+
+ switch (i) {
+ case TCP_METRIC_RTT:
+ if (!rtt)
+ rtt = (val * 1000UL) >> 3;
+ continue;
+ case TCP_METRIC_RTTVAR:
+ if (!rttvar)
+ rttvar = (val * 1000UL) >> 2;
+ continue;
+ case TCP_METRIC_RTT_US:
+ rtt = val >> 3;
+ continue;
+
+ case TCP_METRIC_RTTVAR_US:
+ rttvar = val >> 2;
+ continue;
+
+ case TCP_METRIC_SSTHRESH:
+ case TCP_METRIC_CWND:
+ case TCP_METRIC_REORDERING:
+ name = metric_name[i];
+ break;
+
+ default:
+ snprintf(b1, sizeof(b1),
+ " metric_%d ", i);
+ name = b1;
+ }
+
+
+ print_uint(PRINT_JSON, name, NULL, val);
+ print_string(PRINT_FP, NULL, " %s ", name);
+ print_uint(PRINT_FP, NULL, "%u", val);
+ }
+
+ if (rtt) {
+ print_float(PRINT_JSON, "rtt", NULL,
+ (double)rtt / usec_per_sec);
+ print_u64(PRINT_FP, NULL,
+ " rtt %luus", rtt);
+ }
+ if (rttvar) {
+ print_float(PRINT_JSON, "rttvar", NULL,
+ (double) rttvar / usec_per_sec);
+ print_u64(PRINT_FP, NULL,
+ " rttvar %luus", rttvar);
+ }
+}
+
+static int process_msg(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *) arg;
+ struct genlmsghdr *ghdr;
+ struct rtattr *attrs[TCP_METRICS_ATTR_MAX + 1], *a;
+ const char *h;
+ int len = n->nlmsg_len;
+ inet_prefix daddr, saddr;
+ int atype, stype;
+
+ if (n->nlmsg_type != genl_family)
+ return -1;
+
+ len -= NLMSG_LENGTH(GENL_HDRLEN);
+ if (len < 0)
+ return -1;
+
+ ghdr = NLMSG_DATA(n);
+ if (ghdr->cmd != TCP_METRICS_CMD_GET)
+ return 0;
+
+ parse_rtattr(attrs, TCP_METRICS_ATTR_MAX, (void *) ghdr + GENL_HDRLEN,
+ len);
+
+ if (attrs[TCP_METRICS_ATTR_ADDR_IPV4]) {
+ if (f.daddr.family && f.daddr.family != AF_INET)
+ return 0;
+ a = attrs[TCP_METRICS_ATTR_ADDR_IPV4];
+ daddr.family = AF_INET;
+ atype = TCP_METRICS_ATTR_ADDR_IPV4;
+ } else if (attrs[TCP_METRICS_ATTR_ADDR_IPV6]) {
+ if (f.daddr.family && f.daddr.family != AF_INET6)
+ return 0;
+ a = attrs[TCP_METRICS_ATTR_ADDR_IPV6];
+ daddr.family = AF_INET6;
+ atype = TCP_METRICS_ATTR_ADDR_IPV6;
+ } else {
+ return 0;
+ }
+
+ if (get_addr_rta(&daddr, a, daddr.family))
+ return 0;
+
+ if (f.daddr.family && f.daddr.bitlen >= 0 &&
+ inet_addr_match(&daddr, &f.daddr, f.daddr.bitlen))
+ return 0;
+
+ if (attrs[TCP_METRICS_ATTR_SADDR_IPV4]) {
+ if (f.saddr.family && f.saddr.family != AF_INET)
+ return 0;
+ a = attrs[TCP_METRICS_ATTR_SADDR_IPV4];
+ saddr.family = AF_INET;
+ stype = TCP_METRICS_ATTR_SADDR_IPV4;
+ } else if (attrs[TCP_METRICS_ATTR_SADDR_IPV6]) {
+ if (f.saddr.family && f.saddr.family != AF_INET6)
+ return 0;
+ a = attrs[TCP_METRICS_ATTR_SADDR_IPV6];
+ saddr.family = AF_INET6;
+ stype = TCP_METRICS_ATTR_SADDR_IPV6;
+ } else {
+ saddr.family = AF_UNSPEC;
+ stype = 0;
+ }
+
+ /* Only get/check for the source-address if the kernel supports it. */
+ if (saddr.family) {
+ if (get_addr_rta(&saddr, a, saddr.family))
+ return 0;
+
+ if (f.saddr.family && f.saddr.bitlen >= 0 &&
+ inet_addr_match(&saddr, &f.saddr, f.saddr.bitlen))
+ return 0;
+ }
+
+ if (f.flushb) {
+ struct nlmsghdr *fn;
+
+ TCPM_REQUEST(req2, 128, TCP_METRICS_CMD_DEL, NLM_F_REQUEST);
+
+ addattr_l(&req2.n, sizeof(req2), atype, daddr.data,
+ daddr.bytelen);
+ if (saddr.family)
+ addattr_l(&req2.n, sizeof(req2), stype, saddr.data,
+ saddr.bytelen);
+
+ if (NLMSG_ALIGN(f.flushp) + req2.n.nlmsg_len > f.flushe) {
+ if (flush_update())
+ return -1;
+ }
+ fn = (struct nlmsghdr *) (f.flushb + NLMSG_ALIGN(f.flushp));
+ memcpy(fn, &req2.n, req2.n.nlmsg_len);
+ fn->nlmsg_seq = ++grth.seq;
+ f.flushp = (((char *) fn) + req2.n.nlmsg_len) - f.flushb;
+ f.flushed++;
+ if (show_stats < 2)
+ return 0;
+ }
+
+ open_json_object(NULL);
+ if (f.cmd & (CMD_DEL | CMD_FLUSH))
+ print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+ h = format_host(daddr.family, daddr.bytelen, daddr.data);
+ print_color_string(PRINT_ANY,
+ ifa_family_color(daddr.family),
+ "dst", "%s", h);
+
+ a = attrs[TCP_METRICS_ATTR_AGE];
+ if (a) {
+ __u64 val = rta_getattr_u64(a);
+ double age = val / 1000.;
+
+ print_float(PRINT_ANY, "age",
+ " age %.03fsec", age);
+ }
+
+ a = attrs[TCP_METRICS_ATTR_TW_TS_STAMP];
+ if (a) {
+ __s32 val = (__s32) rta_getattr_u32(a);
+ __u32 tsval;
+ char tw_ts[64];
+
+ a = attrs[TCP_METRICS_ATTR_TW_TSVAL];
+ tsval = a ? rta_getattr_u32(a) : 0;
+ snprintf(tw_ts, sizeof(tw_ts),
+ "%u/%d", tsval, val);
+ print_string(PRINT_ANY, "tw_ts_stamp",
+ " tw_ts %s ago", tw_ts);
+ }
+
+ if (attrs[TCP_METRICS_ATTR_VALS])
+ print_tcp_metrics(attrs[TCP_METRICS_ATTR_VALS]);
+
+ a = attrs[TCP_METRICS_ATTR_FOPEN_MSS];
+ if (a) {
+ print_uint(PRINT_ANY, "fopen_miss", " fo_mss %u",
+ rta_getattr_u16(a));
+ }
+
+ a = attrs[TCP_METRICS_ATTR_FOPEN_SYN_DROPS];
+ if (a) {
+ __u16 syn_loss = rta_getattr_u16(a);
+ double ts;
+
+ a = attrs[TCP_METRICS_ATTR_FOPEN_SYN_DROP_TS];
+ ts = a ? rta_getattr_u64(a) : 0;
+
+ print_uint(PRINT_ANY, "fopen_syn_drops",
+ " fo_syn_drops %u", syn_loss);
+ print_float(PRINT_ANY, "fopen_syn_drop_ts",
+ "/%.03fusec ago",
+ ts / 1000000.);
+ }
+
+ a = attrs[TCP_METRICS_ATTR_FOPEN_COOKIE];
+ if (a) {
+ char cookie[32 + 1];
+ unsigned char *ptr = RTA_DATA(a);
+ int i, max = RTA_PAYLOAD(a);
+
+ if (max > 16)
+ max = 16;
+ cookie[0] = 0;
+ for (i = 0; i < max; i++)
+ sprintf(cookie + i + i, "%02x", ptr[i]);
+
+ print_string(PRINT_ANY, "fo_cookie",
+ " fo_cookie %s", cookie);
+ }
+
+ if (saddr.family) {
+ const char *src;
+
+ src = format_host(saddr.family, saddr.bytelen, saddr.data);
+ print_string(PRINT_ANY, "source",
+ " source %s", src);
+ }
+
+ print_string(PRINT_FP, NULL, "\n", "");
+ close_json_object();
+ fflush(fp);
+ return 0;
+}
+
+static int tcpm_do_cmd(int cmd, int argc, char **argv)
+{
+ TCPM_REQUEST(req, 1024, TCP_METRICS_CMD_GET, NLM_F_REQUEST);
+ struct nlmsghdr *answer;
+ int atype = -1, stype = -1;
+ int ack;
+
+ memset(&f, 0, sizeof(f));
+ f.daddr.bitlen = -1;
+ f.daddr.family = preferred_family;
+ f.saddr.bitlen = -1;
+ f.saddr.family = preferred_family;
+
+ switch (preferred_family) {
+ case AF_UNSPEC:
+ case AF_INET:
+ case AF_INET6:
+ break;
+ default:
+ fprintf(stderr, "Unsupported protocol family: %d\n", preferred_family);
+ return -1;
+ }
+
+ for (; argc > 0; argc--, argv++) {
+ if (strcmp(*argv, "src") == 0 ||
+ strcmp(*argv, "source") == 0) {
+ char *who = *argv;
+
+ NEXT_ARG();
+ if (matches(*argv, "help") == 0)
+ usage();
+ if (f.saddr.bitlen >= 0)
+ duparg2(who, *argv);
+
+ get_prefix(&f.saddr, *argv, preferred_family);
+ if (f.saddr.bytelen && f.saddr.bytelen * 8 == f.saddr.bitlen) {
+ if (f.saddr.family == AF_INET)
+ stype = TCP_METRICS_ATTR_SADDR_IPV4;
+ else if (f.saddr.family == AF_INET6)
+ stype = TCP_METRICS_ATTR_SADDR_IPV6;
+ }
+
+ if (stype < 0) {
+ fprintf(stderr, "Error: a specific IP address is expected rather than \"%s\"\n",
+ *argv);
+ return -1;
+ }
+ } else {
+ char *who = "address";
+
+ if (strcmp(*argv, "addr") == 0 ||
+ strcmp(*argv, "address") == 0) {
+ who = *argv;
+ NEXT_ARG();
+ }
+ if (matches(*argv, "help") == 0)
+ usage();
+ if (f.daddr.bitlen >= 0)
+ duparg2(who, *argv);
+
+ get_prefix(&f.daddr, *argv, preferred_family);
+ if (f.daddr.bytelen && f.daddr.bytelen * 8 == f.daddr.bitlen) {
+ if (f.daddr.family == AF_INET)
+ atype = TCP_METRICS_ATTR_ADDR_IPV4;
+ else if (f.daddr.family == AF_INET6)
+ atype = TCP_METRICS_ATTR_ADDR_IPV6;
+ }
+ if ((CMD_DEL & cmd) && atype < 0) {
+ fprintf(stderr, "Error: a specific IP address is expected rather than \"%s\"\n",
+ *argv);
+ return -1;
+ }
+ }
+ argc--; argv++;
+ }
+
+ if (cmd == CMD_DEL && atype < 0)
+ missarg("address");
+
+ /* flush for exact address ? Single del */
+ if (cmd == CMD_FLUSH && atype >= 0)
+ cmd = CMD_DEL;
+
+ /* flush for all addresses ? Single del without address */
+ if (cmd == CMD_FLUSH && f.daddr.bitlen <= 0 &&
+ f.saddr.bitlen <= 0 && preferred_family == AF_UNSPEC) {
+ cmd = CMD_DEL;
+ req.g.cmd = TCP_METRICS_CMD_DEL;
+ ack = 1;
+ } else if (cmd == CMD_DEL) {
+ req.g.cmd = TCP_METRICS_CMD_DEL;
+ ack = 1;
+ } else { /* CMD_FLUSH, CMD_LIST */
+ ack = 0;
+ }
+
+ if (genl_init_handle(&grth, TCP_METRICS_GENL_NAME, &genl_family))
+ exit(1);
+ req.n.nlmsg_type = genl_family;
+
+ if (!(cmd & CMD_FLUSH) && (atype >= 0 || (cmd & CMD_DEL))) {
+ if (ack)
+ req.n.nlmsg_flags |= NLM_F_ACK;
+ if (atype >= 0)
+ addattr_l(&req.n, sizeof(req), atype, &f.daddr.data,
+ f.daddr.bytelen);
+ if (stype >= 0)
+ addattr_l(&req.n, sizeof(req), stype, &f.saddr.data,
+ f.saddr.bytelen);
+ } else {
+ req.n.nlmsg_flags |= NLM_F_DUMP;
+ }
+
+ f.cmd = cmd;
+ if (cmd & CMD_FLUSH) {
+ int round = 0;
+ char flushb[4096-512];
+
+ f.flushb = flushb;
+ f.flushp = 0;
+ f.flushe = sizeof(flushb);
+
+ for (;;) {
+ req.n.nlmsg_seq = grth.dump = ++grth.seq;
+ if (rtnl_send(&grth, &req, req.n.nlmsg_len) < 0) {
+ perror("Failed to send flush request");
+ exit(1);
+ }
+ f.flushed = 0;
+ if (rtnl_dump_filter(&grth, process_msg, stdout) < 0) {
+ fprintf(stderr, "Flush terminated\n");
+ exit(1);
+ }
+ if (f.flushed == 0) {
+ if (round == 0) {
+ fprintf(stderr, "Nothing to flush.\n");
+ } else if (show_stats)
+ printf("*** Flush is complete after %d round%s ***\n",
+ round, round > 1 ? "s" : "");
+ fflush(stdout);
+ return 0;
+ }
+ round++;
+ if (flush_update() < 0)
+ exit(1);
+ if (show_stats) {
+ printf("\n*** Round %d, deleting %d entries ***\n",
+ round, f.flushed);
+ fflush(stdout);
+ }
+ }
+ return 0;
+ }
+
+ if (ack) {
+ if (rtnl_talk(&grth, &req.n, NULL) < 0)
+ return -2;
+ } else if (atype >= 0) {
+ if (rtnl_talk(&grth, &req.n, &answer) < 0)
+ return -2;
+ if (process_msg(answer, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ free(answer);
+ } else {
+ req.n.nlmsg_seq = grth.dump = ++grth.seq;
+ if (rtnl_send(&grth, &req, req.n.nlmsg_len) < 0) {
+ perror("Failed to send dump request");
+ exit(1);
+ }
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&grth, process_msg, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ delete_json_obj();
+ }
+ return 0;
+}
+
+int do_tcp_metrics(int argc, char **argv)
+{
+ int i;
+
+ if (argc < 1)
+ return tcpm_do_cmd(CMD_LIST, 0, NULL);
+ for (i = 0; i < ARRAY_SIZE(cmds); i++) {
+ if (matches(argv[0], cmds[i].name) == 0)
+ return tcpm_do_cmd(cmds[i].code, argc-1, argv+1);
+ }
+ if (matches(argv[0], "help") == 0)
+ usage();
+
+ fprintf(stderr, "Command \"%s\" is unknown, try \"ip tcp_metrics help\".\n",
+ *argv);
+ exit(-1);
+}
diff --git a/ip/tunnel.c b/ip/tunnel.c
new file mode 100644
index 0000000..c5c7a31
--- /dev/null
+++ b/ip/tunnel.c
@@ -0,0 +1,434 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C)2006 USAGI/WIDE Project
+ *
+ * split from ip_tunnel.c
+ *
+ * Author:
+ * Masahide NAKAMURA @USAGI
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <linux/if.h>
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include <linux/if_arp.h>
+
+#include "utils.h"
+#include "tunnel.h"
+#include "json_print.h"
+
+const char *tnl_strproto(__u8 proto)
+{
+ switch (proto) {
+ case IPPROTO_IPIP:
+ return "ip";
+ case IPPROTO_GRE:
+ return "gre";
+ case IPPROTO_IPV6:
+ return "ipv6";
+ case IPPROTO_ESP:
+ return "esp";
+ case IPPROTO_MPLS:
+ return "mpls";
+ case 0:
+ return "any";
+ default:
+ return "unknown";
+ }
+}
+
+int tnl_get_ioctl(const char *basedev, void *p)
+{
+ struct ifreq ifr;
+ int fd;
+ int err;
+
+ strlcpy(ifr.ifr_name, basedev, IFNAMSIZ);
+ ifr.ifr_ifru.ifru_data = (void *)p;
+
+ fd = socket(preferred_family, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ fprintf(stderr, "create socket failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ err = ioctl(fd, SIOCGETTUNNEL, &ifr);
+ if (err)
+ fprintf(stderr, "get tunnel \"%s\" failed: %s\n", basedev,
+ strerror(errno));
+
+ close(fd);
+ return err;
+}
+
+int tnl_add_ioctl(int cmd, const char *basedev, const char *name, void *p)
+{
+ struct ifreq ifr;
+ int fd;
+ int err;
+
+ if (cmd == SIOCCHGTUNNEL && name[0])
+ strlcpy(ifr.ifr_name, name, IFNAMSIZ);
+ else
+ strlcpy(ifr.ifr_name, basedev, IFNAMSIZ);
+ ifr.ifr_ifru.ifru_data = p;
+
+ fd = socket(preferred_family, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ fprintf(stderr, "create socket failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ err = ioctl(fd, cmd, &ifr);
+ if (err)
+ fprintf(stderr, "add tunnel \"%s\" failed: %s\n", ifr.ifr_name,
+ strerror(errno));
+ close(fd);
+ return err;
+}
+
+int tnl_del_ioctl(const char *basedev, const char *name, void *p)
+{
+ struct ifreq ifr;
+ int fd;
+ int err;
+
+ if (name[0])
+ strlcpy(ifr.ifr_name, name, IFNAMSIZ);
+ else
+ strlcpy(ifr.ifr_name, basedev, IFNAMSIZ);
+
+ ifr.ifr_ifru.ifru_data = p;
+
+ fd = socket(preferred_family, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ fprintf(stderr, "create socket failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ err = ioctl(fd, SIOCDELTUNNEL, &ifr);
+ if (err)
+ fprintf(stderr, "delete tunnel \"%s\" failed: %s\n",
+ ifr.ifr_name, strerror(errno));
+ close(fd);
+ return err;
+}
+
+static int tnl_gen_ioctl(int cmd, const char *name,
+ void *p, int skiperr)
+{
+ struct ifreq ifr;
+ int fd;
+ int err;
+
+ strlcpy(ifr.ifr_name, name, IFNAMSIZ);
+ ifr.ifr_ifru.ifru_data = p;
+
+ fd = socket(preferred_family, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ fprintf(stderr, "create socket failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ err = ioctl(fd, cmd, &ifr);
+ if (err && errno != skiperr)
+ fprintf(stderr, "%s: ioctl %x failed: %s\n", name,
+ cmd, strerror(errno));
+ close(fd);
+ return err;
+}
+
+int tnl_prl_ioctl(int cmd, const char *name, void *p)
+{
+ return tnl_gen_ioctl(cmd, name, p, -1);
+}
+
+int tnl_6rd_ioctl(int cmd, const char *name, void *p)
+{
+ return tnl_gen_ioctl(cmd, name, p, -1);
+}
+
+int tnl_ioctl_get_6rd(const char *name, void *p)
+{
+ return tnl_gen_ioctl(SIOCGET6RD, name, p, EINVAL);
+}
+
+__be32 tnl_parse_key(const char *name, const char *key)
+{
+ unsigned int uval;
+
+ if (strchr(key, '.'))
+ return get_addr32(key);
+
+ if (get_unsigned(&uval, key, 0) < 0) {
+ fprintf(stderr,
+ "invalid value for \"%s\": \"%s\"; it should be an unsigned integer\n",
+ name, key);
+ exit(-1);
+ }
+ return htonl(uval);
+}
+
+static const char *tnl_encap_str(const char *name, int enabled, int port)
+{
+ static const char ne[][sizeof("no")] = {
+ [0] = "no",
+ [1] = "",
+ };
+ static char buf[32];
+ char b1[16];
+ const char *val;
+
+ if (!port) {
+ val = "auto ";
+ } else if (port < 0) {
+ val = "";
+ } else {
+ snprintf(b1, sizeof(b1), "%u ", port - 1);
+ val = b1;
+ }
+
+ snprintf(buf, sizeof(buf), "%sencap-%s %s", ne[!!enabled], name, val);
+ return buf;
+}
+
+void tnl_print_encap(struct rtattr *tb[],
+ int encap_type, int encap_flags,
+ int encap_sport, int encap_dport)
+{
+ __u16 type, flags, sport, dport;
+
+ if (!tb[encap_type])
+ return;
+
+ type = rta_getattr_u16(tb[encap_type]);
+ if (type == TUNNEL_ENCAP_NONE)
+ return;
+
+ flags = rta_getattr_u16(tb[encap_flags]);
+ sport = rta_getattr_u16(tb[encap_sport]);
+ dport = rta_getattr_u16(tb[encap_dport]);
+
+ open_json_object("encap");
+ print_string(PRINT_FP, NULL, "encap ", NULL);
+
+ switch (type) {
+ case TUNNEL_ENCAP_FOU:
+ print_string(PRINT_ANY, "type", "%s ", "fou");
+ break;
+ case TUNNEL_ENCAP_GUE:
+ print_string(PRINT_ANY, "type", "%s ", "gue");
+ break;
+ default:
+ print_null(PRINT_ANY, "type", "%s ", "unknown");
+ break;
+ }
+
+ if (is_json_context()) {
+ print_uint(PRINT_JSON, "sport", NULL, ntohs(sport));
+ print_uint(PRINT_JSON, "dport", NULL, ntohs(dport));
+ print_bool(PRINT_JSON, "csum", NULL,
+ flags & TUNNEL_ENCAP_FLAG_CSUM);
+ print_bool(PRINT_JSON, "csum6", NULL,
+ flags & TUNNEL_ENCAP_FLAG_CSUM6);
+ print_bool(PRINT_JSON, "remcsum", NULL,
+ flags & TUNNEL_ENCAP_FLAG_REMCSUM);
+ close_json_object();
+ } else {
+ int t;
+
+ t = sport ? ntohs(sport) + 1 : 0;
+ print_string(PRINT_FP, NULL, "%s",
+ tnl_encap_str("sport", 1, t));
+
+ t = ntohs(dport) + 1;
+ print_string(PRINT_FP, NULL, "%s",
+ tnl_encap_str("dport", 1, t));
+
+ t = flags & TUNNEL_ENCAP_FLAG_CSUM;
+ print_string(PRINT_FP, NULL, "%s",
+ tnl_encap_str("csum", t, -1));
+
+ t = flags & TUNNEL_ENCAP_FLAG_CSUM6;
+ print_string(PRINT_FP, NULL, "%s",
+ tnl_encap_str("csum6", t, -1));
+
+ t = flags & TUNNEL_ENCAP_FLAG_REMCSUM;
+ print_string(PRINT_FP, NULL, "%s",
+ tnl_encap_str("remcsum", t, -1));
+ }
+}
+
+void tnl_print_endpoint(const char *name, const struct rtattr *rta, int family)
+{
+ const char *value;
+ inet_prefix dst;
+
+ if (!rta) {
+ value = "any";
+ } else if (get_addr_rta(&dst, rta, family)) {
+ value = "unknown";
+ } else if (dst.flags & ADDRTYPE_UNSPEC) {
+ value = "any";
+ } else {
+ value = format_host(family, dst.bytelen, dst.data);
+ if (!value)
+ value = "unknown";
+ }
+
+ print_string_name_value(name, value);
+ print_string(PRINT_FP, NULL, " ", NULL);
+}
+
+void tnl_print_gre_flags(__u8 proto,
+ __be16 i_flags, __be16 o_flags,
+ __be32 i_key, __be32 o_key)
+{
+ if ((i_flags & GRE_KEY) && (o_flags & GRE_KEY) &&
+ o_key == i_key) {
+ print_uint(PRINT_ANY, "key", " key %u", ntohl(i_key));
+ } else {
+ if (i_flags & GRE_KEY)
+ print_uint(PRINT_ANY, "ikey", " ikey %u", ntohl(i_key));
+ if (o_flags & GRE_KEY)
+ print_uint(PRINT_ANY, "okey", " okey %u", ntohl(o_key));
+ }
+
+ if (proto != IPPROTO_GRE)
+ return;
+
+ open_json_array(PRINT_JSON, "flags");
+ if (i_flags & GRE_SEQ) {
+ if (is_json_context())
+ print_string(PRINT_JSON, NULL, "%s", "rx_drop_ooseq");
+ else
+ printf("%s Drop packets out of sequence.", _SL_);
+ }
+ if (i_flags & GRE_CSUM) {
+ if (is_json_context())
+ print_string(PRINT_JSON, NULL, "%s", "rx_csum");
+ else
+ printf("%s Checksum in received packet is required.", _SL_);
+ }
+ if (o_flags & GRE_SEQ) {
+ if (is_json_context())
+ print_string(PRINT_JSON, NULL, "%s", "tx_seq");
+ else
+ printf("%s Sequence packets on output.", _SL_);
+ }
+ if (o_flags & GRE_CSUM) {
+ if (is_json_context())
+ print_string(PRINT_JSON, NULL, "%s", "tx_csum");
+ else
+ printf("%s Checksum output packets.", _SL_);
+ }
+ close_json_array(PRINT_JSON, NULL);
+}
+
+static void tnl_print_stats(const struct rtnl_link_stats64 *s)
+{
+ printf("%s", _SL_);
+ printf("RX: Packets Bytes Errors CsumErrs OutOfSeq Mcasts%s", _SL_);
+ printf(" %-10lld %-12lld %-6lld %-8lld %-8lld %-8lld%s",
+ s->rx_packets, s->rx_bytes, s->rx_errors, s->rx_frame_errors,
+ s->rx_fifo_errors, s->multicast, _SL_);
+ printf("TX: Packets Bytes Errors DeadLoop NoRoute NoBufs%s", _SL_);
+ printf(" %-10lld %-12lld %-6lld %-8lld %-8lld %-6lld",
+ s->tx_packets, s->tx_bytes, s->tx_errors, s->collisions,
+ s->tx_carrier_errors, s->tx_dropped);
+}
+
+static int print_nlmsg_tunnel(struct nlmsghdr *n, void *arg)
+{
+ struct tnl_print_nlmsg_info *info = arg;
+ struct ifinfomsg *ifi = NLMSG_DATA(n);
+ struct rtattr *tb[IFLA_MAX+1];
+ const char *name, *n1;
+
+ if (n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK)
+ return 0;
+
+ if (n->nlmsg_len < NLMSG_LENGTH(sizeof(*ifi)))
+ return -1;
+
+ if (preferred_family == AF_INET) {
+ switch (ifi->ifi_type) {
+ case ARPHRD_TUNNEL:
+ case ARPHRD_IPGRE:
+ case ARPHRD_SIT:
+ break;
+ default:
+ return 0;
+ }
+ } else {
+ switch (ifi->ifi_type) {
+ case ARPHRD_TUNNEL6:
+ case ARPHRD_IP6GRE:
+ break;
+ default:
+ return 0;
+ }
+ }
+
+ parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), IFLA_PAYLOAD(n));
+
+ if (!tb[IFLA_IFNAME])
+ return 0;
+
+ name = rta_getattr_str(tb[IFLA_IFNAME]);
+
+ /* Assume p1->name[IFNAMSIZ] is first field of structure */
+ n1 = info->p1;
+ if (n1[0] && strcmp(n1, name))
+ return 0;
+
+ info->ifi = ifi;
+ info->init(info);
+
+ /* TODO: parse netlink attributes */
+ if (tnl_get_ioctl(name, info->p2))
+ return 0;
+
+ if (!info->match(info))
+ return 0;
+
+ info->print(info->p2);
+ if (show_stats) {
+ struct rtnl_link_stats64 s;
+
+ if (get_rtnl_link_stats_rta(&s, tb) <= 0)
+ return -1;
+
+ tnl_print_stats(&s);
+ }
+ fputc('\n', stdout);
+
+ return 0;
+}
+
+int do_tunnels_list(struct tnl_print_nlmsg_info *info)
+{
+ new_json_obj(json);
+ if (rtnl_linkdump_req(&rth, preferred_family) < 0) {
+ perror("Cannot send dump request\n");
+ delete_json_obj();
+ return -1;
+ }
+
+ if (rtnl_dump_filter(&rth, print_nlmsg_tunnel, info) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ delete_json_obj();
+ return -1;
+ }
+ delete_json_obj();
+
+ return 0;
+}
diff --git a/ip/tunnel.h b/ip/tunnel.h
new file mode 100644
index 0000000..0c9852e
--- /dev/null
+++ b/ip/tunnel.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C)2006 USAGI/WIDE Project
+ *
+ * Author:
+ * Masahide NAKAMURA @USAGI
+ */
+#ifndef __TUNNEL_H__
+#define __TUNNEL_H__ 1
+
+#include <stdbool.h>
+#include <linux/types.h>
+
+struct rtattr;
+struct ifinfomsg;
+
+extern struct rtnl_handle rth;
+
+struct tnl_print_nlmsg_info {
+ const struct ifinfomsg *ifi;
+ const void *p1;
+ void *p2;
+
+ void (*init)(const struct tnl_print_nlmsg_info *info);
+ bool (*match)(const struct tnl_print_nlmsg_info *info);
+ void (*print)(const void *t);
+};
+
+int do_tunnels_list(struct tnl_print_nlmsg_info *info);
+
+const char *tnl_strproto(__u8 proto);
+
+int tnl_get_ioctl(const char *basedev, void *p);
+int tnl_add_ioctl(int cmd, const char *basedev, const char *name, void *p);
+int tnl_del_ioctl(const char *basedev, const char *name, void *p);
+int tnl_prl_ioctl(int cmd, const char *name, void *p);
+int tnl_6rd_ioctl(int cmd, const char *name, void *p);
+int tnl_ioctl_get_6rd(const char *name, void *p);
+__be32 tnl_parse_key(const char *name, const char *key);
+void tnl_print_encap(struct rtattr *tb[],
+ int encap_type, int encap_flags,
+ int encap_sport, int encap_dport);
+void tnl_print_endpoint(const char *name,
+ const struct rtattr *rta, int family);
+void tnl_print_gre_flags(__u8 proto,
+ __be16 i_flags, __be16 o_flags,
+ __be32 i_key, __be32 o_key);
+
+#endif
diff --git a/ip/xfrm.h b/ip/xfrm.h
new file mode 100644
index 0000000..5238fc8
--- /dev/null
+++ b/ip/xfrm.h
@@ -0,0 +1,130 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C)2004 USAGI/WIDE Project
+ *
+ * Authors:
+ * Masahide NAKAMURA @USAGI
+ */
+
+#ifndef __XFRM_H__
+#define __XFRM_H__ 1
+
+#include <stdio.h>
+#include <sys/socket.h>
+#include <linux/in.h>
+#include <linux/xfrm.h>
+#include <linux/ipsec.h>
+
+#ifndef IPPROTO_MH
+#define IPPROTO_MH 135
+#endif
+
+#define XFRMS_RTA(x) ((struct rtattr*)(((char*)(x)) + NLMSG_ALIGN(sizeof(struct xfrm_usersa_info))))
+#define XFRMS_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct xfrm_usersa_info))
+
+#define XFRMP_RTA(x) ((struct rtattr*)(((char*)(x)) + NLMSG_ALIGN(sizeof(struct xfrm_userpolicy_info))))
+#define XFRMP_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct xfrm_userpoilcy_info))
+
+#define XFRMSID_RTA(x) ((struct rtattr*)(((char*)(x)) + NLMSG_ALIGN(sizeof(struct xfrm_usersa_id))))
+#define XFRMSID_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct xfrm_usersa_id))
+
+#define XFRMPID_RTA(x) ((struct rtattr*)(((char*)(x)) + NLMSG_ALIGN(sizeof(struct xfrm_userpolicy_id))))
+#define XFRMPID_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct xfrm_userpoilcy_id))
+
+#define XFRMACQ_RTA(x) ((struct rtattr*)(((char*)(x)) + NLMSG_ALIGN(sizeof(struct xfrm_user_acquire))))
+#define XFRMEXP_RTA(x) ((struct rtattr*)(((char*)(x)) + NLMSG_ALIGN(sizeof(struct xfrm_user_expire))))
+#define XFRMPEXP_RTA(x) ((struct rtattr*)(((char*)(x)) + NLMSG_ALIGN(sizeof(struct xfrm_user_polexpire))))
+
+#define XFRMREP_RTA(x) ((struct rtattr*)(((char*)(x)) + NLMSG_ALIGN(sizeof(struct xfrm_user_report))))
+
+#define XFRMSAPD_RTA(x) ((struct rtattr*)(((char*)(x)) + NLMSG_ALIGN(sizeof(__u32))))
+#define XFRM_FLAG_PRINT(fp, flags, f, s) \
+ do { \
+ if (flags & f) { \
+ flags &= ~f; \
+ fprintf(fp, s "%s", (flags ? " " : "")); \
+ } \
+ } while(0)
+
+struct xfrm_buffer {
+ char *buf;
+ int size;
+ int offset;
+
+ int nlmsg_count;
+ struct rtnl_handle *rth;
+};
+
+struct xfrm_filter {
+ int use;
+
+ struct xfrm_usersa_info xsinfo;
+ __u8 id_src_mask;
+ __u8 id_dst_mask;
+ __u8 id_proto_mask;
+ __u32 id_spi_mask;
+ __u8 mode_mask;
+ __u32 reqid_mask;
+ __u8 state_flags_mask;
+
+ struct xfrm_userpolicy_info xpinfo;
+ __u8 dir_mask;
+ __u8 sel_src_mask;
+ __u8 sel_dst_mask;
+ __u32 sel_dev_mask;
+ __u8 upspec_proto_mask;
+ __u16 upspec_sport_mask;
+ __u16 upspec_dport_mask;
+ __u32 index_mask;
+ __u8 action_mask;
+ __u32 priority_mask;
+ __u8 policy_flags_mask;
+ __u8 filter_socket;
+
+ __u8 ptype;
+ __u8 ptype_mask;
+
+};
+#define XFRM_FILTER_MASK_FULL (~0)
+
+extern struct xfrm_filter filter;
+
+int xfrm_state_print(struct nlmsghdr *n, void *arg);
+int xfrm_policy_print(struct nlmsghdr *n, void *arg);
+int do_xfrm_state(int argc, char **argv);
+int do_xfrm_policy(int argc, char **argv);
+int do_xfrm_monitor(int argc, char **argv);
+
+int xfrm_addr_match(xfrm_address_t *x1, xfrm_address_t *x2, int bits);
+int xfrm_xfrmproto_is_ipsec(__u8 proto);
+int xfrm_xfrmproto_is_ro(__u8 proto);
+int xfrm_xfrmproto_getbyname(char *name);
+int xfrm_algotype_getbyname(char *name);
+int xfrm_parse_mark(struct xfrm_mark *mark, int *argcp, char ***argvp);
+const char *strxf_xfrmproto(__u8 proto);
+const char *strxf_algotype(int type);
+const char *strxf_mask32(__u32 mask);
+const char *strxf_proto(__u8 proto);
+const char *strxf_ptype(__u8 ptype);
+void xfrm_selector_print(struct xfrm_selector *sel, __u16 family,
+ FILE *fp, const char *prefix);
+void xfrm_xfrma_print(struct rtattr *tb[], __u16 family, FILE *fp,
+ const char *prefix, bool nokeys, bool dir);
+void xfrm_state_info_print(struct xfrm_usersa_info *xsinfo,
+ struct rtattr *tb[], FILE *fp, const char *prefix,
+ const char *title, bool nokeys);
+void xfrm_policy_info_print(struct xfrm_userpolicy_info *xpinfo,
+ struct rtattr *tb[], FILE *fp, const char *prefix,
+ const char *title);
+int xfrm_policy_default_print(struct nlmsghdr *n, FILE *fp);
+int xfrm_id_parse(xfrm_address_t *saddr, struct xfrm_id *id, __u16 *family,
+ int loose, int *argcp, char ***argvp);
+int xfrm_mode_parse(__u8 *mode, int *argcp, char ***argvp);
+int xfrm_encap_type_parse(__u16 *type, int *argcp, char ***argvp);
+int xfrm_reqid_parse(__u32 *reqid, int *argcp, char ***argvp);
+int xfrm_selector_parse(struct xfrm_selector *sel, int *argcp, char ***argvp);
+int xfrm_lifetime_cfg_parse(struct xfrm_lifetime_cfg *lft,
+ int *argcp, char ***argvp);
+int xfrm_sctx_parse(char *ctxstr, char *context,
+ struct xfrm_user_sec_ctx *sctx);
+#endif
diff --git a/ip/xfrm_monitor.c b/ip/xfrm_monitor.c
new file mode 100644
index 0000000..1f67fe9
--- /dev/null
+++ b/ip/xfrm_monitor.c
@@ -0,0 +1,413 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C)2005 USAGI/WIDE Project
+ *
+ * based on ipmonitor.c
+ *
+ * Authors:
+ * Masahide NAKAMURA @USAGI
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+
+#include "utils.h"
+#include "xfrm.h"
+#include "ip_common.h"
+
+static void usage(void) __attribute__((noreturn));
+static int listen_all_nsid;
+static bool nokeys;
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip xfrm monitor [ nokeys ] [ all-nsid ] [ all | OBJECTS | help ]\n"
+ "OBJECTS := { acquire | expire | SA | aevent | policy | report }\n");
+ exit(-1);
+}
+
+static int xfrm_acquire_print(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+ struct xfrm_user_acquire *xacq = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *tb[XFRMA_MAX+1];
+ __u16 family;
+
+ len -= NLMSG_LENGTH(sizeof(*xacq));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ parse_rtattr(tb, XFRMA_MAX, XFRMACQ_RTA(xacq), len);
+
+ family = xacq->sel.family;
+ if (family == AF_UNSPEC)
+ family = xacq->policy.sel.family;
+ if (family == AF_UNSPEC)
+ family = preferred_family;
+
+ fprintf(fp, "acquire ");
+
+ fprintf(fp, "proto %s ", strxf_xfrmproto(xacq->id.proto));
+ if (show_stats > 0 || xacq->id.spi) {
+ __u32 spi = ntohl(xacq->id.spi);
+
+ fprintf(fp, "spi 0x%08x", spi);
+ if (show_stats > 0)
+ fprintf(fp, "(%u)", spi);
+ fprintf(fp, " ");
+ }
+ fprintf(fp, "%s", _SL_);
+
+ xfrm_selector_print(&xacq->sel, family, fp, " sel ");
+
+ xfrm_policy_info_print(&xacq->policy, tb, fp, " ", " policy ");
+
+ if (show_stats > 0)
+ fprintf(fp, " seq 0x%08u ", xacq->seq);
+ if (show_stats > 0) {
+ fprintf(fp, "%s-mask %s ",
+ strxf_algotype(XFRMA_ALG_CRYPT),
+ strxf_mask32(xacq->ealgos));
+ fprintf(fp, "%s-mask %s ",
+ strxf_algotype(XFRMA_ALG_AUTH),
+ strxf_mask32(xacq->aalgos));
+ fprintf(fp, "%s-mask %s",
+ strxf_algotype(XFRMA_ALG_COMP),
+ strxf_mask32(xacq->calgos));
+ }
+ fprintf(fp, "%s", _SL_);
+
+ if (oneline)
+ fprintf(fp, "\n");
+ fflush(fp);
+
+ return 0;
+}
+
+static int xfrm_state_flush_print(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+ struct xfrm_usersa_flush *xsf = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ const char *str;
+
+ len -= NLMSG_SPACE(sizeof(*xsf));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ fprintf(fp, "Flushed state ");
+
+ str = strxf_xfrmproto(xsf->proto);
+ if (str)
+ fprintf(fp, "proto %s", str);
+ else
+ fprintf(fp, "proto %u", xsf->proto);
+ fprintf(fp, "%s", _SL_);
+
+ if (oneline)
+ fprintf(fp, "\n");
+ fflush(fp);
+
+ return 0;
+}
+
+static int xfrm_policy_flush_print(struct nlmsghdr *n, void *arg)
+{
+ struct rtattr *tb[XFRMA_MAX+1];
+ FILE *fp = (FILE *)arg;
+ int len = n->nlmsg_len;
+
+ len -= NLMSG_SPACE(0);
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ fprintf(fp, "Flushed policy ");
+
+ parse_rtattr(tb, XFRMA_MAX, NLMSG_DATA(n), len);
+
+ if (tb[XFRMA_POLICY_TYPE]) {
+ struct xfrm_userpolicy_type *upt;
+
+ fprintf(fp, "ptype ");
+
+ if (RTA_PAYLOAD(tb[XFRMA_POLICY_TYPE]) < sizeof(*upt))
+ fprintf(fp, "(ERROR truncated)");
+
+ upt = RTA_DATA(tb[XFRMA_POLICY_TYPE]);
+ fprintf(fp, "%s ", strxf_ptype(upt->type));
+ }
+
+ fprintf(fp, "%s", _SL_);
+
+ if (oneline)
+ fprintf(fp, "\n");
+ fflush(fp);
+
+ return 0;
+}
+
+static int xfrm_report_print(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+ struct xfrm_user_report *xrep = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *tb[XFRMA_MAX+1];
+ __u16 family;
+
+ len -= NLMSG_LENGTH(sizeof(*xrep));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ family = xrep->sel.family;
+ if (family == AF_UNSPEC)
+ family = preferred_family;
+
+ fprintf(fp, "report ");
+
+ fprintf(fp, "proto %s ", strxf_xfrmproto(xrep->proto));
+ fprintf(fp, "%s", _SL_);
+
+ xfrm_selector_print(&xrep->sel, family, fp, " sel ");
+
+ parse_rtattr(tb, XFRMA_MAX, XFRMREP_RTA(xrep), len);
+
+ xfrm_xfrma_print(tb, family, fp, " ", nokeys, true);
+
+ if (oneline)
+ fprintf(fp, "\n");
+
+ return 0;
+}
+
+static void xfrm_ae_flags_print(__u32 flags, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+
+ fprintf(fp, " (0x%x) ", flags);
+ if (!flags)
+ return;
+ if (flags & XFRM_AE_CR)
+ fprintf(fp, " replay update ");
+ if (flags & XFRM_AE_CE)
+ fprintf(fp, " timer expired ");
+ if (flags & XFRM_AE_CU)
+ fprintf(fp, " policy updated ");
+
+}
+
+static void xfrm_usersa_print(const struct xfrm_usersa_id *sa_id, __u32 reqid, FILE *fp)
+{
+ fprintf(fp, "dst %s ",
+ rt_addr_n2a(sa_id->family, sizeof(sa_id->daddr), &sa_id->daddr));
+
+ fprintf(fp, " reqid 0x%x", reqid);
+
+ fprintf(fp, " protocol %s ", strxf_proto(sa_id->proto));
+ fprintf(fp, " SPI 0x%x", ntohl(sa_id->spi));
+}
+
+static int xfrm_ae_print(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+ struct xfrm_aevent_id *id = NLMSG_DATA(n);
+
+ fprintf(fp, "Async event ");
+ xfrm_ae_flags_print(id->flags, arg);
+ fprintf(fp, "\n\t");
+ fprintf(fp, "src %s ", rt_addr_n2a(id->sa_id.family,
+ sizeof(id->saddr), &id->saddr));
+
+ xfrm_usersa_print(&id->sa_id, id->reqid, fp);
+
+ fprintf(fp, "\n");
+ fflush(fp);
+
+ return 0;
+}
+
+static void xfrm_print_addr(FILE *fp, int family, xfrm_address_t *a)
+{
+ fprintf(fp, "%s", rt_addr_n2a(family, sizeof(*a), a));
+}
+
+static int xfrm_mapping_print(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+ struct xfrm_user_mapping *map = NLMSG_DATA(n);
+
+ fprintf(fp, "Mapping change ");
+ xfrm_print_addr(fp, map->id.family, &map->old_saddr);
+
+ fprintf(fp, ":%d -> ", ntohs(map->old_sport));
+ xfrm_print_addr(fp, map->id.family, &map->new_saddr);
+ fprintf(fp, ":%d\n\t", ntohs(map->new_sport));
+
+ xfrm_usersa_print(&map->id, map->reqid, fp);
+
+ fprintf(fp, "\n");
+ fflush(fp);
+ return 0;
+}
+
+static int xfrm_accept_msg(struct rtnl_ctrl_data *ctrl,
+ struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+
+ if (timestamp)
+ print_timestamp(fp);
+
+ if (listen_all_nsid) {
+ if (ctrl == NULL || ctrl->nsid < 0)
+ fprintf(fp, "[nsid current]");
+ else
+ fprintf(fp, "[nsid %d]", ctrl->nsid);
+ }
+
+ switch (n->nlmsg_type) {
+ case XFRM_MSG_NEWSA:
+ case XFRM_MSG_DELSA:
+ case XFRM_MSG_UPDSA:
+ case XFRM_MSG_EXPIRE:
+ xfrm_state_print(n, arg);
+ return 0;
+ case XFRM_MSG_NEWPOLICY:
+ case XFRM_MSG_DELPOLICY:
+ case XFRM_MSG_UPDPOLICY:
+ case XFRM_MSG_POLEXPIRE:
+ xfrm_policy_print(n, arg);
+ return 0;
+ case XFRM_MSG_ACQUIRE:
+ xfrm_acquire_print(n, arg);
+ return 0;
+ case XFRM_MSG_FLUSHSA:
+ xfrm_state_flush_print(n, arg);
+ return 0;
+ case XFRM_MSG_FLUSHPOLICY:
+ xfrm_policy_flush_print(n, arg);
+ return 0;
+ case XFRM_MSG_REPORT:
+ xfrm_report_print(n, arg);
+ return 0;
+ case XFRM_MSG_NEWAE:
+ xfrm_ae_print(n, arg);
+ return 0;
+ case XFRM_MSG_MAPPING:
+ xfrm_mapping_print(n, arg);
+ return 0;
+ case XFRM_MSG_GETDEFAULT:
+ xfrm_policy_default_print(n, arg);
+ return 0;
+ default:
+ break;
+ }
+
+ if (n->nlmsg_type != NLMSG_ERROR && n->nlmsg_type != NLMSG_NOOP &&
+ n->nlmsg_type != NLMSG_DONE) {
+ fprintf(fp, "Unknown message: %08d 0x%08x 0x%08x\n",
+ n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+ }
+ return 0;
+}
+
+extern struct rtnl_handle rth;
+
+int do_xfrm_monitor(int argc, char **argv)
+{
+ char *file = NULL;
+ unsigned int groups = ~((unsigned)0); /* XXX */
+ int lacquire = 0;
+ int lexpire = 0;
+ int laevent = 0;
+ int lpolicy = 0;
+ int lsa = 0;
+ int lreport = 0;
+
+ rtnl_close(&rth);
+
+ while (argc > 0) {
+ if (matches(*argv, "file") == 0) {
+ NEXT_ARG();
+ file = *argv;
+ } else if (strcmp(*argv, "nokeys") == 0) {
+ nokeys = true;
+ } else if (strcmp(*argv, "all") == 0) {
+ /* fall out */
+ } else if (matches(*argv, "all-nsid") == 0) {
+ listen_all_nsid = 1;
+ } else if (matches(*argv, "acquire") == 0) {
+ lacquire = 1;
+ groups = 0;
+ } else if (matches(*argv, "expire") == 0) {
+ lexpire = 1;
+ groups = 0;
+ } else if (matches(*argv, "SA") == 0) {
+ lsa = 1;
+ groups = 0;
+ } else if (matches(*argv, "aevent") == 0) {
+ laevent = 1;
+ groups = 0;
+ } else if (matches(*argv, "policy") == 0) {
+ lpolicy = 1;
+ groups = 0;
+ } else if (matches(*argv, "report") == 0) {
+ lreport = 1;
+ groups = 0;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ fprintf(stderr, "Argument \"%s\" is unknown, try \"ip xfrm monitor help\".\n", *argv);
+ exit(-1);
+ }
+ argc--; argv++;
+ }
+
+ if (lacquire)
+ groups |= nl_mgrp(XFRMNLGRP_ACQUIRE);
+ if (lexpire)
+ groups |= nl_mgrp(XFRMNLGRP_EXPIRE);
+ if (lsa)
+ groups |= nl_mgrp(XFRMNLGRP_SA);
+ if (lpolicy)
+ groups |= nl_mgrp(XFRMNLGRP_POLICY);
+ if (laevent)
+ groups |= nl_mgrp(XFRMNLGRP_AEVENTS);
+ if (lreport)
+ groups |= nl_mgrp(XFRMNLGRP_REPORT);
+
+ if (file) {
+ FILE *fp;
+ int err;
+
+ fp = fopen(file, "r");
+ if (fp == NULL) {
+ perror("Cannot fopen");
+ exit(-1);
+ }
+ err = rtnl_from_file(fp, xfrm_accept_msg, stdout);
+ fclose(fp);
+ return err;
+ }
+
+ if (rtnl_open_byproto(&rth, groups, NETLINK_XFRM) < 0)
+ exit(1);
+ if (listen_all_nsid && rtnl_listen_all_nsid(&rth) < 0)
+ exit(1);
+
+ if (rtnl_listen(&rth, xfrm_accept_msg, (void *)stdout) < 0)
+ exit(2);
+
+ return 0;
+}
diff --git a/ip/xfrm_policy.c b/ip/xfrm_policy.c
new file mode 100644
index 0000000..8687ced
--- /dev/null
+++ b/ip/xfrm_policy.c
@@ -0,0 +1,1348 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C)2004 USAGI/WIDE Project
+ *
+ * based on iproute.c
+ *
+ * Authors:
+ * Masahide NAKAMURA @USAGI
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netdb.h>
+#include <linux/netlink.h>
+#include "utils.h"
+#include "xfrm.h"
+#include "ip_common.h"
+
+/* #define NLMSG_DELETEALL_BUF_SIZE (4096-512) */
+#define NLMSG_DELETEALL_BUF_SIZE 8192
+
+/*
+ * Receiving buffer defines:
+ * nlmsg
+ * data = struct xfrm_userpolicy_info
+ * rtattr
+ * data = struct xfrm_user_tmpl[]
+ */
+#define NLMSG_BUF_SIZE 4096
+#define RTA_BUF_SIZE 2048
+#define XFRM_TMPLS_BUF_SIZE 1024
+#define CTX_BUF_SIZE 256
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip xfrm policy { add | update } SELECTOR dir DIR [ ctx CTX ]\n"
+ " [ mark MARK [ mask MASK ] ] [ index INDEX ] [ ptype PTYPE ]\n"
+ " [ action ACTION ] [ priority PRIORITY ] [ flag FLAG-LIST ]\n"
+ " [ if_id IF_ID ] [ LIMIT-LIST ] [ TMPL-LIST ]\n"
+ " [ offload packet dev DEV] } ]\n"
+ "Usage: ip xfrm policy { delete | get } { SELECTOR | index INDEX } dir DIR\n"
+ " [ ctx CTX ] [ mark MARK [ mask MASK ] ] [ ptype PTYPE ]\n"
+ " [ if_id IF_ID ]\n"
+ "Usage: ip xfrm policy { deleteall | list } [ nosock ] [ SELECTOR ] [ dir DIR ]\n"
+ " [ index INDEX ] [ ptype PTYPE ] [ action ACTION ] [ priority PRIORITY ]\n"
+ " [ flag FLAG-LIST ]\n"
+ "Usage: ip xfrm policy flush [ ptype PTYPE ]\n"
+ "Usage: ip xfrm policy count\n"
+ "Usage: ip xfrm policy set [ hthresh4 LBITS RBITS ] [ hthresh6 LBITS RBITS ]\n"
+ "Usage: ip xfrm policy setdefault DIR ACTION [ DIR ACTION ] [ DIR ACTION ]\n"
+ "Usage: ip xfrm policy getdefault\n"
+ "SELECTOR := [ src ADDR[/PLEN] ] [ dst ADDR[/PLEN] ] [ dev DEV ] [ UPSPEC ]\n"
+ "UPSPEC := proto { { tcp | udp | sctp | dccp } [ sport PORT ] [ dport PORT ] |\n"
+ " { icmp | ipv6-icmp | mobility-header } [ type NUMBER ] [ code NUMBER ] |\n"
+ " gre [ key { DOTTED-QUAD | NUMBER } ] | PROTO }\n"
+ "DIR := in | out | fwd\n"
+ "PTYPE := main | sub\n"
+ "ACTION := allow | block\n"
+ "FLAG-LIST := [ FLAG-LIST ] FLAG\n"
+ "FLAG := localok | icmp\n"
+ "LIMIT-LIST := [ LIMIT-LIST ] limit LIMIT\n"
+ "LIMIT := { time-soft | time-hard | time-use-soft | time-use-hard } SECONDS |\n"
+ " { byte-soft | byte-hard } SIZE | { packet-soft | packet-hard } COUNT\n"
+ "TMPL-LIST := [ TMPL-LIST ] tmpl TMPL\n"
+ "TMPL := ID [ mode MODE ] [ reqid REQID ] [ level LEVEL ]\n"
+ "ID := [ src ADDR ] [ dst ADDR ] [ proto XFRM-PROTO ] [ spi SPI ]\n"
+ "XFRM-PROTO := ");
+ fprintf(stderr,
+ "%s | %s | %s | %s | %s\n",
+ strxf_xfrmproto(IPPROTO_ESP),
+ strxf_xfrmproto(IPPROTO_AH),
+ strxf_xfrmproto(IPPROTO_COMP),
+ strxf_xfrmproto(IPPROTO_ROUTING),
+ strxf_xfrmproto(IPPROTO_DSTOPTS));
+ fprintf(stderr,
+ "MODE := transport | tunnel | beet | ro | in_trigger\n"
+ "LEVEL := required | use\n");
+
+ exit(-1);
+}
+
+static int xfrm_policy_dir_parse(__u8 *dir, int *argcp, char ***argvp)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+
+ if (strcmp(*argv, "in") == 0)
+ *dir = XFRM_POLICY_IN;
+ else if (strcmp(*argv, "out") == 0)
+ *dir = XFRM_POLICY_OUT;
+ else if (strcmp(*argv, "fwd") == 0)
+ *dir = XFRM_POLICY_FWD;
+ else
+ invarg("DIR value is invalid", *argv);
+
+ *argcp = argc;
+ *argvp = argv;
+
+ return 0;
+}
+
+static int xfrm_policy_ptype_parse(__u8 *ptype, int *argcp, char ***argvp)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+
+ if (strcmp(*argv, "main") == 0)
+ *ptype = XFRM_POLICY_TYPE_MAIN;
+ else if (strcmp(*argv, "sub") == 0)
+ *ptype = XFRM_POLICY_TYPE_SUB;
+ else
+ invarg("PTYPE value is invalid", *argv);
+
+ *argcp = argc;
+ *argvp = argv;
+
+ return 0;
+}
+
+static int xfrm_policy_flag_parse(__u8 *flags, int *argcp, char ***argvp)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+ int len = strlen(*argv);
+
+ if (len > 2 && strncmp(*argv, "0x", 2) == 0) {
+ __u8 val = 0;
+
+ if (get_u8(&val, *argv, 16))
+ invarg("FLAG value is invalid", *argv);
+ *flags = val;
+ } else {
+ while (1) {
+ if (strcmp(*argv, "localok") == 0)
+ *flags |= XFRM_POLICY_LOCALOK;
+ else if (strcmp(*argv, "icmp") == 0)
+ *flags |= XFRM_POLICY_ICMP;
+ else {
+ PREV_ARG(); /* back track */
+ break;
+ }
+
+ if (!NEXT_ARG_OK())
+ break;
+ NEXT_ARG();
+ }
+ }
+
+ *argcp = argc;
+ *argvp = argv;
+
+ return 0;
+}
+
+static int xfrm_tmpl_parse(struct xfrm_user_tmpl *tmpl,
+ int *argcp, char ***argvp)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+ char *idp = NULL;
+
+ while (1) {
+ if (strcmp(*argv, "mode") == 0) {
+ NEXT_ARG();
+ xfrm_mode_parse(&tmpl->mode, &argc, &argv);
+ } else if (strcmp(*argv, "reqid") == 0) {
+ NEXT_ARG();
+ xfrm_reqid_parse(&tmpl->reqid, &argc, &argv);
+ } else if (strcmp(*argv, "level") == 0) {
+ NEXT_ARG();
+
+ if (strcmp(*argv, "required") == 0)
+ tmpl->optional = 0;
+ else if (strcmp(*argv, "use") == 0)
+ tmpl->optional = 1;
+ else
+ invarg("LEVEL value is invalid\n", *argv);
+
+ } else {
+ if (idp) {
+ PREV_ARG(); /* back track */
+ break;
+ }
+ idp = *argv;
+ preferred_family = AF_UNSPEC;
+ xfrm_id_parse(&tmpl->saddr, &tmpl->id, &tmpl->family,
+ 0, &argc, &argv);
+ preferred_family = tmpl->family;
+ }
+
+ if (!NEXT_ARG_OK())
+ break;
+
+ NEXT_ARG();
+ }
+ if (argc == *argcp)
+ missarg("TMPL");
+
+ *argcp = argc;
+ *argvp = argv;
+
+ return 0;
+}
+
+int xfrm_sctx_parse(char *ctxstr, char *s,
+ struct xfrm_user_sec_ctx *sctx)
+{
+ int slen;
+
+ slen = strlen(s) + 1;
+
+ sctx->exttype = XFRMA_SEC_CTX;
+ sctx->ctx_doi = 1;
+ sctx->ctx_alg = 1;
+ sctx->ctx_len = slen;
+ sctx->len = sizeof(struct xfrm_user_sec_ctx) + slen;
+ memcpy(ctxstr, s, slen);
+
+ return 0;
+}
+
+static int xfrm_policy_modify(int cmd, unsigned int flags, int argc, char **argv)
+{
+ struct rtnl_handle rth;
+ struct {
+ struct nlmsghdr n;
+ struct xfrm_userpolicy_info xpinfo;
+ char buf[RTA_BUF_SIZE];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(req.xpinfo)),
+ .n.nlmsg_flags = NLM_F_REQUEST | flags,
+ .n.nlmsg_type = cmd,
+ .xpinfo.sel.family = preferred_family,
+ .xpinfo.lft.soft_byte_limit = XFRM_INF,
+ .xpinfo.lft.hard_byte_limit = XFRM_INF,
+ .xpinfo.lft.soft_packet_limit = XFRM_INF,
+ .xpinfo.lft.hard_packet_limit = XFRM_INF,
+ };
+ char *dirp = NULL;
+ char *selp = NULL;
+ char *ptypep = NULL;
+ char *sctxp = NULL;
+ struct xfrm_userpolicy_type upt = {};
+ struct xfrm_user_offload xuo = {};
+ char tmpls_buf[XFRM_TMPLS_BUF_SIZE] = {};
+ int tmpls_len = 0;
+ struct xfrm_mark mark = {0, 0};
+ struct {
+ struct xfrm_user_sec_ctx sctx;
+ char str[CTX_BUF_SIZE];
+ } ctx = {};
+ bool is_if_id_set = false;
+ unsigned int ifindex = 0;
+ bool is_offload = false;
+ __u32 if_id = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dir") == 0) {
+ if (dirp)
+ duparg("dir", *argv);
+ dirp = *argv;
+
+ NEXT_ARG();
+ xfrm_policy_dir_parse(&req.xpinfo.dir, &argc, &argv);
+ } else if (strcmp(*argv, "ctx") == 0) {
+ char *context;
+
+ if (sctxp)
+ duparg("ctx", *argv);
+ sctxp = *argv;
+ NEXT_ARG();
+ context = *argv;
+ xfrm_sctx_parse((char *)&ctx.str, context, &ctx.sctx);
+ } else if (strcmp(*argv, "mark") == 0) {
+ xfrm_parse_mark(&mark, &argc, &argv);
+ } else if (strcmp(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&req.xpinfo.index, *argv, 0))
+ invarg("INDEX value is invalid", *argv);
+ } else if (strcmp(*argv, "ptype") == 0) {
+ if (ptypep)
+ duparg("ptype", *argv);
+ ptypep = *argv;
+
+ NEXT_ARG();
+ xfrm_policy_ptype_parse(&upt.type, &argc, &argv);
+ } else if (strcmp(*argv, "action") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "allow") == 0)
+ req.xpinfo.action = XFRM_POLICY_ALLOW;
+ else if (strcmp(*argv, "block") == 0)
+ req.xpinfo.action = XFRM_POLICY_BLOCK;
+ else
+ invarg("ACTION value is invalid\n", *argv);
+ } else if (strcmp(*argv, "priority") == 0) {
+ NEXT_ARG();
+ if (get_u32(&req.xpinfo.priority, *argv, 0))
+ invarg("PRIORITY value is invalid", *argv);
+ } else if (strcmp(*argv, "flag") == 0) {
+ NEXT_ARG();
+ xfrm_policy_flag_parse(&req.xpinfo.flags, &argc,
+ &argv);
+ } else if (strcmp(*argv, "limit") == 0) {
+ NEXT_ARG();
+ xfrm_lifetime_cfg_parse(&req.xpinfo.lft, &argc, &argv);
+ } else if (strcmp(*argv, "tmpl") == 0) {
+ struct xfrm_user_tmpl *tmpl;
+
+ if (tmpls_len + sizeof(*tmpl) > sizeof(tmpls_buf)) {
+ fprintf(stderr, "Too many tmpls: buffer overflow\n");
+ exit(1);
+ }
+ tmpl = (struct xfrm_user_tmpl *)((char *)tmpls_buf + tmpls_len);
+
+ tmpl->family = preferred_family;
+ tmpl->aalgos = (~(__u32)0);
+ tmpl->ealgos = (~(__u32)0);
+ tmpl->calgos = (~(__u32)0);
+
+ NEXT_ARG();
+ xfrm_tmpl_parse(tmpl, &argc, &argv);
+
+ tmpls_len += sizeof(*tmpl);
+ } else if (strcmp(*argv, "if_id") == 0) {
+ NEXT_ARG();
+ if (get_u32(&if_id, *argv, 0))
+ invarg("IF_ID value is invalid", *argv);
+ is_if_id_set = true;
+ } else if (strcmp(*argv, "offload") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "packet") == 0)
+ NEXT_ARG();
+ else
+ invarg("Invalid offload mode", *argv);
+
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ ifindex = ll_name_to_index(*argv);
+ if (!ifindex)
+ invarg("Invalid device name", *argv);
+ } else
+ invarg("Missing dev keyword", *argv);
+ is_offload = true;
+ } else {
+ if (selp)
+ duparg("unknown", *argv);
+ selp = *argv;
+
+ xfrm_selector_parse(&req.xpinfo.sel, &argc, &argv);
+ if (preferred_family == AF_UNSPEC)
+ preferred_family = req.xpinfo.sel.family;
+ }
+
+ argc--; argv++;
+ }
+
+ if (!dirp) {
+ fprintf(stderr, "Not enough information: DIR is required.\n");
+ exit(1);
+ }
+
+ if (ptypep) {
+ addattr_l(&req.n, sizeof(req), XFRMA_POLICY_TYPE,
+ (void *)&upt, sizeof(upt));
+ }
+
+ if (tmpls_len > 0) {
+ addattr_l(&req.n, sizeof(req), XFRMA_TMPL,
+ (void *)tmpls_buf, tmpls_len);
+ }
+
+ if (mark.m) {
+ int r = addattr_l(&req.n, sizeof(req.buf), XFRMA_MARK,
+ (void *)&mark, sizeof(mark));
+ if (r < 0) {
+ fprintf(stderr, "%s: XFRMA_MARK failed\n", __func__);
+ exit(1);
+ }
+ }
+
+ if (sctxp) {
+ addattr_l(&req.n, sizeof(req), XFRMA_SEC_CTX,
+ (void *)&ctx, ctx.sctx.len);
+ }
+
+ if (is_if_id_set)
+ addattr32(&req.n, sizeof(req.buf), XFRMA_IF_ID, if_id);
+
+ if (is_offload) {
+ xuo.ifindex = ifindex;
+ xuo.flags |= XFRM_OFFLOAD_PACKET;
+ addattr_l(&req.n, sizeof(req.buf), XFRMA_OFFLOAD_DEV, &xuo,
+ sizeof(xuo));
+ }
+
+ if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+ exit(1);
+
+ if (req.xpinfo.sel.family == AF_UNSPEC)
+ req.xpinfo.sel.family = AF_INET;
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ exit(2);
+
+ rtnl_close(&rth);
+
+ return 0;
+}
+
+static int xfrm_policy_filter_match(struct xfrm_userpolicy_info *xpinfo,
+ __u8 ptype)
+{
+ if (!filter.use)
+ return 1;
+
+ if (filter.xpinfo.sel.family != AF_UNSPEC &&
+ filter.xpinfo.sel.family != xpinfo->sel.family)
+ return 0;
+
+ if ((xpinfo->dir^filter.xpinfo.dir)&filter.dir_mask)
+ return 0;
+
+ if (filter.filter_socket && (xpinfo->dir >= XFRM_POLICY_MAX))
+ return 0;
+
+ if ((ptype^filter.ptype)&filter.ptype_mask)
+ return 0;
+
+ if (filter.sel_src_mask) {
+ if (xfrm_addr_match(&xpinfo->sel.saddr, &filter.xpinfo.sel.saddr,
+ filter.sel_src_mask))
+ return 0;
+ }
+
+ if (filter.sel_dst_mask) {
+ if (xfrm_addr_match(&xpinfo->sel.daddr, &filter.xpinfo.sel.daddr,
+ filter.sel_dst_mask))
+ return 0;
+ }
+
+ if ((xpinfo->sel.ifindex^filter.xpinfo.sel.ifindex)&filter.sel_dev_mask)
+ return 0;
+
+ if ((xpinfo->sel.proto^filter.xpinfo.sel.proto)&filter.upspec_proto_mask)
+ return 0;
+
+ if (filter.upspec_sport_mask) {
+ if ((xpinfo->sel.sport^filter.xpinfo.sel.sport)&filter.upspec_sport_mask)
+ return 0;
+ }
+
+ if (filter.upspec_dport_mask) {
+ if ((xpinfo->sel.dport^filter.xpinfo.sel.dport)&filter.upspec_dport_mask)
+ return 0;
+ }
+
+ if ((xpinfo->index^filter.xpinfo.index)&filter.index_mask)
+ return 0;
+
+ if ((xpinfo->action^filter.xpinfo.action)&filter.action_mask)
+ return 0;
+
+ if ((xpinfo->priority^filter.xpinfo.priority)&filter.priority_mask)
+ return 0;
+
+ if (filter.policy_flags_mask)
+ if ((xpinfo->flags & filter.xpinfo.flags) == 0)
+ return 0;
+
+ return 1;
+}
+
+int xfrm_policy_print(struct nlmsghdr *n, void *arg)
+{
+ struct rtattr *tb[XFRMA_MAX+1];
+ struct rtattr *rta;
+ struct xfrm_userpolicy_info *xpinfo = NULL;
+ struct xfrm_user_polexpire *xpexp = NULL;
+ struct xfrm_userpolicy_id *xpid = NULL;
+ __u8 ptype = XFRM_POLICY_TYPE_MAIN;
+ FILE *fp = (FILE *)arg;
+ int len = n->nlmsg_len;
+
+ if (n->nlmsg_type != XFRM_MSG_NEWPOLICY &&
+ n->nlmsg_type != XFRM_MSG_DELPOLICY &&
+ n->nlmsg_type != XFRM_MSG_UPDPOLICY &&
+ n->nlmsg_type != XFRM_MSG_POLEXPIRE) {
+ fprintf(stderr, "Not a policy: %08x %08x %08x\n",
+ n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+ return 0;
+ }
+
+ if (n->nlmsg_type == XFRM_MSG_DELPOLICY) {
+ xpid = NLMSG_DATA(n);
+ len -= NLMSG_SPACE(sizeof(*xpid));
+ } else if (n->nlmsg_type == XFRM_MSG_POLEXPIRE) {
+ xpexp = NLMSG_DATA(n);
+ xpinfo = &xpexp->pol;
+ len -= NLMSG_SPACE(sizeof(*xpexp));
+ } else {
+ xpexp = NULL;
+ xpinfo = NLMSG_DATA(n);
+ len -= NLMSG_SPACE(sizeof(*xpinfo));
+ }
+
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ if (n->nlmsg_type == XFRM_MSG_DELPOLICY)
+ rta = XFRMPID_RTA(xpid);
+ else if (n->nlmsg_type == XFRM_MSG_POLEXPIRE)
+ rta = XFRMPEXP_RTA(xpexp);
+ else
+ rta = XFRMP_RTA(xpinfo);
+
+ parse_rtattr(tb, XFRMA_MAX, rta, len);
+
+ if (tb[XFRMA_POLICY_TYPE]) {
+ struct xfrm_userpolicy_type *upt;
+
+ if (RTA_PAYLOAD(tb[XFRMA_POLICY_TYPE]) < sizeof(*upt)) {
+ fprintf(stderr, "too short XFRMA_POLICY_TYPE len\n");
+ return -1;
+ }
+ upt = RTA_DATA(tb[XFRMA_POLICY_TYPE]);
+ ptype = upt->type;
+ }
+
+ if (xpinfo && !xfrm_policy_filter_match(xpinfo, ptype))
+ return 0;
+
+ if (n->nlmsg_type == XFRM_MSG_DELPOLICY)
+ fprintf(fp, "Deleted ");
+ else if (n->nlmsg_type == XFRM_MSG_UPDPOLICY)
+ fprintf(fp, "Updated ");
+ else if (n->nlmsg_type == XFRM_MSG_POLEXPIRE)
+ fprintf(fp, "Expired ");
+
+ if (n->nlmsg_type == XFRM_MSG_DELPOLICY) {
+ /* xfrm_policy_id_print(); */
+ if (!tb[XFRMA_POLICY]) {
+ fprintf(stderr, "Buggy XFRM_MSG_DELPOLICY: no XFRMA_POLICY\n");
+ return -1;
+ }
+ if (RTA_PAYLOAD(tb[XFRMA_POLICY]) < sizeof(*xpinfo)) {
+ fprintf(stderr, "Buggy XFRM_MSG_DELPOLICY: too short XFRMA_POLICY len\n");
+ return -1;
+ }
+ xpinfo = RTA_DATA(tb[XFRMA_POLICY]);
+ }
+
+ xfrm_policy_info_print(xpinfo, tb, fp, NULL, NULL);
+
+ if (n->nlmsg_type == XFRM_MSG_POLEXPIRE) {
+ fprintf(fp, "\t");
+ fprintf(fp, "hard %u", xpexp->hard);
+ fprintf(fp, "%s", _SL_);
+ }
+
+ if (oneline)
+ fprintf(fp, "\n");
+ fflush(fp);
+
+ return 0;
+}
+
+static int xfrm_policy_get_or_delete(int argc, char **argv, int delete,
+ struct nlmsghdr **answer)
+{
+ struct rtnl_handle rth;
+ struct {
+ struct nlmsghdr n;
+ struct xfrm_userpolicy_id xpid;
+ char buf[RTA_BUF_SIZE];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(req.xpid)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = delete ? XFRM_MSG_DELPOLICY
+ : XFRM_MSG_GETPOLICY,
+ };
+ char *dirp = NULL;
+ char *selp = NULL;
+ char *indexp = NULL;
+ char *ptypep = NULL;
+ char *sctxp = NULL;
+ struct xfrm_userpolicy_type upt = {};
+ struct xfrm_mark mark = {0, 0};
+ struct {
+ struct xfrm_user_sec_ctx sctx;
+ char str[CTX_BUF_SIZE];
+ } ctx = {};
+ bool is_if_id_set = false;
+ __u32 if_id = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dir") == 0) {
+ if (dirp)
+ duparg("dir", *argv);
+ dirp = *argv;
+
+ NEXT_ARG();
+ xfrm_policy_dir_parse(&req.xpid.dir, &argc, &argv);
+
+ } else if (strcmp(*argv, "ctx") == 0) {
+ char *context;
+
+ if (sctxp)
+ duparg("ctx", *argv);
+ sctxp = *argv;
+ NEXT_ARG();
+ context = *argv;
+ xfrm_sctx_parse((char *)&ctx.str, context, &ctx.sctx);
+ } else if (strcmp(*argv, "mark") == 0) {
+ xfrm_parse_mark(&mark, &argc, &argv);
+ } else if (strcmp(*argv, "index") == 0) {
+ if (indexp)
+ duparg("index", *argv);
+ indexp = *argv;
+
+ NEXT_ARG();
+ if (get_u32(&req.xpid.index, *argv, 0))
+ invarg("INDEX value is invalid", *argv);
+
+ } else if (strcmp(*argv, "ptype") == 0) {
+ if (ptypep)
+ duparg("ptype", *argv);
+ ptypep = *argv;
+
+ NEXT_ARG();
+ xfrm_policy_ptype_parse(&upt.type, &argc, &argv);
+ } else if (strcmp(*argv, "if_id") == 0) {
+ NEXT_ARG();
+ if (get_u32(&if_id, *argv, 0))
+ invarg("IF_ID value is invalid", *argv);
+ is_if_id_set = true;
+ } else {
+ if (selp)
+ invarg("unknown", *argv);
+ selp = *argv;
+
+ xfrm_selector_parse(&req.xpid.sel, &argc, &argv);
+ if (preferred_family == AF_UNSPEC)
+ preferred_family = req.xpid.sel.family;
+
+ }
+
+ argc--; argv++;
+ }
+
+ if (!dirp) {
+ fprintf(stderr, "Not enough information: DIR is required.\n");
+ exit(1);
+ }
+ if (ptypep) {
+ addattr_l(&req.n, sizeof(req), XFRMA_POLICY_TYPE,
+ (void *)&upt, sizeof(upt));
+ }
+ if (!selp && !indexp) {
+ fprintf(stderr, "Not enough information: either SELECTOR or INDEX is required.\n");
+ exit(1);
+ }
+ if (selp && indexp)
+ duparg2("SELECTOR", "INDEX");
+
+ if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+ exit(1);
+
+ if (req.xpid.sel.family == AF_UNSPEC)
+ req.xpid.sel.family = AF_INET;
+
+ if (mark.m & mark.v) {
+ int r = addattr_l(&req.n, sizeof(req.buf), XFRMA_MARK,
+ (void *)&mark, sizeof(mark));
+ if (r < 0) {
+ fprintf(stderr, "%s: XFRMA_MARK failed\n", __func__);
+ exit(1);
+ }
+ }
+
+ if (sctxp) {
+ addattr_l(&req.n, sizeof(req), XFRMA_SEC_CTX,
+ (void *)&ctx, ctx.sctx.len);
+ }
+
+ if (is_if_id_set)
+ addattr32(&req.n, sizeof(req.buf), XFRMA_IF_ID, if_id);
+
+ if (rtnl_talk(&rth, &req.n, answer) < 0)
+ exit(2);
+
+ rtnl_close(&rth);
+
+ return 0;
+}
+
+static int xfrm_policy_delete(int argc, char **argv)
+{
+ return xfrm_policy_get_or_delete(argc, argv, 1, NULL);
+}
+
+static int xfrm_policy_get(int argc, char **argv)
+{
+ struct nlmsghdr *n = NULL;
+
+ xfrm_policy_get_or_delete(argc, argv, 0, &n);
+
+ if (xfrm_policy_print(n, (void *)stdout) < 0) {
+ fprintf(stderr, "An error :-)\n");
+ exit(1);
+ }
+
+ free(n);
+ return 0;
+}
+
+/*
+ * With an existing policy of nlmsg, make new nlmsg for deleting the policy
+ * and store it to buffer.
+ */
+static int xfrm_policy_keep(struct nlmsghdr *n, void *arg)
+{
+ struct xfrm_buffer *xb = (struct xfrm_buffer *)arg;
+ struct rtnl_handle *rth = xb->rth;
+ struct xfrm_userpolicy_info *xpinfo = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *tb[XFRMA_MAX+1];
+ __u8 ptype = XFRM_POLICY_TYPE_MAIN;
+ struct nlmsghdr *new_n;
+ struct xfrm_userpolicy_id *xpid;
+
+ if (n->nlmsg_type != XFRM_MSG_NEWPOLICY) {
+ fprintf(stderr, "Not a policy: %08x %08x %08x\n",
+ n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+ return 0;
+ }
+
+ len -= NLMSG_LENGTH(sizeof(*xpinfo));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ parse_rtattr(tb, XFRMA_MAX, XFRMP_RTA(xpinfo), len);
+
+ if (tb[XFRMA_POLICY_TYPE]) {
+ struct xfrm_userpolicy_type *upt;
+
+ if (RTA_PAYLOAD(tb[XFRMA_POLICY_TYPE]) < sizeof(*upt)) {
+ fprintf(stderr, "too short XFRMA_POLICY_TYPE len\n");
+ return -1;
+ }
+ upt = RTA_DATA(tb[XFRMA_POLICY_TYPE]);
+ ptype = upt->type;
+ }
+
+ if (!xfrm_policy_filter_match(xpinfo, ptype))
+ return 0;
+
+ /* can't delete socket policies */
+ if (xpinfo->dir >= XFRM_POLICY_MAX)
+ return 0;
+
+ if (xb->offset + NLMSG_LENGTH(sizeof(*xpid)) > xb->size)
+ return 0;
+
+ new_n = (struct nlmsghdr *)(xb->buf + xb->offset);
+ new_n->nlmsg_len = NLMSG_LENGTH(sizeof(*xpid));
+ new_n->nlmsg_flags = NLM_F_REQUEST;
+ new_n->nlmsg_type = XFRM_MSG_DELPOLICY;
+ new_n->nlmsg_seq = ++rth->seq;
+
+ xpid = NLMSG_DATA(new_n);
+ memcpy(&xpid->sel, &xpinfo->sel, sizeof(xpid->sel));
+ xpid->dir = xpinfo->dir;
+ xpid->index = xpinfo->index;
+
+ if (tb[XFRMA_MARK]) {
+ int r = addattr_l(new_n, xb->size, XFRMA_MARK,
+ (void *)RTA_DATA(tb[XFRMA_MARK]), tb[XFRMA_MARK]->rta_len);
+ if (r < 0) {
+ fprintf(stderr, "%s: XFRMA_MARK failed\n", __func__);
+ exit(1);
+ }
+ }
+
+ if (tb[XFRMA_IF_ID]) {
+ addattr32(new_n, xb->size, XFRMA_IF_ID,
+ rta_getattr_u32(tb[XFRMA_IF_ID]));
+ }
+
+ xb->offset += new_n->nlmsg_len;
+ xb->nlmsg_count++;
+
+ return 0;
+}
+
+static int xfrm_policy_list_or_deleteall(int argc, char **argv, int deleteall)
+{
+ char *selp = NULL;
+ struct rtnl_handle rth;
+
+ if (argc > 0 || preferred_family != AF_UNSPEC)
+ filter.use = 1;
+ filter.xpinfo.sel.family = preferred_family;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dir") == 0) {
+ NEXT_ARG();
+ xfrm_policy_dir_parse(&filter.xpinfo.dir, &argc, &argv);
+
+ filter.dir_mask = XFRM_FILTER_MASK_FULL;
+
+ } else if (strcmp(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&filter.xpinfo.index, *argv, 0))
+ invarg("INDEX value is invalid", *argv);
+
+ filter.index_mask = XFRM_FILTER_MASK_FULL;
+
+ } else if (strcmp(*argv, "ptype") == 0) {
+ NEXT_ARG();
+ xfrm_policy_ptype_parse(&filter.ptype, &argc, &argv);
+
+ filter.ptype_mask = XFRM_FILTER_MASK_FULL;
+
+ } else if (strcmp(*argv, "action") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "allow") == 0)
+ filter.xpinfo.action = XFRM_POLICY_ALLOW;
+ else if (strcmp(*argv, "block") == 0)
+ filter.xpinfo.action = XFRM_POLICY_BLOCK;
+ else
+ invarg("ACTION value is invalid\n", *argv);
+
+ filter.action_mask = XFRM_FILTER_MASK_FULL;
+
+ } else if (strcmp(*argv, "priority") == 0) {
+ NEXT_ARG();
+ if (get_u32(&filter.xpinfo.priority, *argv, 0))
+ invarg("PRIORITY value is invalid", *argv);
+
+ filter.priority_mask = XFRM_FILTER_MASK_FULL;
+
+ } else if (strcmp(*argv, "flag") == 0) {
+ NEXT_ARG();
+ xfrm_policy_flag_parse(&filter.xpinfo.flags, &argc,
+ &argv);
+
+ filter.policy_flags_mask = XFRM_FILTER_MASK_FULL;
+
+ } else if (strcmp(*argv, "nosock") == 0) {
+ /* filter all socket-based policies */
+ filter.filter_socket = 1;
+ } else {
+ if (selp)
+ invarg("unknown", *argv);
+ selp = *argv;
+
+ xfrm_selector_parse(&filter.xpinfo.sel, &argc, &argv);
+ if (preferred_family == AF_UNSPEC)
+ preferred_family = filter.xpinfo.sel.family;
+
+ }
+
+ argc--; argv++;
+ }
+
+ if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+ exit(1);
+
+ if (deleteall) {
+ struct xfrm_buffer xb;
+ char buf[NLMSG_DELETEALL_BUF_SIZE];
+ int i;
+
+ xb.buf = buf;
+ xb.size = sizeof(buf);
+ xb.rth = &rth;
+
+ for (i = 0; ; i++) {
+ struct {
+ struct nlmsghdr n;
+ char buf[NLMSG_BUF_SIZE];
+ } req = {
+ .n.nlmsg_len = NLMSG_HDRLEN,
+ .n.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+ .n.nlmsg_type = XFRM_MSG_GETPOLICY,
+ .n.nlmsg_seq = rth.dump = ++rth.seq,
+ };
+
+ xb.offset = 0;
+ xb.nlmsg_count = 0;
+
+ if (show_stats > 1)
+ fprintf(stderr, "Delete-all round = %d\n", i);
+
+ if (rtnl_send(&rth, (void *)&req, req.n.nlmsg_len) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+
+ if (rtnl_dump_filter(&rth, xfrm_policy_keep, &xb) < 0) {
+ fprintf(stderr, "Delete-all terminated\n");
+ exit(1);
+ }
+ if (xb.nlmsg_count == 0) {
+ if (show_stats > 1)
+ fprintf(stderr, "Delete-all completed\n");
+ break;
+ }
+
+ if (rtnl_send_check(&rth, xb.buf, xb.offset) < 0) {
+ perror("Failed to send delete-all request");
+ exit(1);
+ }
+ if (show_stats > 1)
+ fprintf(stderr, "Delete-all nlmsg count = %d\n", xb.nlmsg_count);
+
+ xb.offset = 0;
+ xb.nlmsg_count = 0;
+ }
+ } else {
+ struct {
+ struct nlmsghdr n;
+ char buf[NLMSG_BUF_SIZE];
+ } req = {
+ .n.nlmsg_len = NLMSG_HDRLEN,
+ .n.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+ .n.nlmsg_type = XFRM_MSG_GETPOLICY,
+ .n.nlmsg_seq = rth.dump = ++rth.seq,
+ };
+
+ if (rtnl_send(&rth, (void *)&req, req.n.nlmsg_len) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+
+ if (rtnl_dump_filter(&rth, xfrm_policy_print, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ }
+
+ rtnl_close(&rth);
+
+ exit(0);
+}
+
+static int print_spdinfo(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+ __u32 *f = NLMSG_DATA(n);
+ struct rtattr *tb[XFRMA_SPD_MAX+1];
+ struct rtattr *rta;
+
+ int len = n->nlmsg_len;
+
+ len -= NLMSG_LENGTH(sizeof(__u32));
+ if (len < 0) {
+ fprintf(stderr, "SPDinfo: Wrong len %d\n", len);
+ return -1;
+ }
+
+ rta = XFRMSAPD_RTA(f);
+ parse_rtattr(tb, XFRMA_SPD_MAX, rta, len);
+
+ fprintf(fp, "\t SPD");
+ if (tb[XFRMA_SPD_INFO]) {
+ struct xfrmu_spdinfo *si;
+
+ if (RTA_PAYLOAD(tb[XFRMA_SPD_INFO]) < sizeof(*si)) {
+ fprintf(stderr, "SPDinfo: Wrong len %d\n", len);
+ return -1;
+ }
+ si = RTA_DATA(tb[XFRMA_SPD_INFO]);
+ fprintf(fp, " IN %d", si->incnt);
+ fprintf(fp, " OUT %d", si->outcnt);
+ fprintf(fp, " FWD %d", si->fwdcnt);
+
+ if (show_stats) {
+ fprintf(fp, " (Sock:");
+ fprintf(fp, " IN %d", si->inscnt);
+ fprintf(fp, " OUT %d", si->outscnt);
+ fprintf(fp, " FWD %d", si->fwdscnt);
+ fprintf(fp, ")");
+ }
+
+ fprintf(fp, "%s", _SL_);
+ }
+ if (show_stats > 1) {
+ struct xfrmu_spdhinfo *sh;
+
+ if (tb[XFRMA_SPD_HINFO]) {
+ if (RTA_PAYLOAD(tb[XFRMA_SPD_HINFO]) < sizeof(*sh)) {
+ fprintf(stderr, "SPDinfo: Wrong len %d\n", len);
+ return -1;
+ }
+ sh = RTA_DATA(tb[XFRMA_SPD_HINFO]);
+ fprintf(fp, "\t SPD buckets:");
+ fprintf(fp, " count %d", sh->spdhcnt);
+ fprintf(fp, " Max %d", sh->spdhmcnt);
+ fprintf(fp, "%s", _SL_);
+ }
+ if (tb[XFRMA_SPD_IPV4_HTHRESH]) {
+ struct xfrmu_spdhthresh *th;
+
+ if (RTA_PAYLOAD(tb[XFRMA_SPD_IPV4_HTHRESH]) < sizeof(*th)) {
+ fprintf(stderr, "SPDinfo: Wrong len %d\n", len);
+ return -1;
+ }
+ th = RTA_DATA(tb[XFRMA_SPD_IPV4_HTHRESH]);
+ fprintf(fp, "\t SPD IPv4 thresholds:");
+ fprintf(fp, " local %d", th->lbits);
+ fprintf(fp, " remote %d", th->rbits);
+ fprintf(fp, "%s", _SL_);
+
+ }
+ if (tb[XFRMA_SPD_IPV6_HTHRESH]) {
+ struct xfrmu_spdhthresh *th;
+
+ if (RTA_PAYLOAD(tb[XFRMA_SPD_IPV6_HTHRESH]) < sizeof(*th)) {
+ fprintf(stderr, "SPDinfo: Wrong len %d\n", len);
+ return -1;
+ }
+ th = RTA_DATA(tb[XFRMA_SPD_IPV6_HTHRESH]);
+ fprintf(fp, "\t SPD IPv6 thresholds:");
+ fprintf(fp, " local %d", th->lbits);
+ fprintf(fp, " remote %d", th->rbits);
+ fprintf(fp, "%s", _SL_);
+ }
+ }
+
+ if (oneline)
+ fprintf(fp, "\n");
+
+ return 0;
+}
+
+static int xfrm_spd_setinfo(int argc, char **argv)
+{
+ struct rtnl_handle rth;
+ struct {
+ struct nlmsghdr n;
+ __u32 flags;
+ char buf[RTA_BUF_SIZE];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(__u32)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = XFRM_MSG_NEWSPDINFO,
+ .flags = 0XFFFFFFFF,
+ };
+
+ char *thr4 = NULL;
+ char *thr6 = NULL;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "hthresh4") == 0) {
+ struct xfrmu_spdhthresh thr;
+
+ if (thr4)
+ duparg("hthresh4", *argv);
+ thr4 = *argv;
+ NEXT_ARG();
+ if (get_u8(&thr.lbits, *argv, 0) || thr.lbits > 32)
+ invarg("hthresh4 LBITS value is invalid", *argv);
+ NEXT_ARG();
+ if (get_u8(&thr.rbits, *argv, 0) || thr.rbits > 32)
+ invarg("hthresh4 RBITS value is invalid", *argv);
+
+ addattr_l(&req.n, sizeof(req), XFRMA_SPD_IPV4_HTHRESH,
+ (void *)&thr, sizeof(thr));
+ } else if (strcmp(*argv, "hthresh6") == 0) {
+ struct xfrmu_spdhthresh thr;
+
+ if (thr6)
+ duparg("hthresh6", *argv);
+ thr6 = *argv;
+ NEXT_ARG();
+ if (get_u8(&thr.lbits, *argv, 0) || thr.lbits > 128)
+ invarg("hthresh6 LBITS value is invalid", *argv);
+ NEXT_ARG();
+ if (get_u8(&thr.rbits, *argv, 0) || thr.rbits > 128)
+ invarg("hthresh6 RBITS value is invalid", *argv);
+
+ addattr_l(&req.n, sizeof(req), XFRMA_SPD_IPV6_HTHRESH,
+ (void *)&thr, sizeof(thr));
+ } else {
+ invarg("unknown", *argv);
+ }
+
+ argc--; argv++;
+ }
+
+ if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+ exit(1);
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ exit(2);
+
+ rtnl_close(&rth);
+
+ return 0;
+}
+
+static int xfrm_spd_getinfo(int argc, char **argv)
+{
+ struct rtnl_handle rth;
+ struct {
+ struct nlmsghdr n;
+ __u32 flags;
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(__u32)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = XFRM_MSG_GETSPDINFO,
+ .flags = 0XFFFFFFFF,
+ };
+ struct nlmsghdr *answer;
+
+ if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+ exit(1);
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0)
+ exit(2);
+
+ print_spdinfo(answer, (void *)stdout);
+
+ free(answer);
+ rtnl_close(&rth);
+
+ return 0;
+}
+
+static int xfrm_str_to_policy(char *name, uint8_t *policy)
+{
+ if (strcmp(name, "block") == 0) {
+ *policy = XFRM_USERPOLICY_BLOCK;
+ return 0;
+ } else if (strcmp(name, "accept") == 0 ||
+ strcmp(name, "allow") == 0) {
+ *policy = XFRM_USERPOLICY_ACCEPT;
+ return 0;
+ }
+
+ return -1;
+}
+
+static char *xfrm_policy_to_str(uint8_t policy)
+{
+ switch (policy) {
+ case XFRM_USERPOLICY_UNSPEC:
+ return "unspec";
+ case XFRM_USERPOLICY_BLOCK:
+ return "block";
+ case XFRM_USERPOLICY_ACCEPT:
+ return "accept";
+ default:
+ return "unknown";
+ }
+}
+
+static int xfrm_spd_setdefault(int argc, char **argv)
+{
+ struct rtnl_handle rth;
+ struct {
+ struct nlmsghdr n;
+ struct xfrm_userpolicy_default up;
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct xfrm_userpolicy_default)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = XFRM_MSG_SETDEFAULT,
+ };
+
+ while (argc > 0) {
+ if (strcmp(*argv, "in") == 0) {
+ if (req.up.in)
+ duparg("in", *argv);
+
+ NEXT_ARG();
+ if (xfrm_str_to_policy(*argv, &req.up.in) < 0)
+ invarg("in policy value is invalid", *argv);
+ } else if (strcmp(*argv, "fwd") == 0) {
+ if (req.up.fwd)
+ duparg("fwd", *argv);
+
+ NEXT_ARG();
+ if (xfrm_str_to_policy(*argv, &req.up.fwd) < 0)
+ invarg("fwd policy value is invalid", *argv);
+ } else if (strcmp(*argv, "out") == 0) {
+ if (req.up.out)
+ duparg("out", *argv);
+
+ NEXT_ARG();
+ if (xfrm_str_to_policy(*argv, &req.up.out) < 0)
+ invarg("out policy value is invalid", *argv);
+ } else {
+ invarg("unknown direction", *argv);
+ }
+
+ argc--; argv++;
+ }
+
+ if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+ exit(1);
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ exit(2);
+
+ rtnl_close(&rth);
+
+ return 0;
+}
+
+int xfrm_policy_default_print(struct nlmsghdr *n, FILE *fp)
+{
+ struct xfrm_userpolicy_default *up = NLMSG_DATA(n);
+ int len = n->nlmsg_len - NLMSG_SPACE(sizeof(*up));
+
+ if (len < 0) {
+ fprintf(stderr,
+ "BUG: short nlmsg len %u (expect %lu) for XFRM_MSG_GETDEFAULT\n",
+ n->nlmsg_len, NLMSG_SPACE(sizeof(*up)));
+ return -1;
+ }
+
+ fprintf(fp, "Default policies:\n");
+ fprintf(fp, " in: %s\n", xfrm_policy_to_str(up->in));
+ fprintf(fp, " fwd: %s\n", xfrm_policy_to_str(up->fwd));
+ fprintf(fp, " out: %s\n", xfrm_policy_to_str(up->out));
+ fflush(fp);
+
+ return 0;
+}
+
+static int xfrm_spd_getdefault(int argc, char **argv)
+{
+ struct rtnl_handle rth;
+ struct {
+ struct nlmsghdr n;
+ struct xfrm_userpolicy_default up;
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct xfrm_userpolicy_default)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = XFRM_MSG_GETDEFAULT,
+ };
+ struct nlmsghdr *answer;
+
+ if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+ exit(1);
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0)
+ exit(2);
+
+ xfrm_policy_default_print(answer, (FILE *)stdout);
+
+ free(answer);
+ rtnl_close(&rth);
+
+ return 0;
+}
+
+static int xfrm_policy_flush(int argc, char **argv)
+{
+ struct rtnl_handle rth;
+ struct {
+ struct nlmsghdr n;
+ char buf[RTA_BUF_SIZE];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(0), /* nlmsg data is nothing */
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = XFRM_MSG_FLUSHPOLICY,
+ };
+ char *ptypep = NULL;
+ struct xfrm_userpolicy_type upt = {};
+
+ while (argc > 0) {
+ if (strcmp(*argv, "ptype") == 0) {
+ if (ptypep)
+ duparg("ptype", *argv);
+ ptypep = *argv;
+
+ NEXT_ARG();
+ xfrm_policy_ptype_parse(&upt.type, &argc, &argv);
+ } else
+ invarg("unknown", *argv);
+
+ argc--; argv++;
+ }
+
+ if (ptypep) {
+ addattr_l(&req.n, sizeof(req), XFRMA_POLICY_TYPE,
+ (void *)&upt, sizeof(upt));
+ }
+
+ if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+ exit(1);
+
+ if (show_stats > 1)
+ fprintf(stderr, "Flush policy\n");
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ exit(2);
+
+ rtnl_close(&rth);
+
+ return 0;
+}
+
+int do_xfrm_policy(int argc, char **argv)
+{
+ if (argc < 1)
+ return xfrm_policy_list_or_deleteall(0, NULL, 0);
+
+ if (matches(*argv, "add") == 0)
+ return xfrm_policy_modify(XFRM_MSG_NEWPOLICY, 0,
+ argc-1, argv+1);
+ if (matches(*argv, "update") == 0)
+ return xfrm_policy_modify(XFRM_MSG_UPDPOLICY, 0,
+ argc-1, argv+1);
+ if (matches(*argv, "delete") == 0)
+ return xfrm_policy_delete(argc-1, argv+1);
+ if (matches(*argv, "deleteall") == 0 || matches(*argv, "delall") == 0)
+ return xfrm_policy_list_or_deleteall(argc-1, argv+1, 1);
+ if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
+ || matches(*argv, "lst") == 0)
+ return xfrm_policy_list_or_deleteall(argc-1, argv+1, 0);
+ if (matches(*argv, "get") == 0)
+ return xfrm_policy_get(argc-1, argv+1);
+ if (matches(*argv, "flush") == 0)
+ return xfrm_policy_flush(argc-1, argv+1);
+ if (matches(*argv, "count") == 0)
+ return xfrm_spd_getinfo(argc, argv);
+ if (matches(*argv, "set") == 0)
+ return xfrm_spd_setinfo(argc-1, argv+1);
+ if (matches(*argv, "setdefault") == 0)
+ return xfrm_spd_setdefault(argc-1, argv+1);
+ if (matches(*argv, "getdefault") == 0)
+ return xfrm_spd_getdefault(argc-1, argv+1);
+ if (matches(*argv, "help") == 0)
+ usage();
+ fprintf(stderr, "Command \"%s\" is unknown, try \"ip xfrm policy help\".\n", *argv);
+ exit(-1);
+}
diff --git a/ip/xfrm_state.c b/ip/xfrm_state.c
new file mode 100644
index 0000000..9be65b2
--- /dev/null
+++ b/ip/xfrm_state.c
@@ -0,0 +1,1486 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C)2004 USAGI/WIDE Project
+ *
+ * based on iproute.c
+ *
+ * Authors:
+ * Masahide NAKAMURA @USAGI
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netdb.h>
+#include "utils.h"
+#include "xfrm.h"
+#include "ip_common.h"
+
+/* #define NLMSG_DELETEALL_BUF_SIZE (4096-512) */
+#define NLMSG_DELETEALL_BUF_SIZE 8192
+
+/*
+ * Receiving buffer defines:
+ * nlmsg
+ * data = struct xfrm_usersa_info
+ * rtattr
+ * rtattr
+ * ... (max count of rtattr is XFRM_MAX+1
+ *
+ * each rtattr data = struct xfrm_algo(dynamic size) or xfrm_address_t
+ */
+#define NLMSG_BUF_SIZE 4096
+#define RTA_BUF_SIZE 2048
+#define XFRM_ALGO_KEY_BUF_SIZE 512
+#define CTX_BUF_SIZE 256
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ip xfrm state { add | update } ID [ ALGO-LIST ] [ mode MODE ]\n"
+ " [ mark MARK [ mask MASK ] ] [ reqid REQID ] [ seq SEQ ]\n"
+ " [ replay-window SIZE ] [ replay-seq SEQ ] [ replay-oseq SEQ ]\n"
+ " [ replay-seq-hi SEQ ] [ replay-oseq-hi SEQ ]\n"
+ " [ flag FLAG-LIST ] [ sel SELECTOR ] [ LIMIT-LIST ] [ encap ENCAP ]\n"
+ " [ coa ADDR[/PLEN] ] [ ctx CTX ] [ extra-flag EXTRA-FLAG-LIST ]\n"
+ " [ offload [ crypto | packet ] dev DEV dir DIR ]\n"
+ " [ output-mark OUTPUT-MARK [ mask MASK ] ]\n"
+ " [ if_id IF_ID ] [ tfcpad LENGTH ]\n"
+ "Usage: ip xfrm state allocspi ID [ mode MODE ] [ mark MARK [ mask MASK ] ]\n"
+ " [ reqid REQID ] [ seq SEQ ] [ min SPI max SPI ]\n"
+ "Usage: ip xfrm state { delete | get } ID [ mark MARK [ mask MASK ] ]\n"
+ "Usage: ip xfrm state deleteall [ ID ] [ mode MODE ] [ reqid REQID ]\n"
+ " [ flag FLAG-LIST ]\n"
+ "Usage: ip xfrm state list [ nokeys ] [ ID ] [ mode MODE ] [ reqid REQID ]\n"
+ " [ flag FLAG-LIST ]\n"
+ "Usage: ip xfrm state flush [ proto XFRM-PROTO ]\n"
+ "Usage: ip xfrm state count\n"
+ "ID := [ src ADDR ] [ dst ADDR ] [ proto XFRM-PROTO ] [ spi SPI ]\n"
+ "XFRM-PROTO := ");
+ fprintf(stderr,
+ "%s | %s | %s | %s | %s\n",
+ strxf_xfrmproto(IPPROTO_ESP),
+ strxf_xfrmproto(IPPROTO_AH),
+ strxf_xfrmproto(IPPROTO_COMP),
+ strxf_xfrmproto(IPPROTO_ROUTING),
+ strxf_xfrmproto(IPPROTO_DSTOPTS));
+ fprintf(stderr,
+ "ALGO-LIST := [ ALGO-LIST ] ALGO\n"
+ "ALGO := { ");
+ fprintf(stderr,
+ "%s | %s",
+ strxf_algotype(XFRMA_ALG_CRYPT),
+ strxf_algotype(XFRMA_ALG_AUTH));
+ fprintf(stderr,
+ " } ALGO-NAME ALGO-KEYMAT |\n"
+ " %s", strxf_algotype(XFRMA_ALG_AUTH_TRUNC));
+ fprintf(stderr,
+ " ALGO-NAME ALGO-KEYMAT ALGO-TRUNC-LEN |\n"
+ " %s", strxf_algotype(XFRMA_ALG_AEAD));
+ fprintf(stderr,
+ " ALGO-NAME ALGO-KEYMAT ALGO-ICV-LEN |\n"
+ " %s", strxf_algotype(XFRMA_ALG_COMP));
+ fprintf(stderr,
+ " ALGO-NAME\n"
+ "MODE := transport | tunnel | beet | ro | in_trigger\n"
+ "FLAG-LIST := [ FLAG-LIST ] FLAG\n"
+ "FLAG := noecn | decap-dscp | nopmtudisc | wildrecv | icmp | af-unspec | align4 | esn\n"
+ "EXTRA-FLAG-LIST := [ EXTRA-FLAG-LIST ] EXTRA-FLAG\n"
+ "EXTRA-FLAG := dont-encap-dscp | oseq-may-wrap\n"
+ "SELECTOR := [ src ADDR[/PLEN] ] [ dst ADDR[/PLEN] ] [ dev DEV ] [ UPSPEC ]\n"
+ "UPSPEC := proto { { tcp | udp | sctp | dccp } [ sport PORT ] [ dport PORT ] |\n"
+ " { icmp | ipv6-icmp | mobility-header } [ type NUMBER ] [ code NUMBER ] |\n"
+ " gre [ key { DOTTED-QUAD | NUMBER } ] | PROTO }\n"
+ "LIMIT-LIST := [ LIMIT-LIST ] limit LIMIT\n"
+ "LIMIT := { time-soft | time-hard | time-use-soft | time-use-hard } SECONDS |\n"
+ " { byte-soft | byte-hard } SIZE | { packet-soft | packet-hard } COUNT\n"
+ "ENCAP := { espinudp | espinudp-nonike | espintcp } SPORT DPORT OADDR\n"
+ "DIR := in | out\n");
+
+ exit(-1);
+}
+
+static int xfrm_algo_parse(struct xfrm_algo *alg, enum xfrm_attr_type_t type,
+ char *name, char *key, char *buf, int max)
+{
+ int len;
+ int slen = strlen(key);
+
+ strlcpy(alg->alg_name, name, sizeof(alg->alg_name));
+
+ if (slen > 2 && strncmp(key, "0x", 2) == 0) {
+ /* split two chars "0x" from the top */
+ char *p = key + 2;
+ int plen = slen - 2;
+ int i;
+ int j;
+
+ /* Converting hexadecimal numbered string into real key;
+ * Convert each two chars into one char(value). If number
+ * of the length is odd, add zero on the top for rounding.
+ */
+
+ /* calculate length of the converted values(real key) */
+ len = (plen + 1) / 2;
+ if (len > max)
+ invarg("ALGO-KEYMAT value makes buffer overflow\n", key);
+
+ for (i = -(plen % 2), j = 0; j < len; i += 2, j++) {
+ char vbuf[3];
+ __u8 val;
+
+ vbuf[0] = i >= 0 ? p[i] : '0';
+ vbuf[1] = p[i + 1];
+ vbuf[2] = '\0';
+
+ if (get_u8(&val, vbuf, 16))
+ invarg("ALGO-KEYMAT value is invalid", key);
+
+ buf[j] = val;
+ }
+ } else {
+ len = slen;
+ if (len > 0) {
+ if (len > max)
+ invarg("ALGO-KEYMAT value makes buffer overflow\n", key);
+
+ memcpy(buf, key, len);
+ }
+ }
+
+ alg->alg_key_len = len * 8;
+
+ return 0;
+}
+
+static int xfrm_seq_parse(__u32 *seq, int *argcp, char ***argvp)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+
+ if (get_be32(seq, *argv, 0))
+ invarg("SEQ value is invalid", *argv);
+
+ *argcp = argc;
+ *argvp = argv;
+
+ return 0;
+}
+
+static int xfrm_state_flag_parse(__u8 *flags, int *argcp, char ***argvp)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+ int len = strlen(*argv);
+
+ if (len > 2 && strncmp(*argv, "0x", 2) == 0) {
+ __u8 val = 0;
+
+ if (get_u8(&val, *argv, 16))
+ invarg("FLAG value is invalid", *argv);
+ *flags = val;
+ } else {
+ while (1) {
+ if (strcmp(*argv, "noecn") == 0)
+ *flags |= XFRM_STATE_NOECN;
+ else if (strcmp(*argv, "decap-dscp") == 0)
+ *flags |= XFRM_STATE_DECAP_DSCP;
+ else if (strcmp(*argv, "nopmtudisc") == 0)
+ *flags |= XFRM_STATE_NOPMTUDISC;
+ else if (strcmp(*argv, "wildrecv") == 0)
+ *flags |= XFRM_STATE_WILDRECV;
+ else if (strcmp(*argv, "icmp") == 0)
+ *flags |= XFRM_STATE_ICMP;
+ else if (strcmp(*argv, "af-unspec") == 0)
+ *flags |= XFRM_STATE_AF_UNSPEC;
+ else if (strcmp(*argv, "align4") == 0)
+ *flags |= XFRM_STATE_ALIGN4;
+ else if (strcmp(*argv, "esn") == 0)
+ *flags |= XFRM_STATE_ESN;
+ else {
+ PREV_ARG(); /* back track */
+ break;
+ }
+
+ if (!NEXT_ARG_OK())
+ break;
+ NEXT_ARG();
+ }
+ }
+
+ *argcp = argc;
+ *argvp = argv;
+
+ return 0;
+}
+
+static int xfrm_state_extra_flag_parse(__u32 *extra_flags, int *argcp, char ***argvp)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+ int len = strlen(*argv);
+
+ if (len > 2 && strncmp(*argv, "0x", 2) == 0) {
+ __u32 val = 0;
+
+ if (get_u32(&val, *argv, 16))
+ invarg("\"EXTRA-FLAG\" is invalid", *argv);
+ *extra_flags = val;
+ } else {
+ while (1) {
+ if (strcmp(*argv, "dont-encap-dscp") == 0)
+ *extra_flags |= XFRM_SA_XFLAG_DONT_ENCAP_DSCP;
+ else if (strcmp(*argv, "oseq-may-wrap") == 0)
+ *extra_flags |= XFRM_SA_XFLAG_OSEQ_MAY_WRAP;
+ else {
+ PREV_ARG(); /* back track */
+ break;
+ }
+
+ if (!NEXT_ARG_OK())
+ break;
+ NEXT_ARG();
+ }
+ }
+
+ *argcp = argc;
+ *argvp = argv;
+
+ return 0;
+}
+
+static bool xfrm_offload_dir_parse(__u8 *dir, int *argcp, char ***argvp)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+
+ if (strcmp(*argv, "in") == 0)
+ *dir = XFRM_OFFLOAD_INBOUND;
+ else if (strcmp(*argv, "out") == 0)
+ *dir = 0;
+ else
+ return false;
+
+ *argcp = argc;
+ *argvp = argv;
+
+ return true;
+}
+
+static int xfrm_state_modify(int cmd, unsigned int flags, int argc, char **argv)
+{
+ struct rtnl_handle rth;
+ struct {
+ struct nlmsghdr n;
+ struct xfrm_usersa_info xsinfo;
+ char buf[RTA_BUF_SIZE];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(req.xsinfo)),
+ .n.nlmsg_flags = NLM_F_REQUEST | flags,
+ .n.nlmsg_type = cmd,
+ .xsinfo.family = preferred_family,
+ .xsinfo.lft.soft_byte_limit = XFRM_INF,
+ .xsinfo.lft.hard_byte_limit = XFRM_INF,
+ .xsinfo.lft.soft_packet_limit = XFRM_INF,
+ .xsinfo.lft.hard_packet_limit = XFRM_INF,
+ };
+ struct xfrm_replay_state replay = {};
+ struct xfrm_replay_state_esn replay_esn = {};
+ struct xfrm_user_offload xuo = {};
+ unsigned int ifindex = 0;
+ __u8 dir = 0;
+ bool is_offload = false, is_packet_offload = false;
+ __u32 replay_window = 0;
+ __u32 seq = 0, oseq = 0, seq_hi = 0, oseq_hi = 0;
+ char *idp = NULL;
+ char *aeadop = NULL;
+ char *ealgop = NULL;
+ char *aalgop = NULL;
+ char *calgop = NULL;
+ char *coap = NULL;
+ char *sctxp = NULL;
+ __u32 extra_flags = 0;
+ struct xfrm_mark mark = {0, 0};
+ struct {
+ struct xfrm_user_sec_ctx sctx;
+ char str[CTX_BUF_SIZE];
+ } ctx = {};
+ struct xfrm_mark output_mark = {0, 0};
+ bool is_if_id_set = false;
+ __u32 if_id = 0;
+ __u32 tfcpad = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "mode") == 0) {
+ NEXT_ARG();
+ xfrm_mode_parse(&req.xsinfo.mode, &argc, &argv);
+ } else if (strcmp(*argv, "mark") == 0) {
+ xfrm_parse_mark(&mark, &argc, &argv);
+ } else if (strcmp(*argv, "reqid") == 0) {
+ NEXT_ARG();
+ xfrm_reqid_parse(&req.xsinfo.reqid, &argc, &argv);
+ } else if (strcmp(*argv, "seq") == 0) {
+ NEXT_ARG();
+ xfrm_seq_parse(&req.xsinfo.seq, &argc, &argv);
+ } else if (strcmp(*argv, "replay-window") == 0) {
+ NEXT_ARG();
+ if (get_u32(&replay_window, *argv, 0))
+ invarg("value after \"replay-window\" is invalid", *argv);
+ } else if (strcmp(*argv, "replay-seq") == 0) {
+ NEXT_ARG();
+ if (get_u32(&seq, *argv, 0))
+ invarg("value after \"replay-seq\" is invalid", *argv);
+ } else if (strcmp(*argv, "replay-seq-hi") == 0) {
+ NEXT_ARG();
+ if (get_u32(&seq_hi, *argv, 0))
+ invarg("value after \"replay-seq-hi\" is invalid", *argv);
+ } else if (strcmp(*argv, "replay-oseq") == 0) {
+ NEXT_ARG();
+ if (get_u32(&oseq, *argv, 0))
+ invarg("value after \"replay-oseq\" is invalid", *argv);
+ } else if (strcmp(*argv, "replay-oseq-hi") == 0) {
+ NEXT_ARG();
+ if (get_u32(&oseq_hi, *argv, 0))
+ invarg("value after \"replay-oseq-hi\" is invalid", *argv);
+ } else if (strcmp(*argv, "flag") == 0) {
+ NEXT_ARG();
+ xfrm_state_flag_parse(&req.xsinfo.flags, &argc, &argv);
+ } else if (strcmp(*argv, "extra-flag") == 0) {
+ NEXT_ARG();
+ xfrm_state_extra_flag_parse(&extra_flags, &argc, &argv);
+ } else if (strcmp(*argv, "sel") == 0) {
+ NEXT_ARG();
+ preferred_family = AF_UNSPEC;
+ xfrm_selector_parse(&req.xsinfo.sel, &argc, &argv);
+ preferred_family = req.xsinfo.sel.family;
+ } else if (strcmp(*argv, "limit") == 0) {
+ NEXT_ARG();
+ xfrm_lifetime_cfg_parse(&req.xsinfo.lft, &argc, &argv);
+ } else if (strcmp(*argv, "encap") == 0) {
+ struct xfrm_encap_tmpl encap;
+ inet_prefix oa;
+ NEXT_ARG();
+ xfrm_encap_type_parse(&encap.encap_type, &argc, &argv);
+ NEXT_ARG();
+ if (get_be16(&encap.encap_sport, *argv, 0))
+ invarg("SPORT value after \"encap\" is invalid", *argv);
+ NEXT_ARG();
+ if (get_be16(&encap.encap_dport, *argv, 0))
+ invarg("DPORT value after \"encap\" is invalid", *argv);
+ NEXT_ARG();
+ get_addr(&oa, *argv, AF_UNSPEC);
+ memcpy(&encap.encap_oa, &oa.data, sizeof(encap.encap_oa));
+ addattr_l(&req.n, sizeof(req.buf), XFRMA_ENCAP,
+ (void *)&encap, sizeof(encap));
+ } else if (strcmp(*argv, "coa") == 0) {
+ inet_prefix coa;
+ xfrm_address_t xcoa = {};
+
+ if (coap)
+ duparg("coa", *argv);
+ coap = *argv;
+
+ NEXT_ARG();
+
+ get_prefix(&coa, *argv, preferred_family);
+ if (coa.family == AF_UNSPEC)
+ invarg("value after \"coa\" has an unrecognized address family", *argv);
+ if (coa.bytelen > sizeof(xcoa))
+ invarg("value after \"coa\" is too large", *argv);
+
+ memcpy(&xcoa, &coa.data, coa.bytelen);
+
+ addattr_l(&req.n, sizeof(req.buf), XFRMA_COADDR,
+ (void *)&xcoa, sizeof(xcoa));
+ } else if (strcmp(*argv, "ctx") == 0) {
+ char *context;
+
+ if (sctxp)
+ duparg("ctx", *argv);
+ sctxp = *argv;
+
+ NEXT_ARG();
+ context = *argv;
+
+ xfrm_sctx_parse((char *)&ctx.str, context, &ctx.sctx);
+ addattr_l(&req.n, sizeof(req.buf), XFRMA_SEC_CTX,
+ (void *)&ctx, ctx.sctx.len);
+ } else if (strcmp(*argv, "offload") == 0) {
+ NEXT_ARG();
+ /* If user doesn't provide offload mode, treat it as
+ * crypto one for the backward compatibility.
+ */
+ if (strcmp(*argv, "crypto") == 0)
+ NEXT_ARG();
+ else if (strcmp(*argv, "packet") == 0) {
+ is_packet_offload = true;
+ NEXT_ARG();
+ }
+
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ ifindex = ll_name_to_index(*argv);
+ if (!ifindex)
+ invarg("Invalid device name", *argv);
+ } else
+ invarg("Missing dev keyword", *argv);
+
+ NEXT_ARG();
+ if (strcmp(*argv, "dir") == 0) {
+ bool is_dir;
+
+ NEXT_ARG();
+ is_dir = xfrm_offload_dir_parse(&dir, &argc,
+ &argv);
+ if (!is_dir)
+ invarg("DIR value is invalid", *argv);
+ } else
+ invarg("Missing DIR keyword", *argv);
+ is_offload = true;
+ } else if (strcmp(*argv, "output-mark") == 0) {
+ NEXT_ARG();
+ if (get_u32(&output_mark.v, *argv, 0))
+ invarg("value after \"output-mark\" is invalid", *argv);
+ if (argc > 1) {
+ NEXT_ARG();
+ if (strcmp(*argv, "mask") == 0) {
+ NEXT_ARG();
+ if (get_u32(&output_mark.m, *argv, 0))
+ invarg("mask value is invalid\n", *argv);
+ } else {
+ PREV_ARG();
+ }
+ }
+ } else if (strcmp(*argv, "if_id") == 0) {
+ NEXT_ARG();
+ if (get_u32(&if_id, *argv, 0))
+ invarg("value after \"if_id\" is invalid", *argv);
+ is_if_id_set = true;
+ } else if (strcmp(*argv, "tfcpad") == 0) {
+ NEXT_ARG();
+ if (get_u32(&tfcpad, *argv, 0))
+ invarg("value after \"tfcpad\" is invalid", *argv);
+ } else {
+ /* try to assume ALGO */
+ int type = xfrm_algotype_getbyname(*argv);
+
+ switch (type) {
+ case XFRMA_ALG_AEAD:
+ case XFRMA_ALG_CRYPT:
+ case XFRMA_ALG_AUTH:
+ case XFRMA_ALG_AUTH_TRUNC:
+ case XFRMA_ALG_COMP:
+ {
+ /* ALGO */
+ struct {
+ union {
+ struct xfrm_algo alg;
+ struct xfrm_algo_aead aead;
+ struct xfrm_algo_auth auth;
+ } u;
+ char buf[XFRM_ALGO_KEY_BUF_SIZE];
+ } alg = {};
+ int len;
+ __u32 icvlen, trunclen;
+ char *name;
+ char *key = "";
+ char *buf;
+
+ switch (type) {
+ case XFRMA_ALG_AEAD:
+ if (ealgop || aalgop || aeadop)
+ duparg("ALGO-TYPE", *argv);
+ aeadop = *argv;
+ break;
+ case XFRMA_ALG_CRYPT:
+ if (ealgop || aeadop)
+ duparg("ALGO-TYPE", *argv);
+ ealgop = *argv;
+ break;
+ case XFRMA_ALG_AUTH:
+ case XFRMA_ALG_AUTH_TRUNC:
+ if (aalgop || aeadop)
+ duparg("ALGO-TYPE", *argv);
+ aalgop = *argv;
+ break;
+ case XFRMA_ALG_COMP:
+ if (calgop)
+ duparg("ALGO-TYPE", *argv);
+ calgop = *argv;
+ break;
+ default:
+ /* not reached */
+ invarg("ALGO-TYPE value is invalid\n", *argv);
+ }
+
+ if (!NEXT_ARG_OK())
+ missarg("ALGO-NAME");
+ NEXT_ARG();
+ name = *argv;
+
+ switch (type) {
+ case XFRMA_ALG_AEAD:
+ case XFRMA_ALG_CRYPT:
+ case XFRMA_ALG_AUTH:
+ case XFRMA_ALG_AUTH_TRUNC:
+ if (!NEXT_ARG_OK())
+ missarg("ALGO-KEYMAT");
+ NEXT_ARG();
+ key = *argv;
+ break;
+ }
+
+ buf = alg.u.alg.alg_key;
+ len = sizeof(alg.u.alg);
+
+ switch (type) {
+ case XFRMA_ALG_AEAD:
+ if (!NEXT_ARG_OK())
+ missarg("ALGO-ICV-LEN");
+ NEXT_ARG();
+ if (get_u32(&icvlen, *argv, 0))
+ invarg("ALGO-ICV-LEN value is invalid",
+ *argv);
+ alg.u.aead.alg_icv_len = icvlen;
+
+ buf = alg.u.aead.alg_key;
+ len = sizeof(alg.u.aead);
+ break;
+ case XFRMA_ALG_AUTH_TRUNC:
+ if (!NEXT_ARG_OK())
+ missarg("ALGO-TRUNC-LEN");
+ NEXT_ARG();
+ if (get_u32(&trunclen, *argv, 0))
+ invarg("ALGO-TRUNC-LEN value is invalid",
+ *argv);
+ alg.u.auth.alg_trunc_len = trunclen;
+
+ buf = alg.u.auth.alg_key;
+ len = sizeof(alg.u.auth);
+ break;
+ }
+
+ xfrm_algo_parse((void *)&alg, type, name, key,
+ buf, sizeof(alg.buf));
+ len += alg.u.alg.alg_key_len / 8;
+
+ addattr_l(&req.n, sizeof(req.buf), type,
+ (void *)&alg, len);
+ break;
+ }
+ default:
+ /* try to assume ID */
+ if (idp)
+ invarg("unknown", *argv);
+ idp = *argv;
+
+ /* ID */
+ xfrm_id_parse(&req.xsinfo.saddr, &req.xsinfo.id,
+ &req.xsinfo.family, 0, &argc, &argv);
+ if (preferred_family == AF_UNSPEC)
+ preferred_family = req.xsinfo.family;
+ }
+ }
+ argc--; argv++;
+ }
+
+ if (req.xsinfo.flags & XFRM_STATE_ESN &&
+ replay_window == 0) {
+ fprintf(stderr, "Error: esn flag set without replay-window.\n");
+ exit(-1);
+ }
+
+ if (replay_window > XFRMA_REPLAY_ESN_MAX) {
+ fprintf(stderr,
+ "Error: replay-window (%u) > XFRMA_REPLAY_ESN_MAX (%u).\n",
+ replay_window, XFRMA_REPLAY_ESN_MAX);
+ exit(-1);
+ }
+
+ if (is_offload) {
+ xuo.ifindex = ifindex;
+ xuo.flags = dir;
+ if (is_packet_offload)
+ xuo.flags |= XFRM_OFFLOAD_PACKET;
+ addattr_l(&req.n, sizeof(req.buf), XFRMA_OFFLOAD_DEV, &xuo,
+ sizeof(xuo));
+ }
+ if (req.xsinfo.flags & XFRM_STATE_ESN ||
+ replay_window > (sizeof(replay.bitmap) * 8)) {
+ replay_esn.seq = seq;
+ replay_esn.oseq = oseq;
+ replay_esn.seq_hi = seq_hi;
+ replay_esn.oseq_hi = oseq_hi;
+ replay_esn.replay_window = replay_window;
+ replay_esn.bmp_len = (replay_window + sizeof(__u32) * 8 - 1) /
+ (sizeof(__u32) * 8);
+ addattr_l(&req.n, sizeof(req.buf), XFRMA_REPLAY_ESN_VAL,
+ &replay_esn, sizeof(replay_esn));
+ } else {
+ if (seq || oseq) {
+ replay.seq = seq;
+ replay.oseq = oseq;
+ addattr_l(&req.n, sizeof(req.buf), XFRMA_REPLAY_VAL,
+ &replay, sizeof(replay));
+ }
+ req.xsinfo.replay_window = replay_window;
+ }
+
+ if (extra_flags)
+ addattr32(&req.n, sizeof(req.buf), XFRMA_SA_EXTRA_FLAGS,
+ extra_flags);
+
+ if (!idp) {
+ fprintf(stderr, "Not enough information: ID is required\n");
+ exit(1);
+ }
+
+ if (mark.m) {
+ int r = addattr_l(&req.n, sizeof(req.buf), XFRMA_MARK,
+ (void *)&mark, sizeof(mark));
+ if (r < 0) {
+ fprintf(stderr, "XFRMA_MARK failed\n");
+ exit(1);
+ }
+ }
+
+ if (is_if_id_set)
+ addattr32(&req.n, sizeof(req.buf), XFRMA_IF_ID, if_id);
+
+ if (tfcpad)
+ addattr32(&req.n, sizeof(req.buf), XFRMA_TFCPAD, tfcpad);
+
+ if (xfrm_xfrmproto_is_ipsec(req.xsinfo.id.proto)) {
+ switch (req.xsinfo.mode) {
+ case XFRM_MODE_TRANSPORT:
+ case XFRM_MODE_TUNNEL:
+ break;
+ case XFRM_MODE_BEET:
+ if (req.xsinfo.id.proto == IPPROTO_ESP)
+ break;
+ /* fallthrough */
+ default:
+ fprintf(stderr, "MODE value is invalid with XFRM-PROTO value \"%s\"\n",
+ strxf_xfrmproto(req.xsinfo.id.proto));
+ exit(1);
+ }
+
+ switch (req.xsinfo.id.proto) {
+ case IPPROTO_ESP:
+ if (calgop) {
+ fprintf(stderr, "ALGO-TYPE value \"%s\" is invalid with XFRM-PROTO value \"%s\"\n",
+ strxf_algotype(XFRMA_ALG_COMP),
+ strxf_xfrmproto(req.xsinfo.id.proto));
+ exit(1);
+ }
+ if (!ealgop && !aeadop) {
+ fprintf(stderr, "ALGO-TYPE value \"%s\" or \"%s\" is required with XFRM-PROTO value \"%s\"\n",
+ strxf_algotype(XFRMA_ALG_CRYPT),
+ strxf_algotype(XFRMA_ALG_AEAD),
+ strxf_xfrmproto(req.xsinfo.id.proto));
+ exit(1);
+ }
+ break;
+ case IPPROTO_AH:
+ if (ealgop || aeadop || calgop) {
+ fprintf(stderr, "ALGO-TYPE values \"%s\", \"%s\", and \"%s\" are invalid with XFRM-PROTO value \"%s\"\n",
+ strxf_algotype(XFRMA_ALG_CRYPT),
+ strxf_algotype(XFRMA_ALG_AEAD),
+ strxf_algotype(XFRMA_ALG_COMP),
+ strxf_xfrmproto(req.xsinfo.id.proto));
+ exit(1);
+ }
+ if (!aalgop) {
+ fprintf(stderr, "ALGO-TYPE value \"%s\" or \"%s\" is required with XFRM-PROTO value \"%s\"\n",
+ strxf_algotype(XFRMA_ALG_AUTH),
+ strxf_algotype(XFRMA_ALG_AUTH_TRUNC),
+ strxf_xfrmproto(req.xsinfo.id.proto));
+ exit(1);
+ }
+ break;
+ case IPPROTO_COMP:
+ if (ealgop || aalgop || aeadop) {
+ fprintf(stderr, "ALGO-TYPE values \"%s\", \"%s\", \"%s\", and \"%s\" are invalid with XFRM-PROTO value \"%s\"\n",
+ strxf_algotype(XFRMA_ALG_CRYPT),
+ strxf_algotype(XFRMA_ALG_AUTH),
+ strxf_algotype(XFRMA_ALG_AUTH_TRUNC),
+ strxf_algotype(XFRMA_ALG_AEAD),
+ strxf_xfrmproto(req.xsinfo.id.proto));
+ exit(1);
+ }
+ if (!calgop) {
+ fprintf(stderr, "ALGO-TYPE value \"%s\" is required with XFRM-PROTO value \"%s\"\n",
+ strxf_algotype(XFRMA_ALG_COMP),
+ strxf_xfrmproto(req.xsinfo.id.proto));
+ exit(1);
+ }
+ break;
+ }
+ } else {
+ if (ealgop || aalgop || aeadop || calgop) {
+ fprintf(stderr, "ALGO is invalid with XFRM-PROTO value \"%s\"\n",
+ strxf_xfrmproto(req.xsinfo.id.proto));
+ exit(1);
+ }
+ }
+
+ if (xfrm_xfrmproto_is_ro(req.xsinfo.id.proto)) {
+ switch (req.xsinfo.mode) {
+ case XFRM_MODE_ROUTEOPTIMIZATION:
+ case XFRM_MODE_IN_TRIGGER:
+ break;
+ case 0:
+ fprintf(stderr, "\"mode\" is required with XFRM-PROTO value \"%s\"\n",
+ strxf_xfrmproto(req.xsinfo.id.proto));
+ exit(1);
+ default:
+ fprintf(stderr, "MODE value is invalid with XFRM-PROTO value \"%s\"\n",
+ strxf_xfrmproto(req.xsinfo.id.proto));
+ exit(1);
+ }
+
+ if (!coap) {
+ fprintf(stderr, "\"coa\" is required with XFRM-PROTO value \"%s\"\n",
+ strxf_xfrmproto(req.xsinfo.id.proto));
+ exit(1);
+ }
+ } else {
+ if (coap) {
+ fprintf(stderr, "\"coa\" is invalid with XFRM-PROTO value \"%s\"\n",
+ strxf_xfrmproto(req.xsinfo.id.proto));
+ exit(1);
+ }
+ }
+
+ if (output_mark.v)
+ addattr32(&req.n, sizeof(req.buf), XFRMA_OUTPUT_MARK, output_mark.v);
+
+ if (output_mark.m)
+ addattr32(&req.n, sizeof(req.buf), XFRMA_SET_MARK_MASK, output_mark.m);
+
+ if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+ exit(1);
+
+ if (req.xsinfo.family == AF_UNSPEC)
+ req.xsinfo.family = AF_INET;
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ exit(2);
+
+ rtnl_close(&rth);
+
+ return 0;
+}
+
+static int xfrm_state_allocspi(int argc, char **argv)
+{
+ struct rtnl_handle rth;
+ struct {
+ struct nlmsghdr n;
+ struct xfrm_userspi_info xspi;
+ char buf[RTA_BUF_SIZE];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(req.xspi)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = XFRM_MSG_ALLOCSPI,
+ .xspi.info.family = preferred_family,
+ };
+ char *idp = NULL;
+ char *minp = NULL;
+ char *maxp = NULL;
+ struct xfrm_mark mark = {0, 0};
+ struct nlmsghdr *answer;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "mode") == 0) {
+ NEXT_ARG();
+ xfrm_mode_parse(&req.xspi.info.mode, &argc, &argv);
+ } else if (strcmp(*argv, "mark") == 0) {
+ xfrm_parse_mark(&mark, &argc, &argv);
+ } else if (strcmp(*argv, "reqid") == 0) {
+ NEXT_ARG();
+ xfrm_reqid_parse(&req.xspi.info.reqid, &argc, &argv);
+ } else if (strcmp(*argv, "seq") == 0) {
+ NEXT_ARG();
+ xfrm_seq_parse(&req.xspi.info.seq, &argc, &argv);
+ } else if (strcmp(*argv, "min") == 0) {
+ if (minp)
+ duparg("min", *argv);
+ minp = *argv;
+
+ NEXT_ARG();
+
+ if (get_u32(&req.xspi.min, *argv, 0))
+ invarg("value after \"min\" is invalid", *argv);
+ } else if (strcmp(*argv, "max") == 0) {
+ if (maxp)
+ duparg("max", *argv);
+ maxp = *argv;
+
+ NEXT_ARG();
+
+ if (get_u32(&req.xspi.max, *argv, 0))
+ invarg("value after \"max\" is invalid", *argv);
+ } else {
+ /* try to assume ID */
+ if (idp)
+ invarg("unknown", *argv);
+ idp = *argv;
+
+ /* ID */
+ xfrm_id_parse(&req.xspi.info.saddr, &req.xspi.info.id,
+ &req.xspi.info.family, 0, &argc, &argv);
+ if (req.xspi.info.id.spi) {
+ fprintf(stderr, "\"spi\" is invalid\n");
+ exit(1);
+ }
+ if (preferred_family == AF_UNSPEC)
+ preferred_family = req.xspi.info.family;
+ }
+ argc--; argv++;
+ }
+
+ if (!idp) {
+ fprintf(stderr, "Not enough information: ID is required\n");
+ exit(1);
+ }
+
+ if (minp) {
+ if (!maxp) {
+ fprintf(stderr, "\"max\" is missing\n");
+ exit(1);
+ }
+ if (req.xspi.min > req.xspi.max) {
+ fprintf(stderr, "value after \"min\" is larger than value after \"max\"\n");
+ exit(1);
+ }
+ } else {
+ if (maxp) {
+ fprintf(stderr, "\"min\" is missing\n");
+ exit(1);
+ }
+
+ /* XXX: Default value defined in PF_KEY;
+ * See kernel's net/key/af_key.c(pfkey_getspi).
+ */
+ req.xspi.min = 0x100;
+ req.xspi.max = 0x0fffffff;
+
+ /* XXX: IPCOMP spi is 16-bits;
+ * See kernel's net/xfrm/xfrm_user(verify_userspi_info).
+ */
+ if (req.xspi.info.id.proto == IPPROTO_COMP)
+ req.xspi.max = 0xffff;
+ }
+
+ if (mark.m & mark.v) {
+ int r = addattr_l(&req.n, sizeof(req.buf), XFRMA_MARK,
+ (void *)&mark, sizeof(mark));
+ if (r < 0) {
+ fprintf(stderr, "XFRMA_MARK failed\n");
+ exit(1);
+ }
+ }
+
+ if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+ exit(1);
+
+ if (req.xspi.info.family == AF_UNSPEC)
+ req.xspi.info.family = AF_INET;
+
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0)
+ exit(2);
+
+ if (xfrm_state_print(answer, (void *)stdout) < 0) {
+ fprintf(stderr, "An error :-)\n");
+ exit(1);
+ }
+
+ free(answer);
+ rtnl_close(&rth);
+
+ return 0;
+}
+
+static int xfrm_state_filter_match(struct xfrm_usersa_info *xsinfo)
+{
+ if (!filter.use)
+ return 1;
+
+ if (filter.xsinfo.family != AF_UNSPEC &&
+ filter.xsinfo.family != xsinfo->family)
+ return 0;
+
+ if (filter.id_src_mask)
+ if (xfrm_addr_match(&xsinfo->saddr, &filter.xsinfo.saddr,
+ filter.id_src_mask))
+ return 0;
+ if (filter.id_dst_mask)
+ if (xfrm_addr_match(&xsinfo->id.daddr, &filter.xsinfo.id.daddr,
+ filter.id_dst_mask))
+ return 0;
+ if ((xsinfo->id.proto^filter.xsinfo.id.proto)&filter.id_proto_mask)
+ return 0;
+ if ((xsinfo->id.spi^filter.xsinfo.id.spi)&filter.id_spi_mask)
+ return 0;
+ if ((xsinfo->mode^filter.xsinfo.mode)&filter.mode_mask)
+ return 0;
+ if ((xsinfo->reqid^filter.xsinfo.reqid)&filter.reqid_mask)
+ return 0;
+ if (filter.state_flags_mask)
+ if ((xsinfo->flags & filter.xsinfo.flags) == 0)
+ return 0;
+
+ return 1;
+}
+
+static int __do_xfrm_state_print(struct nlmsghdr *n, void *arg, bool nokeys)
+{
+ FILE *fp = (FILE *)arg;
+ struct rtattr *tb[XFRMA_MAX+1];
+ struct rtattr *rta;
+ struct xfrm_usersa_info *xsinfo = NULL;
+ struct xfrm_user_expire *xexp = NULL;
+ struct xfrm_usersa_id *xsid = NULL;
+ int len = n->nlmsg_len;
+
+ if (n->nlmsg_type != XFRM_MSG_NEWSA &&
+ n->nlmsg_type != XFRM_MSG_DELSA &&
+ n->nlmsg_type != XFRM_MSG_UPDSA &&
+ n->nlmsg_type != XFRM_MSG_EXPIRE) {
+ fprintf(stderr, "Not a state: %08x %08x %08x\n",
+ n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+ return 0;
+ }
+
+ if (n->nlmsg_type == XFRM_MSG_DELSA) {
+ /* Don't blame me for this .. Herbert made me do it */
+ xsid = NLMSG_DATA(n);
+ len -= NLMSG_SPACE(sizeof(*xsid));
+ } else if (n->nlmsg_type == XFRM_MSG_EXPIRE) {
+ xexp = NLMSG_DATA(n);
+ xsinfo = &xexp->state;
+ len -= NLMSG_SPACE(sizeof(*xexp));
+ } else {
+ xexp = NULL;
+ xsinfo = NLMSG_DATA(n);
+ len -= NLMSG_SPACE(sizeof(*xsinfo));
+ }
+
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ if (xsinfo && !xfrm_state_filter_match(xsinfo))
+ return 0;
+
+ if (n->nlmsg_type == XFRM_MSG_DELSA)
+ fprintf(fp, "Deleted ");
+ else if (n->nlmsg_type == XFRM_MSG_UPDSA)
+ fprintf(fp, "Updated ");
+ else if (n->nlmsg_type == XFRM_MSG_EXPIRE)
+ fprintf(fp, "Expired ");
+
+ if (n->nlmsg_type == XFRM_MSG_DELSA)
+ rta = XFRMSID_RTA(xsid);
+ else if (n->nlmsg_type == XFRM_MSG_EXPIRE)
+ rta = XFRMEXP_RTA(xexp);
+ else
+ rta = XFRMS_RTA(xsinfo);
+
+ parse_rtattr(tb, XFRMA_MAX, rta, len);
+
+ if (n->nlmsg_type == XFRM_MSG_DELSA) {
+ /* xfrm_policy_id_print(); */
+
+ if (!tb[XFRMA_SA]) {
+ fprintf(stderr, "Buggy XFRM_MSG_DELSA: no XFRMA_SA\n");
+ return -1;
+ }
+ if (RTA_PAYLOAD(tb[XFRMA_SA]) < sizeof(*xsinfo)) {
+ fprintf(stderr, "Buggy XFRM_MSG_DELPOLICY: too short XFRMA_POLICY len\n");
+ return -1;
+ }
+ xsinfo = RTA_DATA(tb[XFRMA_SA]);
+ }
+
+ xfrm_state_info_print(xsinfo, tb, fp, NULL, NULL, nokeys);
+
+ if (n->nlmsg_type == XFRM_MSG_EXPIRE) {
+ fprintf(fp, "\t");
+ fprintf(fp, "hard %u", xexp->hard);
+ fprintf(fp, "%s", _SL_);
+ }
+
+ if (oneline)
+ fprintf(fp, "\n");
+ fflush(fp);
+
+ return 0;
+}
+
+int xfrm_state_print(struct nlmsghdr *n, void *arg)
+{
+ return __do_xfrm_state_print(n, arg, false);
+}
+
+static int xfrm_state_print_nokeys(struct nlmsghdr *n, void *arg)
+{
+ return __do_xfrm_state_print(n, arg, true);
+}
+
+static int xfrm_state_get_or_delete(int argc, char **argv, int delete)
+{
+ struct rtnl_handle rth;
+ struct {
+ struct nlmsghdr n;
+ struct xfrm_usersa_id xsid;
+ char buf[RTA_BUF_SIZE];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(req.xsid)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = delete ? XFRM_MSG_DELSA : XFRM_MSG_GETSA,
+ .xsid.family = preferred_family,
+ };
+ struct xfrm_id id;
+ char *idp = NULL;
+ struct xfrm_mark mark = {0, 0};
+
+ while (argc > 0) {
+ xfrm_address_t saddr;
+
+ if (strcmp(*argv, "mark") == 0) {
+ xfrm_parse_mark(&mark, &argc, &argv);
+ } else {
+ if (idp)
+ invarg("unknown", *argv);
+ idp = *argv;
+
+ /* ID */
+ memset(&id, 0, sizeof(id));
+ memset(&saddr, 0, sizeof(saddr));
+ xfrm_id_parse(&saddr, &id, &req.xsid.family, 0,
+ &argc, &argv);
+
+ memcpy(&req.xsid.daddr, &id.daddr, sizeof(req.xsid.daddr));
+ req.xsid.spi = id.spi;
+ req.xsid.proto = id.proto;
+
+ addattr_l(&req.n, sizeof(req.buf), XFRMA_SRCADDR,
+ (void *)&saddr, sizeof(saddr));
+ }
+
+ argc--; argv++;
+ }
+
+ if (mark.m & mark.v) {
+ int r = addattr_l(&req.n, sizeof(req.buf), XFRMA_MARK,
+ (void *)&mark, sizeof(mark));
+ if (r < 0) {
+ fprintf(stderr, "XFRMA_MARK failed\n");
+ exit(1);
+ }
+ }
+
+ if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+ exit(1);
+
+ if (req.xsid.family == AF_UNSPEC)
+ req.xsid.family = AF_INET;
+
+ if (delete) {
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ exit(2);
+ } else {
+ struct nlmsghdr *answer;
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0)
+ exit(2);
+
+ if (xfrm_state_print(answer, (void *)stdout) < 0) {
+ fprintf(stderr, "An error :-)\n");
+ exit(1);
+ }
+
+ free(answer);
+ }
+
+ rtnl_close(&rth);
+
+ return 0;
+}
+
+/*
+ * With an existing state of nlmsg, make new nlmsg for deleting the state
+ * and store it to buffer.
+ */
+static int xfrm_state_keep(struct nlmsghdr *n, void *arg)
+{
+ struct xfrm_buffer *xb = (struct xfrm_buffer *)arg;
+ struct rtnl_handle *rth = xb->rth;
+ struct xfrm_usersa_info *xsinfo = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct nlmsghdr *new_n;
+ struct xfrm_usersa_id *xsid;
+ struct rtattr *tb[XFRMA_MAX+1];
+
+ if (n->nlmsg_type != XFRM_MSG_NEWSA) {
+ fprintf(stderr, "Not a state: %08x %08x %08x\n",
+ n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+ return 0;
+ }
+
+ len -= NLMSG_LENGTH(sizeof(*xsinfo));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ if (!xfrm_state_filter_match(xsinfo))
+ return 0;
+
+ if (xsinfo->id.proto == IPPROTO_IPIP ||
+ xsinfo->id.proto == IPPROTO_IPV6)
+ return 0;
+
+ if (xb->offset > xb->size) {
+ fprintf(stderr, "State buffer overflow\n");
+ return -1;
+ }
+
+ new_n = (struct nlmsghdr *)(xb->buf + xb->offset);
+ new_n->nlmsg_len = NLMSG_LENGTH(sizeof(*xsid));
+ new_n->nlmsg_flags = NLM_F_REQUEST;
+ new_n->nlmsg_type = XFRM_MSG_DELSA;
+ new_n->nlmsg_seq = ++rth->seq;
+
+ xsid = NLMSG_DATA(new_n);
+ xsid->family = xsinfo->family;
+ memcpy(&xsid->daddr, &xsinfo->id.daddr, sizeof(xsid->daddr));
+ xsid->spi = xsinfo->id.spi;
+ xsid->proto = xsinfo->id.proto;
+
+ addattr_l(new_n, xb->size, XFRMA_SRCADDR, &xsinfo->saddr,
+ sizeof(xsid->daddr));
+
+ parse_rtattr(tb, XFRMA_MAX, XFRMS_RTA(xsinfo), len);
+
+ if (tb[XFRMA_MARK]) {
+ int r = addattr_l(new_n, xb->size, XFRMA_MARK,
+ (void *)RTA_DATA(tb[XFRMA_MARK]), tb[XFRMA_MARK]->rta_len);
+ if (r < 0) {
+ fprintf(stderr, "%s: XFRMA_MARK failed\n", __func__);
+ exit(1);
+ }
+ }
+
+ xb->offset += new_n->nlmsg_len;
+ xb->nlmsg_count++;
+
+ return 0;
+}
+
+static int xfrm_state_list_or_deleteall(int argc, char **argv, int deleteall)
+{
+ char *idp = NULL;
+ struct rtnl_handle rth;
+ bool nokeys = false;
+
+ if (argc > 0 || preferred_family != AF_UNSPEC)
+ filter.use = 1;
+ filter.xsinfo.family = preferred_family;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "nokeys") == 0) {
+ nokeys = true;
+ } else if (strcmp(*argv, "mode") == 0) {
+ NEXT_ARG();
+ xfrm_mode_parse(&filter.xsinfo.mode, &argc, &argv);
+
+ filter.mode_mask = XFRM_FILTER_MASK_FULL;
+
+ } else if (strcmp(*argv, "reqid") == 0) {
+ NEXT_ARG();
+ xfrm_reqid_parse(&filter.xsinfo.reqid, &argc, &argv);
+
+ filter.reqid_mask = XFRM_FILTER_MASK_FULL;
+
+ } else if (strcmp(*argv, "flag") == 0) {
+ NEXT_ARG();
+ xfrm_state_flag_parse(&filter.xsinfo.flags, &argc, &argv);
+
+ filter.state_flags_mask = XFRM_FILTER_MASK_FULL;
+
+ } else {
+ if (idp)
+ invarg("unknown", *argv);
+ idp = *argv;
+
+ /* ID */
+ xfrm_id_parse(&filter.xsinfo.saddr, &filter.xsinfo.id,
+ &filter.xsinfo.family, 1, &argc, &argv);
+ if (preferred_family == AF_UNSPEC)
+ preferred_family = filter.xsinfo.family;
+ }
+ argc--; argv++;
+ }
+
+ if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+ exit(1);
+
+ if (deleteall) {
+ struct xfrm_buffer xb;
+ char buf[NLMSG_DELETEALL_BUF_SIZE];
+ int i;
+
+ xb.buf = buf;
+ xb.size = sizeof(buf);
+ xb.rth = &rth;
+
+ for (i = 0; ; i++) {
+ struct {
+ struct nlmsghdr n;
+ char buf[NLMSG_BUF_SIZE];
+ } req = {
+ .n.nlmsg_len = NLMSG_HDRLEN,
+ .n.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+ .n.nlmsg_type = XFRM_MSG_GETSA,
+ .n.nlmsg_seq = rth.dump = ++rth.seq,
+ };
+
+ xb.offset = 0;
+ xb.nlmsg_count = 0;
+
+ if (show_stats > 1)
+ fprintf(stderr, "Delete-all round = %d\n", i);
+
+ if (rtnl_send(&rth, (void *)&req, req.n.nlmsg_len) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+
+ if (rtnl_dump_filter(&rth, xfrm_state_keep, &xb) < 0) {
+ fprintf(stderr, "Delete-all terminated\n");
+ exit(1);
+ }
+ if (xb.nlmsg_count == 0) {
+ if (show_stats > 1)
+ fprintf(stderr, "Delete-all completed\n");
+ break;
+ }
+
+ if (rtnl_send_check(&rth, xb.buf, xb.offset) < 0) {
+ perror("Failed to send delete-all request\n");
+ exit(1);
+ }
+ if (show_stats > 1)
+ fprintf(stderr, "Delete-all nlmsg count = %d\n", xb.nlmsg_count);
+
+ xb.offset = 0;
+ xb.nlmsg_count = 0;
+ }
+
+ } else {
+ struct xfrm_address_filter addrfilter = {
+ .saddr = filter.xsinfo.saddr,
+ .daddr = filter.xsinfo.id.daddr,
+ .family = filter.xsinfo.family,
+ .splen = filter.id_src_mask,
+ .dplen = filter.id_dst_mask,
+ };
+ struct {
+ struct nlmsghdr n;
+ char buf[NLMSG_BUF_SIZE];
+ } req = {
+ .n.nlmsg_len = NLMSG_HDRLEN,
+ .n.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+ .n.nlmsg_type = XFRM_MSG_GETSA,
+ .n.nlmsg_seq = rth.dump = ++rth.seq,
+ };
+
+ if (filter.xsinfo.id.proto)
+ addattr8(&req.n, sizeof(req), XFRMA_PROTO,
+ filter.xsinfo.id.proto);
+ addattr_l(&req.n, sizeof(req), XFRMA_ADDRESS_FILTER,
+ &addrfilter, sizeof(addrfilter));
+
+ if (rtnl_send(&rth, (void *)&req, req.n.nlmsg_len) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+
+ rtnl_filter_t filter = nokeys ?
+ xfrm_state_print_nokeys : xfrm_state_print;
+ if (rtnl_dump_filter(&rth, filter, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ }
+
+ rtnl_close(&rth);
+
+ exit(0);
+}
+
+static int print_sadinfo(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+ __u32 *f = NLMSG_DATA(n);
+ struct rtattr *tb[XFRMA_SAD_MAX+1];
+ struct rtattr *rta;
+ int len = n->nlmsg_len;
+
+ len -= NLMSG_LENGTH(sizeof(__u32));
+ if (len < 0) {
+ fprintf(stderr, "SADinfo: Wrong len %d\n", len);
+ return -1;
+ }
+
+ rta = XFRMSAPD_RTA(f);
+ parse_rtattr(tb, XFRMA_SAD_MAX, rta, len);
+
+ if (tb[XFRMA_SAD_CNT]) {
+ __u32 cnt;
+
+ fprintf(fp, "\t SAD");
+ cnt = rta_getattr_u32(tb[XFRMA_SAD_CNT]);
+ fprintf(fp, " count %u", cnt);
+ } else {
+ fprintf(fp, "BAD SAD info returned\n");
+ return -1;
+ }
+
+ if (show_stats) {
+ if (tb[XFRMA_SAD_HINFO]) {
+ struct xfrmu_sadhinfo *si;
+
+ if (RTA_PAYLOAD(tb[XFRMA_SAD_HINFO]) < sizeof(*si)) {
+ fprintf(fp, "BAD SAD length returned\n");
+ return -1;
+ }
+
+ si = RTA_DATA(tb[XFRMA_SAD_HINFO]);
+ fprintf(fp, " (buckets ");
+ fprintf(fp, "count %d", si->sadhcnt);
+ fprintf(fp, " Max %d", si->sadhmcnt);
+ fprintf(fp, ")");
+ }
+ }
+ fprintf(fp, "\n");
+
+ return 0;
+}
+
+static int xfrm_sad_getinfo(int argc, char **argv)
+{
+ struct rtnl_handle rth;
+ struct {
+ struct nlmsghdr n;
+ __u32 flags;
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(req.flags)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = XFRM_MSG_GETSADINFO,
+ .flags = 0XFFFFFFFF,
+ };
+ struct nlmsghdr *answer;
+
+ if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+ exit(1);
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0)
+ exit(2);
+
+ print_sadinfo(answer, (void *)stdout);
+
+ free(answer);
+ rtnl_close(&rth);
+
+ return 0;
+}
+
+static int xfrm_state_flush(int argc, char **argv)
+{
+ struct rtnl_handle rth;
+ struct {
+ struct nlmsghdr n;
+ struct xfrm_usersa_flush xsf;
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(req.xsf)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = XFRM_MSG_FLUSHSA,
+ };
+ char *protop = NULL;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "proto") == 0) {
+ int ret;
+
+ if (protop)
+ duparg("proto", *argv);
+ protop = *argv;
+
+ NEXT_ARG();
+
+ ret = xfrm_xfrmproto_getbyname(*argv);
+ if (ret < 0)
+ invarg("XFRM-PROTO value is invalid", *argv);
+
+ req.xsf.proto = (__u8)ret;
+ } else
+ invarg("unknown", *argv);
+
+ argc--; argv++;
+ }
+
+ if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+ exit(1);
+
+ if (show_stats > 1)
+ fprintf(stderr, "Flush state with XFRM-PROTO value \"%s\"\n",
+ strxf_xfrmproto(req.xsf.proto));
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ exit(2);
+
+ rtnl_close(&rth);
+
+ return 0;
+}
+
+int do_xfrm_state(int argc, char **argv)
+{
+ if (argc < 1)
+ return xfrm_state_list_or_deleteall(0, NULL, 0);
+
+ if (matches(*argv, "add") == 0)
+ return xfrm_state_modify(XFRM_MSG_NEWSA, 0,
+ argc-1, argv+1);
+ if (matches(*argv, "update") == 0)
+ return xfrm_state_modify(XFRM_MSG_UPDSA, 0,
+ argc-1, argv+1);
+ if (matches(*argv, "allocspi") == 0)
+ return xfrm_state_allocspi(argc-1, argv+1);
+ if (matches(*argv, "delete") == 0)
+ return xfrm_state_get_or_delete(argc-1, argv+1, 1);
+ if (matches(*argv, "deleteall") == 0 || matches(*argv, "delall") == 0)
+ return xfrm_state_list_or_deleteall(argc-1, argv+1, 1);
+ if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
+ || matches(*argv, "lst") == 0)
+ return xfrm_state_list_or_deleteall(argc-1, argv+1, 0);
+ if (matches(*argv, "get") == 0)
+ return xfrm_state_get_or_delete(argc-1, argv+1, 0);
+ if (matches(*argv, "flush") == 0)
+ return xfrm_state_flush(argc-1, argv+1);
+ if (matches(*argv, "count") == 0) {
+ return xfrm_sad_getinfo(argc, argv);
+ }
+ if (matches(*argv, "help") == 0)
+ usage();
+ fprintf(stderr, "Command \"%s\" is unknown, try \"ip xfrm state help\".\n", *argv);
+ exit(-1);
+}