diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 13:14:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 13:14:35 +0000 |
commit | 9b8a97db9ec4b795e29e72289005fbc58484ebeb (patch) | |
tree | e24ca2d68215e57b4759fe5c032629821eabb250 /ip | |
parent | Initial commit. (diff) | |
download | iproute2-9b8a97db9ec4b795e29e72289005fbc58484ebeb.tar.xz iproute2-9b8a97db9ec4b795e29e72289005fbc58484ebeb.zip |
Adding upstream version 6.8.0.upstream/6.8.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ip')
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_ */ @@ -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(ðertype, *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); +} |